1
0
mirror of https://github.com/chylex/Minecraft-Phantom-Panel.git synced 2024-11-25 07:42:58 +01:00

Compare commits

..

1 Commits

Author SHA1 Message Date
df4b64cf8d
Implement actors in Agent via Akka.NET 2024-03-29 10:23:03 +01:00
7 changed files with 34 additions and 40 deletions

View File

@ -59,7 +59,7 @@ sealed class BackupScheduler : CancellableBackgroundTask {
} }
try { try {
return await context.Actor.Request(new InstanceActor.BackupInstanceCommand(backupManager)); return await context.Actor.Request(new InstanceActor.BackupInstanceCommand(backupManager, CancellationToken));
} finally { } finally {
backupSemaphore.Release(); backupSemaphore.Release();
} }

View File

@ -13,32 +13,28 @@ using Phantom.Utils.Logging;
namespace Phantom.Agent.Services.Instances; namespace Phantom.Agent.Services.Instances;
sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> { sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> {
public readonly record struct Init(AgentState AgentState, Guid InstanceGuid, string ShortName, InstanceServices InstanceServices, InstanceTicketManager InstanceTicketManager, CancellationToken ShutdownCancellationToken); public readonly record struct Init(Guid InstanceGuid, string ShortName, InstanceServices Services, AgentState AgentState);
public static Props<ICommand> Factory(Init init) { public static Props<ICommand> Factory(Init init) {
return Props<ICommand>.Create(() => new InstanceActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume, MailboxType = UnboundedJumpAheadMailbox.Name }); return Props<ICommand>.Create(() => new InstanceActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume, MailboxType = UnboundedJumpAheadMailbox.Name });
} }
private readonly AgentState agentState;
private readonly CancellationToken shutdownCancellationToken;
private readonly Guid instanceGuid; private readonly Guid instanceGuid;
private readonly InstanceServices instanceServices; private readonly InstanceServices services;
private readonly InstanceTicketManager instanceTicketManager; private readonly AgentState agentState;
private readonly InstanceContext context; private readonly InstanceContext context;
private IInstanceStatus currentStatus = InstanceStatus.NotRunning; private IInstanceStatus currentStatus = InstanceStatus.NotRunning;
private InstanceRunningState? runningState = null; private InstanceRunningState? runningState = null;
private InstanceActor(Init init) { private InstanceActor(Init init) {
this.agentState = init.AgentState;
this.instanceGuid = init.InstanceGuid; this.instanceGuid = init.InstanceGuid;
this.instanceServices = init.InstanceServices; this.services = init.Services;
this.instanceTicketManager = init.InstanceTicketManager; this.agentState = init.AgentState;
this.shutdownCancellationToken = init.ShutdownCancellationToken;
var logger = PhantomLogger.Create<InstanceActor>(init.ShortName); var logger = PhantomLogger.Create<InstanceActor>(init.ShortName);
this.context = new InstanceContext(instanceGuid, init.ShortName, logger, instanceServices, SelfTyped); this.context = new InstanceContext(instanceGuid, init.ShortName, logger, services, SelfTyped);
Receive<ReportInstanceStatusCommand>(ReportInstanceStatus); Receive<ReportInstanceStatusCommand>(ReportInstanceStatus);
ReceiveAsync<LaunchInstanceCommand>(LaunchInstance); ReceiveAsync<LaunchInstanceCommand>(LaunchInstance);
@ -56,7 +52,7 @@ sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> {
private void ReportCurrentStatus() { private void ReportCurrentStatus() {
agentState.UpdateInstance(new Instance(instanceGuid, currentStatus)); agentState.UpdateInstance(new Instance(instanceGuid, currentStatus));
instanceServices.ControllerConnection.Send(new ReportInstanceStatusMessage(instanceGuid, currentStatus)); services.ControllerConnection.Send(new ReportInstanceStatusMessage(instanceGuid, currentStatus));
} }
private void TransitionState(InstanceRunningState? newState) { private void TransitionState(InstanceRunningState? newState) {
@ -64,6 +60,10 @@ sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> {
return; return;
} }
if (runningState is not null && newState is null) {
context.Services.InstanceTicketManager.Release(runningState.Ticket);
}
runningState?.Dispose(); runningState?.Dispose();
runningState = newState; runningState = newState;
runningState?.Initialize(); runningState?.Initialize();
@ -73,13 +73,13 @@ sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> {
public sealed record ReportInstanceStatusCommand : ICommand; public sealed record ReportInstanceStatusCommand : ICommand;
public sealed record LaunchInstanceCommand(InstanceConfiguration Configuration, IServerLauncher Launcher, InstanceTicketManager.Ticket Ticket, bool IsRestarting) : ICommand; public sealed record LaunchInstanceCommand(InstanceConfiguration Configuration, IServerLauncher Launcher, InstanceTicketManager.Ticket Ticket, bool IsRestarting, CancellationToken CancellationToken) : ICommand;
public sealed record StopInstanceCommand(MinecraftStopStrategy StopStrategy) : ICommand; public sealed record StopInstanceCommand(MinecraftStopStrategy StopStrategy, CancellationToken CancellationToken) : ICommand;
public sealed record SendCommandToInstanceCommand(string Command) : ICommand, ICanReply<SendCommandToInstanceResult>; public sealed record SendCommandToInstanceCommand(string Command, CancellationToken CancellationToken) : ICommand, ICanReply<SendCommandToInstanceResult>;
public sealed record BackupInstanceCommand(BackupManager BackupManager) : ICommand, ICanReply<BackupCreationResult>; public sealed record BackupInstanceCommand(BackupManager BackupManager, CancellationToken CancellationToken) : ICommand, ICanReply<BackupCreationResult>;
public sealed record HandleProcessEndedCommand(IInstanceStatus Status) : ICommand, IJumpAhead; public sealed record HandleProcessEndedCommand(IInstanceStatus Status) : ICommand, IJumpAhead;
@ -93,9 +93,9 @@ sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> {
if (command.IsRestarting || runningState is null) { if (command.IsRestarting || runningState is null) {
SetAndReportStatus(command.IsRestarting ? InstanceStatus.Restarting : InstanceStatus.Launching); SetAndReportStatus(command.IsRestarting ? InstanceStatus.Restarting : InstanceStatus.Launching);
var newState = await InstanceLaunchProcedure.Run(context, command.Configuration, command.Launcher, instanceTicketManager, command.Ticket, SetAndReportStatus, shutdownCancellationToken); var newState = await InstanceLaunchProcedure.Run(context, command.Configuration, command.Launcher, command.Ticket, SetAndReportStatus, command.CancellationToken);
if (newState is null) { if (newState is null) {
instanceTicketManager.Release(command.Ticket); context.Services.InstanceTicketManager.Release(command.Ticket);
} }
TransitionState(newState); TransitionState(newState);
@ -110,8 +110,7 @@ sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> {
IInstanceStatus oldStatus = currentStatus; IInstanceStatus oldStatus = currentStatus;
SetAndReportStatus(InstanceStatus.Stopping); SetAndReportStatus(InstanceStatus.Stopping);
if (await InstanceStopProcedure.Run(context, command.StopStrategy, runningState, SetAndReportStatus, shutdownCancellationToken)) { if (await InstanceStopProcedure.Run(context, command.StopStrategy, runningState, SetAndReportStatus, command.CancellationToken)) {
instanceTicketManager.Release(runningState.Ticket);
TransitionState(null); TransitionState(null);
} }
else { else {
@ -124,7 +123,7 @@ sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> {
return SendCommandToInstanceResult.InstanceNotRunning; return SendCommandToInstanceResult.InstanceNotRunning;
} }
else { else {
return await runningState.SendCommand(command.Command, shutdownCancellationToken); return await runningState.SendCommand(command.Command, command.CancellationToken);
} }
} }
@ -133,7 +132,7 @@ sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> {
return new BackupCreationResult(BackupCreationResultKind.InstanceNotRunning); return new BackupCreationResult(BackupCreationResultKind.InstanceNotRunning);
} }
else { else {
return await command.BackupManager.CreateBackup(context.ShortName, runningState.Process, shutdownCancellationToken); return await command.BackupManager.CreateBackup(context.ShortName, runningState.Process, command.CancellationToken);
} }
} }
@ -141,13 +140,12 @@ sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> {
if (runningState is { Process.HasEnded: true }) { if (runningState is { Process.HasEnded: true }) {
SetAndReportStatus(command.Status); SetAndReportStatus(command.Status);
context.ReportEvent(InstanceEvent.Stopped); context.ReportEvent(InstanceEvent.Stopped);
instanceTicketManager.Release(runningState.Ticket);
TransitionState(null); TransitionState(null);
} }
} }
private async Task Shutdown(ShutdownCommand command) { private async Task Shutdown(ShutdownCommand command) {
await StopInstance(new StopInstanceCommand(MinecraftStopStrategy.Instant)); await StopInstance(new StopInstanceCommand(MinecraftStopStrategy.Instant, CancellationToken.None));
Context.Stop(Self); Context.Stop(Self);
} }
} }

View File

@ -20,7 +20,7 @@ namespace Phantom.Agent.Services.Instances;
sealed class InstanceManagerActor : ReceiveActor<InstanceManagerActor.ICommand> { sealed class InstanceManagerActor : ReceiveActor<InstanceManagerActor.ICommand> {
private static readonly ILogger Logger = PhantomLogger.Create<InstanceManagerActor>(); private static readonly ILogger Logger = PhantomLogger.Create<InstanceManagerActor>();
public readonly record struct Init(ControllerConnection ControllerConnection, AgentFolders AgentFolders, AgentState AgentState, JavaRuntimeRepository JavaRuntimeRepository, InstanceTicketManager InstanceTicketManager, TaskManager TaskManager, BackupManager BackupManager); public readonly record struct Init(ControllerConnection ControllerConnection, AgentFolders AgentFolders, AgentState AgentState, JavaRuntimeRepository JavaRuntimeRepository, InstanceTicketManager TicketManager, TaskManager TaskManager, BackupManager BackupManager);
public static Props<ICommand> Factory(Init init) { public static Props<ICommand> Factory(Init init) {
return Props<ICommand>.Create(() => new InstanceManagerActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume }); return Props<ICommand>.Create(() => new InstanceManagerActor(init), new ActorConfiguration { SupervisorStrategy = SupervisorStrategies.Resume });
@ -30,7 +30,6 @@ sealed class InstanceManagerActor : ReceiveActor<InstanceManagerActor.ICommand>
private readonly string basePath; private readonly string basePath;
private readonly InstanceServices instanceServices; private readonly InstanceServices instanceServices;
private readonly InstanceTicketManager instanceTicketManager;
private readonly Dictionary<Guid, InstanceInfo> instances = new (); private readonly Dictionary<Guid, InstanceInfo> instances = new ();
private readonly CancellationTokenSource shutdownCancellationTokenSource = new (); private readonly CancellationTokenSource shutdownCancellationTokenSource = new ();
@ -41,13 +40,12 @@ sealed class InstanceManagerActor : ReceiveActor<InstanceManagerActor.ICommand>
private InstanceManagerActor(Init init) { private InstanceManagerActor(Init init) {
this.agentState = init.AgentState; this.agentState = init.AgentState;
this.basePath = init.AgentFolders.InstancesFolderPath; this.basePath = init.AgentFolders.InstancesFolderPath;
this.instanceTicketManager = init.InstanceTicketManager;
this.shutdownCancellationToken = shutdownCancellationTokenSource.Token; this.shutdownCancellationToken = shutdownCancellationTokenSource.Token;
var minecraftServerExecutables = new MinecraftServerExecutables(init.AgentFolders.ServerExecutableFolderPath); var minecraftServerExecutables = new MinecraftServerExecutables(init.AgentFolders.ServerExecutableFolderPath);
var launchServices = new LaunchServices(minecraftServerExecutables, init.JavaRuntimeRepository); var launchServices = new LaunchServices(minecraftServerExecutables, init.JavaRuntimeRepository);
this.instanceServices = new InstanceServices(init.ControllerConnection, init.TaskManager, init.BackupManager, launchServices); this.instanceServices = new InstanceServices(init.ControllerConnection, init.TaskManager, init.TicketManager, init.BackupManager, launchServices);
ReceiveAndReply<ConfigureInstanceCommand, InstanceActionResult<ConfigureInstanceResult>>(ConfigureInstance); ReceiveAndReply<ConfigureInstanceCommand, InstanceActionResult<ConfigureInstanceResult>>(ConfigureInstance);
ReceiveAndReply<LaunchInstanceCommand, InstanceActionResult<LaunchInstanceResult>>(LaunchInstance); ReceiveAndReply<LaunchInstanceCommand, InstanceActionResult<LaunchInstanceResult>>(LaunchInstance);
@ -118,7 +116,7 @@ sealed class InstanceManagerActor : ReceiveActor<InstanceManagerActor.ICommand>
} }
} }
else { else {
var instanceInit = new InstanceActor.Init(agentState, instanceGuid, GetInstanceLoggerName(instanceGuid), instanceServices, instanceTicketManager, shutdownCancellationToken); var instanceInit = new InstanceActor.Init(instanceGuid, GetInstanceLoggerName(instanceGuid), instanceServices, agentState);
instances[instanceGuid] = instance = new InstanceInfo(Context.ActorOf(InstanceActor.Factory(instanceInit), "Instance-" + instanceGuid), configuration, launcher); instances[instanceGuid] = instance = new InstanceInfo(Context.ActorOf(InstanceActor.Factory(instanceInit), "Instance-" + instanceGuid), configuration, launcher);
Logger.Information("Created instance \"{Name}\" (GUID {Guid}).", configuration.InstanceName, instanceGuid); Logger.Information("Created instance \"{Name}\" (GUID {Guid}).", configuration.InstanceName, instanceGuid);
@ -139,7 +137,7 @@ sealed class InstanceManagerActor : ReceiveActor<InstanceManagerActor.ICommand>
return InstanceActionResult.General<LaunchInstanceResult>(InstanceActionGeneralResult.InstanceDoesNotExist); return InstanceActionResult.General<LaunchInstanceResult>(InstanceActionGeneralResult.InstanceDoesNotExist);
} }
var ticket = instanceTicketManager.Reserve(instanceInfo.Configuration); var ticket = instanceServices.InstanceTicketManager.Reserve(instanceInfo.Configuration);
if (ticket is Result<InstanceTicketManager.Ticket, LaunchInstanceResult>.Fail fail) { if (ticket is Result<InstanceTicketManager.Ticket, LaunchInstanceResult>.Fail fail) {
return InstanceActionResult.Concrete(fail.Error); return InstanceActionResult.Concrete(fail.Error);
} }
@ -155,7 +153,7 @@ sealed class InstanceManagerActor : ReceiveActor<InstanceManagerActor.ICommand>
} }
// TODO report status? // TODO report status?
instanceInfo.Actor.Tell(new InstanceActor.LaunchInstanceCommand(instanceInfo.Configuration, instanceInfo.Launcher, ticket.Value, IsRestarting: false)); instanceInfo.Actor.Tell(new InstanceActor.LaunchInstanceCommand(instanceInfo.Configuration, instanceInfo.Launcher, ticket.Value, IsRestarting: false, shutdownCancellationToken));
return InstanceActionResult.Concrete(LaunchInstanceResult.LaunchInitiated); return InstanceActionResult.Concrete(LaunchInstanceResult.LaunchInitiated);
} }
@ -175,7 +173,7 @@ sealed class InstanceManagerActor : ReceiveActor<InstanceManagerActor.ICommand>
} }
} }
instanceInfo.Actor.Tell(new InstanceActor.StopInstanceCommand(command.StopStrategy)); instanceInfo.Actor.Tell(new InstanceActor.StopInstanceCommand(command.StopStrategy, shutdownCancellationToken));
return InstanceActionResult.Concrete(StopInstanceResult.StopInitiated); return InstanceActionResult.Concrete(StopInstanceResult.StopInitiated);
} }
@ -186,7 +184,7 @@ sealed class InstanceManagerActor : ReceiveActor<InstanceManagerActor.ICommand>
} }
try { try {
return InstanceActionResult.Concrete(await instanceInfo.Actor.Request(new InstanceActor.SendCommandToInstanceCommand(command.Command), shutdownCancellationToken)); return InstanceActionResult.Concrete(await instanceInfo.Actor.Request(new InstanceActor.SendCommandToInstanceCommand(command.Command, shutdownCancellationToken), shutdownCancellationToken));
} catch (OperationCanceledException) { } catch (OperationCanceledException) {
return InstanceActionResult.General<SendCommandToInstanceResult>(InstanceActionGeneralResult.AgentShuttingDown); return InstanceActionResult.General<SendCommandToInstanceResult>(InstanceActionGeneralResult.AgentShuttingDown);
} }

