mirror of
https://github.com/chylex/Minecraft-Phantom-Panel.git
synced 2024-11-26 01:42:53 +01:00
Compare commits
No commits in common. "2180e0c067028a3638a8de0d3a4ff9b143ee68ef" and "01d6648b1fea9efc78eb8d99abb8ca07f06ada49" have entirely different histories.
2180e0c067
...
01d6648b1f
@ -5,6 +5,7 @@ namespace Phantom.Agent.Minecraft.Instance;
|
||||
|
||||
public sealed class InstanceProcess : IDisposable {
|
||||
public InstanceProperties InstanceProperties { get; }
|
||||
public CancellableSemaphore BackupSemaphore { get; } = new (1, 1);
|
||||
|
||||
private readonly RingBuffer<string> outputBuffer = new (100);
|
||||
private event EventHandler<string>? OutputEvent;
|
||||
@ -60,6 +61,7 @@ public sealed class InstanceProcess : IDisposable {
|
||||
|
||||
public void Dispose() {
|
||||
process.Dispose();
|
||||
BackupSemaphore.Dispose();
|
||||
OutputEvent = null;
|
||||
Ended = null;
|
||||
}
|
||||
|
@ -16,14 +16,28 @@ sealed class BackupManager : IDisposable {
|
||||
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() {
|
||||
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 readonly BackupManager manager;
|
||||
private readonly string loggerName;
|
||||
@ -75,7 +89,7 @@ sealed class BackupManager : IDisposable {
|
||||
try {
|
||||
await dispatcher.EnableAutomaticSaving();
|
||||
} catch (OperationCanceledException) {
|
||||
// Ignore.
|
||||
// ignore
|
||||
} catch (Exception e) {
|
||||
resultBuilder.Warnings |= BackupCreationWarnings.CouldNotRestoreAutomaticSaving;
|
||||
logger.Error(e, "Caught exception while enabling automatic saving after creating an instance backup.");
|
||||
|
@ -1,7 +1,5 @@
|
||||
using Phantom.Agent.Minecraft.Instance;
|
||||
using Phantom.Agent.Minecraft.Server;
|
||||
using Phantom.Agent.Services.Instances;
|
||||
using Phantom.Agent.Services.Instances.Procedures;
|
||||
using Phantom.Common.Data.Backups;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Utils.Runtime;
|
||||
@ -14,22 +12,21 @@ sealed class BackupScheduler : CancellableBackgroundTask {
|
||||
private static readonly TimeSpan BackupInterval = TimeSpan.FromMinutes(30);
|
||||
private static readonly TimeSpan BackupFailureRetryDelay = TimeSpan.FromMinutes(5);
|
||||
|
||||
private readonly string loggerName;
|
||||
private readonly BackupManager backupManager;
|
||||
private readonly InstanceProcess process;
|
||||
private readonly IInstanceContext context;
|
||||
private readonly SemaphoreSlim backupSemaphore = new (1, 1);
|
||||
private readonly int serverPort;
|
||||
private readonly ServerStatusProtocol serverStatusProtocol;
|
||||
private readonly ManualResetEventSlim serverOutputWhileWaitingForOnlinePlayers = new ();
|
||||
|
||||
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.process = process;
|
||||
this.context = context;
|
||||
this.serverPort = serverPort;
|
||||
this.serverStatusProtocol = new ServerStatusProtocol(context.ShortName);
|
||||
this.serverStatusProtocol = new ServerStatusProtocol(loggerName);
|
||||
Start();
|
||||
}
|
||||
|
||||
@ -54,17 +51,7 @@ sealed class BackupScheduler : CancellableBackgroundTask {
|
||||
}
|
||||
|
||||
private async Task<BackupCreationResult> CreateBackup() {
|
||||
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();
|
||||
}
|
||||
return await backupManager.CreateBackup(loggerName, process, CancellationToken.None);
|
||||
}
|
||||
|
||||
private async Task WaitForOnlinePlayers() {
|
||||
@ -109,7 +96,6 @@ sealed class BackupScheduler : CancellableBackgroundTask {
|
||||
}
|
||||
|
||||
protected override void Dispose() {
|
||||
backupSemaphore.Dispose();
|
||||
serverOutputWhileWaitingForOnlinePlayers.Dispose();
|
||||
}
|
||||
}
|
||||
|
@ -63,10 +63,8 @@ sealed class Instance : IAsyncDisposable {
|
||||
|
||||
private void ReportAndSetStatus(IInstanceStatus status) {
|
||||
TryUpdateStatus("Report status of instance " + shortName + " as " + status.GetType().Name, async () => {
|
||||
if (status != currentStatus) {
|
||||
currentStatus = status;
|
||||
await ServerMessaging.Send(new ReportInstanceStatusMessage(Configuration.InstanceGuid, status));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -123,7 +121,6 @@ sealed class Instance : IAsyncDisposable {
|
||||
configurationSemaphore.Release();
|
||||
}
|
||||
|
||||
ReportAndSetStatus(InstanceStatus.Launching);
|
||||
await procedureManager.Enqueue(procedure);
|
||||
return LaunchInstanceResult.LaunchInitiated;
|
||||
}
|
||||
@ -137,7 +134,6 @@ sealed class Instance : IAsyncDisposable {
|
||||
return StopInstanceResult.InstanceAlreadyStopping;
|
||||
}
|
||||
|
||||
ReportAndSetStatus(InstanceStatus.Stopping);
|
||||
await procedureManager.Enqueue(new StopInstanceProcedure(stopStrategy));
|
||||
return StopInstanceResult.StopInitiated;
|
||||
}
|
||||
|
@ -32,6 +32,10 @@ sealed class InstanceProcedureManager : IAsyncDisposable {
|
||||
return (await currentProcedure.Get(cancellationToken))?.Procedure;
|
||||
}
|
||||
|
||||
public async Task CancelCurrentProcedure() {
|
||||
(await currentProcedure.Get(shutdownCancellationTokenSource.Token))?.CancellationTokenSource.Cancel();
|
||||
}
|
||||
|
||||
private async Task Run() {
|
||||
try {
|
||||
var shutdownCancellationToken = shutdownCancellationTokenSource.Token;
|
||||
@ -73,7 +77,7 @@ sealed class InstanceProcedureManager : IAsyncDisposable {
|
||||
public async ValueTask DisposeAsync() {
|
||||
shutdownCancellationTokenSource.Cancel();
|
||||
|
||||
(await currentProcedure.Get(CancellationToken.None))?.CancellationTokenSource.Cancel();
|
||||
await CancelCurrentProcedure();
|
||||
await procedureQueueFinished.WaitHandle.WaitOneAsync();
|
||||
|
||||
currentProcedure.Dispose();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -33,7 +33,6 @@ sealed record StopInstanceProcedure(MinecraftStopStrategy StopStrategy) : IInsta
|
||||
try {
|
||||
// Too late to cancel the stop procedure now.
|
||||
if (!process.HasEnded) {
|
||||
context.Logger.Information("Session stopping now.");
|
||||
await DoStop(context, process);
|
||||
}
|
||||
} finally {
|
||||
@ -67,6 +66,14 @@ sealed record StopInstanceProcedure(MinecraftStopStrategy StopStrategy) : IInsta
|
||||
}
|
||||
|
||||
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...");
|
||||
await TrySendStopCommand(context, process);
|
||||
|
||||
|
@ -29,7 +29,7 @@ sealed class InstanceRunningState : IInstanceState, IDisposable {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
28
Utils/Phantom.Utils.Runtime/CancellableSemaphore.cs
Normal file
28
Utils/Phantom.Utils.Runtime/CancellableSemaphore.cs
Normal 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();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user