1
0
mirror of https://github.com/chylex/Minecraft-Phantom-Panel.git synced 2024-10-18 15:42:50 +02:00

Compare commits

..

No commits in common. "2180e0c067028a3638a8de0d3a4ff9b143ee68ef" and "01d6648b1fea9efc78eb8d99abb8ca07f06ada49" have entirely different histories.

9 changed files with 70 additions and 62 deletions

View File

@ -5,6 +5,7 @@ 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;
@ -60,6 +61,7 @@ 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,14 +16,28 @@ sealed class BackupManager : IDisposable {
this.compressionSemaphore = new SemaphoreSlim(maxConcurrentCompressionTasks, maxConcurrentCompressionTasks); this.compressionSemaphore = new SemaphoreSlim(maxConcurrentCompressionTasks, maxConcurrentCompressionTasks);
} }
public Task<BackupCreationResult> CreateBackup(string loggerName, InstanceProcess process, CancellationToken cancellationToken) {
return new BackupCreator(this, loggerName, process, cancellationToken).CreateBackup();
}
public void Dispose() { public void Dispose() {
compressionSemaphore.Dispose(); compressionSemaphore.Dispose();
} }
public async Task<BackupCreationResult> CreateBackup(string loggerName, InstanceProcess process, CancellationToken cancellationToken) {
try {
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 {
private readonly BackupManager manager; private readonly BackupManager manager;
private readonly string loggerName; private readonly string loggerName;
@ -75,7 +89,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,7 +1,5 @@
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;
@ -14,22 +12,21 @@ 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, IInstanceContext context, int serverPort) : base(PhantomLogger.Create<BackupScheduler>(context.ShortName), taskManager, "Backup scheduler for " + context.ShortName) { public BackupScheduler(TaskManager taskManager, BackupManager backupManager, InstanceProcess process, int serverPort, string loggerName) : base(PhantomLogger.Create<BackupScheduler>(loggerName), taskManager, "Backup scheduler for " + loggerName) {
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(context.ShortName); this.serverStatusProtocol = new ServerStatusProtocol(loggerName);
Start(); Start();
} }
@ -54,17 +51,7 @@ sealed class BackupScheduler : CancellableBackgroundTask {
} }
private async Task<BackupCreationResult> CreateBackup() { private async Task<BackupCreationResult> CreateBackup() {
if (!await backupSemaphore.WaitAsync(TimeSpan.FromSeconds(1))) { return await backupManager.CreateBackup(loggerName, process, CancellationToken.None);
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() {
@ -109,7 +96,6 @@ sealed class BackupScheduler : CancellableBackgroundTask {
} }
protected override void Dispose() { protected override void Dispose() {
backupSemaphore.Dispose();
serverOutputWhileWaitingForOnlinePlayers.Dispose(); serverOutputWhileWaitingForOnlinePlayers.Dispose();
} }
} }

View File

@ -63,10 +63,8 @@ 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 () => {
if (status != currentStatus) { currentStatus = status;
currentStatus = status; await ServerMessaging.Send(new ReportInstanceStatusMessage(Configuration.InstanceGuid, status));
await ServerMessaging.Send(new ReportInstanceStatusMessage(Configuration.InstanceGuid, status));
}
}); });
} }
@ -123,7 +121,6 @@ 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;
} }
@ -137,7 +134,6 @@ 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,6 +32,10 @@ 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;
@ -73,7 +77,7 @@ sealed class InstanceProcedureManager : IAsyncDisposable {
public async ValueTask DisposeAsync() { public async ValueTask DisposeAsync() {
shutdownCancellationTokenSource.Cancel(); shutdownCancellationTokenSource.Cancel();
(await currentProcedure.Get(CancellationToken.None))?.CancellationTokenSource.Cancel(); await CancelCurrentProcedure();
await procedureQueueFinished.WaitHandle.WaitOneAsync(); await procedureQueueFinished.WaitHandle.WaitOneAsync();
currentProcedure.Dispose(); currentProcedure.Dispose();

View File

@ -1,29 +0,0 @@
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,7 +33,6 @@ 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 {
@ -67,6 +66,14 @@ 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, context, configuration.ServerPort); this.backupScheduler = new BackupScheduler(context.Services.TaskManager, context.Services.BackupManager, process, configuration.ServerPort, context.ShortName);
this.backupScheduler.BackupCompleted += OnScheduledBackupCompleted; this.backupScheduler.BackupCompleted += OnScheduledBackupCompleted;
} }

View File

@ -0,0 +1,28 @@
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();
}
}