View File

@ -5,4 +5,4 @@ using Phantom.Utils.Tasks;
namespace Phantom.Agent.Services.Instances; namespace Phantom.Agent.Services.Instances;
sealed record InstanceServices(ControllerConnection ControllerConnection, TaskManager TaskManager, BackupManager BackupManager, LaunchServices LaunchServices); sealed record InstanceServices(ControllerConnection ControllerConnection, TaskManager TaskManager, InstanceTicketManager InstanceTicketManager, BackupManager BackupManager, LaunchServices LaunchServices);

View File

@ -7,12 +7,12 @@ using Phantom.Utils.Tasks;
namespace Phantom.Agent.Services.Instances.State; namespace Phantom.Agent.Services.Instances.State;
static class InstanceLaunchProcedure { static class InstanceLaunchProcedure {
public static async Task<InstanceRunningState?> Run(InstanceContext context, InstanceConfiguration configuration, IServerLauncher launcher, InstanceTicketManager ticketManager, InstanceTicketManager.Ticket ticket, Action<IInstanceStatus> reportStatus, CancellationToken cancellationToken) { public static async Task<InstanceRunningState?> Run(InstanceContext context, InstanceConfiguration configuration, IServerLauncher launcher, InstanceTicketManager.Ticket ticket, Action<IInstanceStatus> reportStatus, CancellationToken cancellationToken) {
context.Logger.Information("Session starting..."); context.Logger.Information("Session starting...");
Result<InstanceProcess, InstanceLaunchFailReason> result; Result<InstanceProcess, InstanceLaunchFailReason> result;
if (ticketManager.IsValid(ticket)) { if (context.Services.InstanceTicketManager.IsValid(ticket)) {
try { try {
result = await LaunchInstance(context, launcher, reportStatus, cancellationToken); result = await LaunchInstance(context, launcher, reportStatus, cancellationToken);
} catch (OperationCanceledException) { } catch (OperationCanceledException) {

View File

@ -72,7 +72,7 @@ sealed class InstanceRunningState : IDisposable {
else { else {
context.Logger.Information("Session ended unexpectedly, restarting..."); context.Logger.Information("Session ended unexpectedly, restarting...");
context.ReportEvent(InstanceEvent.Crashed); context.ReportEvent(InstanceEvent.Crashed);
context.Actor.Tell(new InstanceActor.LaunchInstanceCommand(configuration, launcher, Ticket, IsRestarting: true)); context.Actor.Tell(new InstanceActor.LaunchInstanceCommand(configuration, launcher, Ticket, IsRestarting: true, cancellationToken));
} }
} }

View File

@ -75,8 +75,6 @@ static class InstanceStopProcedure {
// Ignore. // Ignore.
} catch (ObjectDisposedException e) when (e.ObjectName == typeof(Process).FullName && process.HasEnded) { } catch (ObjectDisposedException e) when (e.ObjectName == typeof(Process).FullName && process.HasEnded) {
// Ignore. // Ignore.
} catch (IOException e) when (e.HResult == -2147024664 /* The pipe is being closed */) {
// Ignore.
} catch (Exception e) { } catch (Exception e) {
context.Logger.Warning(e, "Caught exception while sending stop command."); context.Logger.Warning(e, "Caught exception while sending stop command.");
} }