From f57d33cb1e2b9743c7455da44b2e07d0b037cb64 Mon Sep 17 00:00:00 2001 From: ChiaraBm Date: Tue, 29 Jul 2025 21:14:41 +0200 Subject: [PATCH] Fixed usage of IAsyncObservable. Added runtime exit handler --- .../ServerSys/Abstractions/IConsole.cs | 4 +- .../ServerSys/Abstractions/IInstaller.cs | 2 +- .../ServerSys/Abstractions/IProvisioner.cs | 2 +- .../ServerSys/Abstractions/Server.cs | 37 +++++++++++++------ .../Implementations/DockerConsole.cs | 13 ++----- .../Implementations/DockerInstaller.cs | 25 +++++++++---- .../Implementations/DockerProvisioner.cs | 18 +++++---- .../Services/DockerEventService.cs | 34 ++++++++++------- MoonlightServers.Daemon/Startup.cs | 7 ++-- 9 files changed, 85 insertions(+), 57 deletions(-) diff --git a/MoonlightServers.Daemon/ServerSys/Abstractions/IConsole.cs b/MoonlightServers.Daemon/ServerSys/Abstractions/IConsole.cs index 60270a9..7545189 100644 --- a/MoonlightServers.Daemon/ServerSys/Abstractions/IConsole.cs +++ b/MoonlightServers.Daemon/ServerSys/Abstractions/IConsole.cs @@ -2,8 +2,8 @@ namespace MoonlightServers.Daemon.ServerSys.Abstractions; public interface IConsole : IServerComponent { - public IObservable OnOutput { get; } - public IObservable OnInput { get; } + public IAsyncObservable OnOutput { get; } + public IAsyncObservable OnInput { get; } public Task AttachToRuntime(); public Task AttachToInstallation(); diff --git a/MoonlightServers.Daemon/ServerSys/Abstractions/IInstaller.cs b/MoonlightServers.Daemon/ServerSys/Abstractions/IInstaller.cs index 4b2e43a..5dad421 100644 --- a/MoonlightServers.Daemon/ServerSys/Abstractions/IInstaller.cs +++ b/MoonlightServers.Daemon/ServerSys/Abstractions/IInstaller.cs @@ -2,7 +2,7 @@ namespace MoonlightServers.Daemon.ServerSys.Abstractions; public interface IInstaller : IServerComponent { - public IObservable OnExited { get; } + public IAsyncObservable OnExited { get; } public bool IsRunning { get; } public Task Start(); diff --git a/MoonlightServers.Daemon/ServerSys/Abstractions/IProvisioner.cs b/MoonlightServers.Daemon/ServerSys/Abstractions/IProvisioner.cs index b2c512c..5a74394 100644 --- a/MoonlightServers.Daemon/ServerSys/Abstractions/IProvisioner.cs +++ b/MoonlightServers.Daemon/ServerSys/Abstractions/IProvisioner.cs @@ -2,7 +2,7 @@ namespace MoonlightServers.Daemon.ServerSys.Abstractions; public interface IProvisioner : IServerComponent { - public IObservable OnExited { get; } + public IAsyncObservable OnExited { get; } public bool IsProvisioned { get; } public Task Provision(); diff --git a/MoonlightServers.Daemon/ServerSys/Abstractions/Server.cs b/MoonlightServers.Daemon/ServerSys/Abstractions/Server.cs index 0404b03..a734299 100644 --- a/MoonlightServers.Daemon/ServerSys/Abstractions/Server.cs +++ b/MoonlightServers.Daemon/ServerSys/Abstractions/Server.cs @@ -1,4 +1,6 @@ +using System.Reactive.Linq; using System.Reactive.Subjects; +using MoonlightServers.Daemon.Extensions; using MoonlightServers.Daemon.ServerSystem; using Stateless; @@ -14,13 +16,13 @@ public class Server : IAsyncDisposable public IStatistics Statistics { get; private set; } public StateMachine StateMachine { get; private set; } public ServerContext Context { get; private set; } - public IObservable OnState => OnStateSubject; + public IAsyncObservable OnState => OnStateSubject.ToAsyncObservable(); private readonly Subject OnStateSubject = new(); private readonly ILogger Logger; - private IDisposable? ProvisionExitSubscription; - private IDisposable? InstallerExitSubscription; + private IAsyncDisposable? ProvisionExitSubscription; + private IAsyncDisposable? InstallerExitSubscription; public Server( ILogger logger, @@ -78,14 +80,14 @@ public class Server : IAsyncDisposable CreateStateMachine(restoredState); // Setup event handling - ProvisionExitSubscription = Provisioner.OnExited.Subscribe(o => + ProvisionExitSubscription = await Provisioner.OnExited.SubscribeAsync(async o => { - StateMachine.Fire(ServerTrigger.Exited); + await StateMachine.FireAsync(ServerTrigger.Exited); }); - InstallerExitSubscription = Installer.OnExited.Subscribe(o => + InstallerExitSubscription = await Installer.OnExited.SubscribeAsync(async o => { - StateMachine.Fire(ServerTrigger.Exited); + await StateMachine.FireAsync(ServerTrigger.Exited); }); } @@ -126,10 +128,15 @@ public class Server : IAsyncDisposable // Handle transitions StateMachine.Configure(ServerState.Starting) - .OnEntryAsync(HandleStart); + .OnEntryAsync(HandleStart) + .OnExitFromAsync(ServerTrigger.Exited, HandleRuntimeExit); + + StateMachine.Configure(ServerState.Online) + .OnExitFromAsync(ServerTrigger.Exited, HandleRuntimeExit); StateMachine.Configure(ServerState.Stopping) - .OnEntryFromAsync(ServerTrigger.Stop, HandleStop); + .OnEntryFromAsync(ServerTrigger.Stop, HandleStop) + .OnExitFromAsync(ServerTrigger.Exited, HandleRuntimeExit); } #region State machine handlers @@ -184,15 +191,23 @@ public class Server : IAsyncDisposable await Provisioner.Stop(); } + private async Task HandleRuntimeExit() + { + Logger.LogDebug("Deprovisioning"); + await Console.WriteToMoonlight("Deprovisioning"); + + await Provisioner.Deprovision(); + } + #endregion public async ValueTask DisposeAsync() { if (ProvisionExitSubscription != null) - ProvisionExitSubscription.Dispose(); + await ProvisionExitSubscription.DisposeAsync(); if (InstallerExitSubscription != null) - InstallerExitSubscription.Dispose(); + await InstallerExitSubscription.DisposeAsync(); await Console.DisposeAsync(); await FileSystem.DisposeAsync(); diff --git a/MoonlightServers.Daemon/ServerSys/Implementations/DockerConsole.cs b/MoonlightServers.Daemon/ServerSys/Implementations/DockerConsole.cs index e1fa561..e87b8ce 100644 --- a/MoonlightServers.Daemon/ServerSys/Implementations/DockerConsole.cs +++ b/MoonlightServers.Daemon/ServerSys/Implementations/DockerConsole.cs @@ -3,7 +3,6 @@ using System.Reactive.Subjects; using System.Text; using Docker.DotNet; using Docker.DotNet.Models; -using Microsoft.Extensions.Options; using MoonCore.Helpers; using MoonlightServers.Daemon.ServerSys.Abstractions; @@ -11,8 +10,8 @@ namespace MoonlightServers.Daemon.ServerSys.Implementations; public class DockerConsole : IConsole { - public IObservable OnOutput => OnOutputSubject; - public IObservable OnInput => OnInputSubject; + public IAsyncObservable OnOutput => OnOutputSubject.ToAsyncObservable(); + public IAsyncObservable OnInput => OnInputSubject.ToAsyncObservable(); private readonly Subject OnOutputSubject = new(); private readonly Subject OnInputSubject = new(); @@ -146,13 +145,7 @@ public class DockerConsole : IConsole OutputCache.Add(content); if (OutputCache.Count > 250) // TODO: Config - { - // TODO: Replace with remove range once it becomes available in mooncore - for (var i = 0; i < 100; i++) - { - OutputCache.RemoveAt(i); - } - } + OutputCache.RemoveRange(0, 100); OnOutputSubject.OnNext(content); return Task.CompletedTask; diff --git a/MoonlightServers.Daemon/ServerSys/Implementations/DockerInstaller.cs b/MoonlightServers.Daemon/ServerSys/Implementations/DockerInstaller.cs index 45941ad..6083a94 100644 --- a/MoonlightServers.Daemon/ServerSys/Implementations/DockerInstaller.cs +++ b/MoonlightServers.Daemon/ServerSys/Implementations/DockerInstaller.cs @@ -1,21 +1,30 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using MoonlightServers.Daemon.ServerSys.Abstractions; +using MoonlightServers.Daemon.Services; namespace MoonlightServers.Daemon.ServerSys.Implementations; public class DockerInstaller : IInstaller { - public IObservable OnExited => OnExitedSubject; + public IAsyncObservable OnExited => OnExitedSubject.ToAsyncObservable(); public bool IsRunning { get; private set; } = false; - - private readonly Subject OnExitedSubject = new(); - - private readonly ILogger Logger; - public DockerInstaller(ILogger logger) + private readonly Subject OnExitedSubject = new(); + + private readonly ILogger Logger; + private readonly DockerEventService EventService; + + private string? ContainerId; + private string? ContainerName; + + public DockerInstaller( + ILogger logger, + DockerEventService eventService + ) { Logger = logger; + EventService = eventService; } public Task Initialize() @@ -27,7 +36,7 @@ public class DockerInstaller : IInstaller { throw new NotImplementedException(); } - + public Task Start() { throw new NotImplementedException(); @@ -47,7 +56,7 @@ public class DockerInstaller : IInstaller { throw new NotImplementedException(); } - + public async ValueTask DisposeAsync() { OnExitedSubject.Dispose(); diff --git a/MoonlightServers.Daemon/ServerSys/Implementations/DockerProvisioner.cs b/MoonlightServers.Daemon/ServerSys/Implementations/DockerProvisioner.cs index 407a91c..c03dd2a 100644 --- a/MoonlightServers.Daemon/ServerSys/Implementations/DockerProvisioner.cs +++ b/MoonlightServers.Daemon/ServerSys/Implementations/DockerProvisioner.cs @@ -11,7 +11,7 @@ namespace MoonlightServers.Daemon.ServerSys.Implementations; public class DockerProvisioner : IProvisioner { - public IObservable OnExited => OnExitedSubject; + public IAsyncObservable OnExited => OnExitedSubject.ToAsyncObservable(); public bool IsProvisioned { get; private set; } private readonly DockerClient DockerClient; @@ -27,7 +27,7 @@ public class DockerProvisioner : IProvisioner private string? ContainerId; private string ContainerName; - private IDisposable? ContainerEventSubscription; + private IAsyncDisposable? ContainerEventSubscription; public DockerProvisioner( DockerClient dockerClient, @@ -54,9 +54,9 @@ public class DockerProvisioner : IProvisioner { ContainerName = $"moonlight-runtime-{Context.Configuration.Id}"; - ContainerEventSubscription = EventService + ContainerEventSubscription = await EventService .OnContainerEvent - .Subscribe(HandleContainerEvent); + .SubscribeAsync(HandleContainerEvent); // Check for any already existing runtime container to reclaim Logger.LogDebug("Searching for orphan container to reclaim"); @@ -74,17 +74,19 @@ public class DockerProvisioner : IProvisioner } } - private void HandleContainerEvent(Message message) + private ValueTask HandleContainerEvent(Message message) { // Only handle events for our own container if (message.ID != ContainerId) - return; + return ValueTask.CompletedTask; // Only handle die events if (message.Action != "die") - return; + return ValueTask.CompletedTask; OnExitedSubject.OnNext(message); + + return ValueTask.CompletedTask; } public Task Sync() @@ -253,6 +255,6 @@ public class DockerProvisioner : IProvisioner OnExitedSubject.Dispose(); if (ContainerEventSubscription != null) - ContainerEventSubscription.Dispose(); + await ContainerEventSubscription.DisposeAsync(); } } \ No newline at end of file diff --git a/MoonlightServers.Daemon/Services/DockerEventService.cs b/MoonlightServers.Daemon/Services/DockerEventService.cs index 54627d1..f1f134c 100644 --- a/MoonlightServers.Daemon/Services/DockerEventService.cs +++ b/MoonlightServers.Daemon/Services/DockerEventService.cs @@ -1,3 +1,4 @@ +using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Reactive.Subjects; using Docker.DotNet; @@ -10,9 +11,9 @@ public class DockerEventService : BackgroundService private readonly ILogger Logger; private readonly DockerClient DockerClient; - public IObservable OnContainerEvent => OnContainerSubject; - public IObservable OnImageEvent => OnImageSubject; - public IObservable OnNetworkEvent => OnNetworkSubject; + public IAsyncObservable OnContainerEvent => OnContainerSubject.ToAsyncObservable().ObserveOn(TaskPoolAsyncScheduler.Default); + public IAsyncObservable OnImageEvent => OnImageSubject.ToAsyncObservable().ObserveOn(TaskPoolAsyncScheduler.Default); + public IAsyncObservable OnNetworkEvent => OnNetworkSubject.ToAsyncObservable().ObserveOn(TaskPoolAsyncScheduler.Default); private readonly Subject OnContainerSubject = new(); private readonly Subject OnImageSubject = new(); @@ -39,19 +40,26 @@ public class DockerEventService : BackgroundService new ContainerEventsParameters(), new Progress(message => { - switch (message.Type) + try { - case "container": - OnContainerSubject.OnNext(message); - break; + switch (message.Type) + { + case "container": + OnContainerSubject.OnNext(message); + break; - case "image": - OnImageSubject.OnNext(message); - break; + case "image": + OnImageSubject.OnNext(message); + break; - case "network": - OnNetworkSubject.OnNext(message); - break; + case "network": + OnNetworkSubject.OnNext(message); + break; + } + } + catch (Exception e) + { + Logger.LogError(e, "An error occured while processing docker event"); } }), stoppingToken diff --git a/MoonlightServers.Daemon/Startup.cs b/MoonlightServers.Daemon/Startup.cs index fb91e02..cd44ddc 100644 --- a/MoonlightServers.Daemon/Startup.cs +++ b/MoonlightServers.Daemon/Startup.cs @@ -107,12 +107,13 @@ public class Startup var factory = WebApplication.Services.GetRequiredService(); var server = factory.CreateServer(config); - using var consoleSub = server.Console.OnOutput - .Subscribe(Console.Write); + await using var consoleSub = await server.Console.OnOutput + .SubscribeAsync(Console.Write); - using var stateSub = server.OnState.Subscribe(state => + await using var stateSub = await server.OnState.SubscribeAsync(state => { Console.WriteLine($"State: {state}"); + return ValueTask.CompletedTask; }); await server.Initialize();