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

Compare commits

..

2 Commits

9 changed files with 62 additions and 70 deletions

View File

@ -5,7 +5,6 @@ namespace Phantom.Agent.Minecraft.Instance;
public sealed class InstanceProcess : IDisposable { public sealed class InstanceProcess : IDisposable {
public InstanceProperties InstanceProperties { get; } public InstanceProperties InstanceProperties { get; }
public CancellableSemaphore BackupSemaphore { get; } = new (1, 1);
private readonly RingBuffer<string> outputBuffer = new (100); private readonly RingBuffer<string> outputBuffer = new (100);
private event EventHandler<string>? OutputEvent; private event EventHandler<string>? OutputEvent;
@ -61,7 +60,6 @@ public sealed class InstanceProcess : IDisposable {
public void Dispose() { public void Dispose() {
process.Dispose(); process.Dispose();
BackupSemaphore.Dispose();
OutputEvent = null; OutputEvent = null;
Ended = null; Ended = null;
} }

View File

@ -16,26 +16,12 @@ sealed class BackupManager : IDisposable {
this.compressionSemaphore = new SemaphoreSlim(maxConcurrentCompressionTasks, maxConcurrentCompressionTasks); this.compressionSemaphore = new SemaphoreSlim(maxConcurrentCompressionTasks, maxConcurrentCompressionTasks);
} }
public void Dispose() { public Task<BackupCreationResult> CreateBackup(string loggerName, InstanceProcess process, CancellationToken cancellationToken) {
compressionSemaphore.Dispose(); return new BackupCreator(this, loggerName, process, cancellationToken).CreateBackup();
} }
public async Task<BackupCreationResult> CreateBackup(string loggerName, InstanceProcess process, CancellationToken cancellationToken) { public void Dispose() {
try { compressionSemaphore.Dispose();
if (!await process.BackupSemaphore.Wait(TimeSpan.FromSeconds(1), cancellationToken)) {
return new BackupCreationResult(BackupCreationResultKind.BackupAlreadyRunning);
}
} catch (ObjectDisposedException) {
return new BackupCreationResult(BackupCreationResultKind.InstanceNotRunning);
} catch (OperationCanceledException) {
return new BackupCreationResult(BackupCreationResultKind.InstanceNotRunning);
}
try {
return await new BackupCreator(this, loggerName, process, cancellationToken).CreateBackup();
} finally {
process.BackupSemaphore.Release();
}
} }
private sealed class BackupCreator { private sealed class BackupCreator {
@ -89,7 +75,7 @@ sealed class BackupManager : IDisposable {
try { try {
await dispatcher.EnableAutomaticSaving(); await dispatcher.EnableAutomaticSaving();
} catch (OperationCanceledException) { } catch (OperationCanceledException) {
// ignore // Ignore.
} catch (Exception e) { } catch (Exception e) {
resultBuilder.Warnings |= BackupCreationWarnings.CouldNotRestoreAutomaticSaving; resultBuilder.Warnings |= BackupCreationWarnings.CouldNotRestoreAutomaticSaving;
logger.Error(e, "Caught exception while enabling automatic saving after creating an instance backup."); logger.Error(e, "Caught exception while enabling automatic saving after creating an instance backup.");

View File

@ -1,5 +1,7 @@
using Phantom.Agent.Minecraft.Instance; using Phantom.Agent.Minecraft.Instance;
using Phantom.Agent.Minecraft.Server; using Phantom.Agent.Minecraft.Server;
using Phantom.Agent.Services.Instances;
using Phantom.Agent.Services.Instances.Procedures;
using Phantom.Common.Data.Backups; using Phantom.Common.Data.Backups;
using Phantom.Common.Logging; using Phantom.Common.Logging;
using Phantom.Utils.Runtime; using Phantom.Utils.Runtime;
@ -12,21 +14,22 @@ sealed class BackupScheduler : CancellableBackgroundTask {
private static readonly TimeSpan BackupInterval = TimeSpan.FromMinutes(30); private static readonly TimeSpan BackupInterval = TimeSpan.FromMinutes(30);
private static readonly TimeSpan BackupFailureRetryDelay = TimeSpan.FromMinutes(5); private static readonly TimeSpan BackupFailureRetryDelay = TimeSpan.FromMinutes(5);
private readonly string loggerName;
private readonly BackupManager backupManager; private readonly BackupManager backupManager;
private readonly InstanceProcess process; private readonly InstanceProcess process;
private readonly IInstanceContext context;
private readonly SemaphoreSlim backupSemaphore = new (1, 1);
private readonly int serverPort; private readonly int serverPort;
private readonly ServerStatusProtocol serverStatusProtocol; private readonly ServerStatusProtocol serverStatusProtocol;
private readonly ManualResetEventSlim serverOutputWhileWaitingForOnlinePlayers = new (); private readonly ManualResetEventSlim serverOutputWhileWaitingForOnlinePlayers = new ();
public event EventHandler<BackupCreationResult>? BackupCompleted; public event EventHandler<BackupCreationResult>? BackupCompleted;
public BackupScheduler(TaskManager taskManager, BackupManager backupManager, InstanceProcess process, int serverPort, string loggerName) : base(PhantomLogger.Create<BackupScheduler>(loggerName), taskManager, "Backup scheduler for " + loggerName) { public BackupScheduler(TaskManager taskManager, BackupManager backupManager, InstanceProcess process, IInstanceContext context, int serverPort) : base(PhantomLogger.Create<BackupScheduler>(context.ShortName), taskManager, "Backup scheduler for " + context.ShortName) {
this.loggerName = loggerName;
this.backupManager = backupManager; this.backupManager = backupManager;
this.process = process; this.process = process;
this.context = context;
this.serverPort = serverPort; this.serverPort = serverPort;
this.serverStatusProtocol = new ServerStatusProtocol(loggerName); this.serverStatusProtocol = new ServerStatusProtocol(context.ShortName);
Start(); Start();
} }
@ -51,7 +54,17 @@ sealed class BackupScheduler : CancellableBackgroundTask {
} }
private async Task<BackupCreationResult> CreateBackup() { private async Task<BackupCreationResult> CreateBackup() {
return await backupManager.CreateBackup(loggerName, process, CancellationToken.None); if (!await backupSemaphore.WaitAsync(TimeSpan.FromSeconds(1))) {
return new BackupCreationResult(BackupCreationResultKind.BackupAlreadyRunning);
}
try {
var procedure = new BackupInstanceProcedure(backupManager);
context.EnqueueProcedure(procedure);
return await procedure.Result;
} finally {
backupSemaphore.Release();
}
} }
private async Task WaitForOnlinePlayers() { private async Task WaitForOnlinePlayers() {
@ -96,6 +109,7 @@ sealed class BackupScheduler : CancellableBackgroundTask {
} }
protected override void Dispose() { protected override void Dispose() {
backupSemaphore.Dispose();
serverOutputWhileWaitingForOnlinePlayers.Dispose(); serverOutputWhileWaitingForOnlinePlayers.Dispose();
} }
} }

View File

@ -63,8 +63,10 @@ sealed class Instance : IAsyncDisposable {
private void ReportAndSetStatus(IInstanceStatus status) { private void ReportAndSetStatus(IInstanceStatus status) {
TryUpdateStatus("Report status of instance " + shortName + " as " + status.GetType().Name, async () => { TryUpdateStatus("Report status of instance " + shortName + " as " + status.GetType().Name, async () => {
currentStatus = status; if (status != currentStatus) {
await ServerMessaging.Send(new ReportInstanceStatusMessage(Configuration.InstanceGuid, status)); currentStatus = status;
await ServerMessaging.Send(new ReportInstanceStatusMessage(Configuration.InstanceGuid, status));
}
}); });
} }
@ -121,6 +123,7 @@ sealed class Instance : IAsyncDisposable {
configurationSemaphore.Release(); configurationSemaphore.Release();
} }
ReportAndSetStatus(InstanceStatus.Launching);
await procedureManager.Enqueue(procedure); await procedureManager.Enqueue(procedure);
return LaunchInstanceResult.LaunchInitiated; return LaunchInstanceResult.LaunchInitiated;
} }
@ -134,6 +137,7 @@ sealed class Instance : IAsyncDisposable {
return StopInstanceResult.InstanceAlreadyStopping; return StopInstanceResult.InstanceAlreadyStopping;
} }
ReportAndSetStatus(InstanceStatus.Stopping);
await procedureManager.Enqueue(new StopInstanceProcedure(stopStrategy)); await procedureManager.Enqueue(new StopInstanceProcedure(stopStrategy));
return StopInstanceResult.StopInitiated; return StopInstanceResult.StopInitiated;
} }

View File

@ -32,10 +32,6 @@ sealed class InstanceProcedureManager : IAsyncDisposable {
return (await currentProcedure.Get(cancellationToken))?.Procedure; return (await currentProcedure.Get(cancellationToken))?.Procedure;
} }
public async Task CancelCurrentProcedure() {
(await currentProcedure.Get(shutdownCancellationTokenSource.Token))?.CancellationTokenSource.Cancel();
}
private async Task Run() { private async Task Run() {
try { try {
var shutdownCancellationToken = shutdownCancellationTokenSource.Token; var shutdownCancellationToken = shutdownCancellationTokenSource.Token;
@ -77,7 +73,7 @@ sealed class InstanceProcedureManager : IAsyncDisposable {
public async ValueTask DisposeAsync() { public async ValueTask DisposeAsync() {
shutdownCancellationTokenSource.Cancel(); shutdownCancellationTokenSource.Cancel();
await CancelCurrentProcedure(); (await currentProcedure.Get(CancellationToken.None))?.CancellationTokenSource.Cancel();
await procedureQueueFinished.WaitHandle.WaitOneAsync(); await procedureQueueFinished.WaitHandle.WaitOneAsync();
currentProcedure.Dispose(); currentProcedure.Dispose();

View File

@ -0,0 +1,29 @@
using Phantom.Agent.Services.Backups;
using Phantom.Agent.Services.Instances.States;
using Phantom.Common.Data.Backups;
namespace Phantom.Agent.Services.Instances.Procedures;
sealed record BackupInstanceProcedure(BackupManager BackupManager) : IInstanceProcedure {
private readonly TaskCompletionSource<BackupCreationResult> resultCompletionSource = new ();
public Task<BackupCreationResult> Result => resultCompletionSource.Task;
public async Task<IInstanceState?> Run(IInstanceContext context, CancellationToken cancellationToken) {
if (context.CurrentState is not InstanceRunningState runningState || runningState.Process.HasEnded) {
resultCompletionSource.SetResult(new BackupCreationResult(BackupCreationResultKind.InstanceNotRunning));
return null;
}
try {
var result = await BackupManager.CreateBackup(context.ShortName, runningState.Process, cancellationToken);
resultCompletionSource.SetResult(result);
} catch (OperationCanceledException) {
resultCompletionSource.SetCanceled(cancellationToken);
} catch (Exception e) {
resultCompletionSource.SetException(e);
}
return null;
}
}

View File

@ -33,6 +33,7 @@ sealed record StopInstanceProcedure(MinecraftStopStrategy StopStrategy) : IInsta
try { try {
// Too late to cancel the stop procedure now. // Too late to cancel the stop procedure now.
if (!process.HasEnded) { if (!process.HasEnded) {
context.Logger.Information("Session stopping now.");
await DoStop(context, process); await DoStop(context, process);
} }
} finally { } finally {
@ -66,14 +67,6 @@ sealed record StopInstanceProcedure(MinecraftStopStrategy StopStrategy) : IInsta
} }
private async Task DoStop(IInstanceContext context, InstanceProcess process) { private async Task DoStop(IInstanceContext context, InstanceProcess process) {
context.Logger.Information("Session stopping now.");
// Do not release the semaphore after this point.
if (!await process.BackupSemaphore.CancelAndWait(TimeSpan.FromSeconds(1))) {
context.Logger.Information("Waiting for backup to finish...");
await process.BackupSemaphore.CancelAndWait(Timeout.InfiniteTimeSpan);
}
context.Logger.Information("Sending stop command..."); context.Logger.Information("Sending stop command...");
await TrySendStopCommand(context, process); await TrySendStopCommand(context, process);

View File

@ -29,7 +29,7 @@ sealed class InstanceRunningState : IInstanceState, IDisposable {
this.logSender = new InstanceLogSender(context.Services.TaskManager, configuration.InstanceGuid, context.ShortName); this.logSender = new InstanceLogSender(context.Services.TaskManager, configuration.InstanceGuid, context.ShortName);
this.backupScheduler = new BackupScheduler(context.Services.TaskManager, context.Services.BackupManager, process, configuration.ServerPort, context.ShortName); this.backupScheduler = new BackupScheduler(context.Services.TaskManager, context.Services.BackupManager, process, context, configuration.ServerPort);
this.backupScheduler.BackupCompleted += OnScheduledBackupCompleted; this.backupScheduler.BackupCompleted += OnScheduledBackupCompleted;
} }

View File

@ -1,28 +0,0 @@
namespace Phantom.Utils.Runtime;
public sealed class CancellableSemaphore : IDisposable {
private readonly SemaphoreSlim semaphore;
private readonly CancellationTokenSource cancellationTokenSource = new ();
public CancellableSemaphore(int initialCount, int maxCount) {
this.semaphore = new SemaphoreSlim(initialCount, maxCount);
}
public async Task<bool> Wait(TimeSpan timeout, CancellationToken cancellationToken) {
return await semaphore.WaitAsync(timeout, cancellationTokenSource.Token).WaitAsync(cancellationToken);
}
public async Task<bool> CancelAndWait(TimeSpan timeout) {
cancellationTokenSource.Cancel();
return await semaphore.WaitAsync(timeout);
}
public void Release() {
semaphore.Release();
}
public void Dispose() {
semaphore.Dispose();
cancellationTokenSource.Dispose();
}
}