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 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;
|
||||||
}
|
}
|
||||||
|
@ -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.");
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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 {
|
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);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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