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

Compare commits

..

No commits in common. "25d0e2b0d16a4c08cd16eb87cebdd85d8af49801" and "21c90cb7c2d18cd0d751ef6a01129de0dfe5b485" have entirely different histories.

8 changed files with 53 additions and 154 deletions

View File

@ -18,5 +18,4 @@ static class MinecraftServerProperties {
public static readonly MinecraftServerProperty<ushort> ServerPort = new UnsignedShort("server-port"); public static readonly MinecraftServerProperty<ushort> ServerPort = new UnsignedShort("server-port");
public static readonly MinecraftServerProperty<ushort> RconPort = new UnsignedShort("rcon.port"); public static readonly MinecraftServerProperty<ushort> RconPort = new UnsignedShort("rcon.port");
public static readonly MinecraftServerProperty<bool> EnableRcon = new Boolean("enable-rcon"); public static readonly MinecraftServerProperty<bool> EnableRcon = new Boolean("enable-rcon");
public static readonly MinecraftServerProperty<bool> SyncChunkWrites = new Boolean("sync-chunk-writes");
} }

View File

@ -5,13 +5,11 @@ namespace Phantom.Agent.Minecraft.Properties;
public sealed record ServerProperties( public sealed record ServerProperties(
ushort ServerPort, ushort ServerPort,
ushort RconPort, ushort RconPort,
bool EnableRcon = true, bool EnableRcon = true
bool SyncChunkWrites = false
) { ) {
internal void SetTo(JavaPropertiesFileEditor properties) { internal void SetTo(JavaPropertiesFileEditor properties) {
MinecraftServerProperties.ServerPort.Set(properties, ServerPort); MinecraftServerProperties.ServerPort.Set(properties, ServerPort);
MinecraftServerProperties.RconPort.Set(properties, RconPort); MinecraftServerProperties.RconPort.Set(properties, RconPort);
MinecraftServerProperties.EnableRcon.Set(properties, EnableRcon); MinecraftServerProperties.EnableRcon.Set(properties, EnableRcon);
MinecraftServerProperties.SyncChunkWrites.Set(properties, SyncChunkWrites);
} }
} }

View File

@ -19,7 +19,7 @@ public sealed class ServerStatusProtocol {
try { try {
return await GetOnlinePlayerCountOrThrow(serverPort, cancellationToken); return await GetOnlinePlayerCountOrThrow(serverPort, cancellationToken);
} catch (Exception e) { } catch (Exception e) {
logger.Error(e, "Caught exception checking online player count."); logger.Error(e, "Caught exception while checking if players are online.");
return null; return null;
} }
} }

View File

@ -1,8 +1,10 @@
using Phantom.Agent.Services.Instances; using Phantom.Agent.Minecraft.Instance;
using Phantom.Agent.Services.Instances.State; using Phantom.Agent.Minecraft.Server;
using Phantom.Agent.Services.Instances;
using Phantom.Common.Data.Backups; using Phantom.Common.Data.Backups;
using Phantom.Utils.Logging; using Phantom.Utils.Logging;
using Phantom.Utils.Tasks; using Phantom.Utils.Tasks;
using Phantom.Utils.Threading;
namespace Phantom.Agent.Services.Backups; namespace Phantom.Agent.Services.Backups;
@ -14,16 +16,20 @@ sealed class BackupScheduler : CancellableBackgroundTask {
private readonly BackupManager backupManager; private readonly BackupManager backupManager;
private readonly InstanceContext context; private readonly InstanceContext context;
private readonly InstanceProcess process;
private readonly SemaphoreSlim backupSemaphore = new (1, 1); private readonly SemaphoreSlim backupSemaphore = new (1, 1);
private readonly int serverPort;
private readonly ServerStatusProtocol serverStatusProtocol;
private readonly ManualResetEventSlim serverOutputWhileWaitingForOnlinePlayers = new (); private readonly ManualResetEventSlim serverOutputWhileWaitingForOnlinePlayers = new ();
private readonly InstancePlayerCountTracker playerCountTracker;
public event EventHandler<BackupCreationResult>? BackupCompleted; public event EventHandler<BackupCreationResult>? BackupCompleted;
public BackupScheduler(InstanceContext context, InstancePlayerCountTracker playerCountTracker) : base(PhantomLogger.Create<BackupScheduler>(context.ShortName)) { public BackupScheduler(InstanceContext context, InstanceProcess process, int serverPort) : base(PhantomLogger.Create<BackupScheduler>(context.ShortName)) {
this.backupManager = context.Services.BackupManager; this.backupManager = context.Services.BackupManager;
this.context = context; this.context = context;
this.playerCountTracker = playerCountTracker; this.process = process;
this.serverPort = serverPort;
this.serverStatusProtocol = new ServerStatusProtocol(context.ShortName);
Start(); Start();
} }
@ -63,18 +69,43 @@ sealed class BackupScheduler : CancellableBackgroundTask {
} }
private async Task WaitForOnlinePlayers() { private async Task WaitForOnlinePlayers() {
var task = playerCountTracker.WaitForOnlinePlayers(CancellationToken); bool needsToLogOfflinePlayersMessage = true;
if (!task.IsCompleted) {
Logger.Information("Waiting for someone to join before starting a new backup.");
}
process.AddOutputListener(ServerOutputListener, maxLinesToReadFromHistory: 0);
try { try {
await task; while (!CancellationToken.IsCancellationRequested) {
Logger.Information("Players are online, starting a new backup."); serverOutputWhileWaitingForOnlinePlayers.Reset();
} catch (OperationCanceledException) {
throw; var onlinePlayerCount = await serverStatusProtocol.GetOnlinePlayerCount(serverPort, CancellationToken);
} catch (Exception) { if (onlinePlayerCount == null) {
Logger.Warning("Could not detect whether any players are online, starting a new backup."); Logger.Warning("Could not detect whether any players are online, starting a new backup.");
break;
}
if (onlinePlayerCount > 0) {
Logger.Information("Players are online, starting a new backup.");
break;
}
if (needsToLogOfflinePlayersMessage) {
needsToLogOfflinePlayersMessage = false;
Logger.Information("No players are online, waiting for someone to join before starting a new backup.");
}
await Task.Delay(TimeSpan.FromSeconds(10), CancellationToken);
Logger.Debug("Waiting for server output before checking for online players again...");
await serverOutputWhileWaitingForOnlinePlayers.WaitHandle.WaitOneAsync(CancellationToken);
}
} finally {
process.RemoveOutputListener(ServerOutputListener);
}
}
private void ServerOutputListener(object? sender, string line) {
if (!serverOutputWhileWaitingForOnlinePlayers.IsSet) {
serverOutputWhileWaitingForOnlinePlayers.Set();
Logger.Debug("Detected server output, signalling to check for online players again.");
} }
} }

View File

@ -55,6 +55,7 @@ sealed partial class BackupServerCommandDispatcher : IDisposable {
} }
public async Task SaveAllChunks() { public async Task SaveAllChunks() {
// TODO Try if not flushing and waiting a few seconds before flushing reduces lag.
await process.SendCommand(MinecraftCommand.SaveAll(flush: true), cancellationToken); await process.SendCommand(MinecraftCommand.SaveAll(flush: true), cancellationToken);
await savedTheGame.Task.WaitAsync(TimeSpan.FromMinutes(1), cancellationToken); await savedTheGame.Task.WaitAsync(TimeSpan.FromMinutes(1), cancellationToken);
} }

View File

@ -1,121 +0,0 @@
using Phantom.Agent.Minecraft.Instance;
using Phantom.Agent.Minecraft.Server;
using Phantom.Utils.Logging;
using Phantom.Utils.Tasks;
using Phantom.Utils.Threading;
namespace Phantom.Agent.Services.Instances.State;
sealed class InstancePlayerCountTracker : CancellableBackgroundTask {
private readonly InstanceProcess process;
private readonly ushort serverPort;
private readonly ServerStatusProtocol serverStatusProtocol;
private readonly TaskCompletionSource firstDetection = AsyncTasks.CreateCompletionSource();
private readonly ManualResetEventSlim serverOutputEvent = new ();
private int? onlinePlayerCount;
public int? OnlinePlayerCount {
get {
lock (this) {
return onlinePlayerCount;
}
}
private set {
EventHandler<int?>? onlinePlayerCountChanged;
lock (this) {
if (onlinePlayerCount == value) {
return;
}
onlinePlayerCount = value;
onlinePlayerCountChanged = OnlinePlayerCountChanged;
}
onlinePlayerCountChanged?.Invoke(this, value);
}
}
private event EventHandler<int?>? OnlinePlayerCountChanged;
private bool isDisposed = false;
public InstancePlayerCountTracker(InstanceContext context, InstanceProcess process, ushort serverPort) : base(PhantomLogger.Create<InstancePlayerCountTracker>(context.ShortName)) {
this.process = process;
this.serverPort = serverPort;
this.serverStatusProtocol = new ServerStatusProtocol(context.ShortName);
Start();
}
protected override async Task RunTask() {
// Give the server time to start accepting connections.
await Task.Delay(TimeSpan.FromSeconds(10), CancellationToken);
serverOutputEvent.Set();
process.AddOutputListener(OnOutput, maxLinesToReadFromHistory: 0);
while (!CancellationToken.IsCancellationRequested) {
serverOutputEvent.Reset();
OnlinePlayerCount = await serverStatusProtocol.GetOnlinePlayerCount(serverPort, CancellationToken);
if (!firstDetection.Task.IsCompleted) {
firstDetection.SetResult();
}
await Task.Delay(TimeSpan.FromSeconds(10), CancellationToken);
await serverOutputEvent.WaitHandle.WaitOneAsync(CancellationToken);
await Task.Delay(TimeSpan.FromSeconds(2), CancellationToken);
}
}
public async Task WaitForOnlinePlayers(CancellationToken cancellationToken) {
await firstDetection.Task.WaitAsync(cancellationToken);
var onlinePlayersDetected = AsyncTasks.CreateCompletionSource();
lock (this) {
if (onlinePlayerCount == null) {
throw new InvalidOperationException();
}
else if (onlinePlayerCount > 0) {
return;
}
OnlinePlayerCountChanged += OnOnlinePlayerCountChanged;
void OnOnlinePlayerCountChanged(object? sender, int? newPlayerCount) {
if (newPlayerCount == null) {
onlinePlayersDetected.TrySetException(new InvalidOperationException());
OnlinePlayerCountChanged -= OnOnlinePlayerCountChanged;
}
else if (newPlayerCount > 0) {
onlinePlayersDetected.TrySetResult();
OnlinePlayerCountChanged -= OnOnlinePlayerCountChanged;
}
}
}
await onlinePlayersDetected.Task;
}
private void OnOutput(object? sender, string? line) {
lock (this) {
if (!isDisposed) {
serverOutputEvent.Set();
}
}
}
protected override void Dispose() {
lock (this) {
isDisposed = true;
onlinePlayerCount = null;
}
process.RemoveOutputListener(OnOutput);
serverOutputEvent.Dispose();
}
}

View File

@ -19,7 +19,6 @@ sealed class InstanceRunningState : IDisposable {
private readonly CancellationToken cancellationToken; private readonly CancellationToken cancellationToken;
private readonly InstanceLogSender logSender; private readonly InstanceLogSender logSender;
private readonly InstancePlayerCountTracker playerCountTracker;
private readonly BackupScheduler backupScheduler; private readonly BackupScheduler backupScheduler;
private bool isDisposed; private bool isDisposed;
@ -33,9 +32,8 @@ sealed class InstanceRunningState : IDisposable {
this.cancellationToken = cancellationToken; this.cancellationToken = cancellationToken;
this.logSender = new InstanceLogSender(context.Services.ControllerConnection, context.InstanceGuid, context.ShortName); this.logSender = new InstanceLogSender(context.Services.ControllerConnection, context.InstanceGuid, context.ShortName);
this.playerCountTracker = new InstancePlayerCountTracker(context, process, configuration.ServerPort);
this.backupScheduler = new BackupScheduler(context, playerCountTracker); this.backupScheduler = new BackupScheduler(context, process, configuration.ServerPort);
this.backupScheduler.BackupCompleted += OnScheduledBackupCompleted; this.backupScheduler.BackupCompleted += OnScheduledBackupCompleted;
} }
@ -95,11 +93,6 @@ sealed class InstanceRunningState : IDisposable {
} }
} }
public void OnStopInitiated() {
backupScheduler.Stop();
playerCountTracker.Stop();
}
private bool TryDispose() { private bool TryDispose() {
lock (this) { lock (this) {
if (isDisposed) { if (isDisposed) {
@ -109,8 +102,8 @@ sealed class InstanceRunningState : IDisposable {
isDisposed = true; isDisposed = true;
} }
OnStopInitiated();
logSender.Stop(); logSender.Stop();
backupScheduler.Stop();
Process.Dispose(); Process.Dispose();

View File

@ -25,8 +25,6 @@ static class InstanceStopProcedure {
try { try {
// Too late to cancel the stop procedure now. // Too late to cancel the stop procedure now.
runningState.OnStopInitiated();
if (!process.HasEnded) { if (!process.HasEnded) {
context.Logger.Information("Session stopping now."); context.Logger.Information("Session stopping now.");
await DoStop(context, process); await DoStop(context, process);
@ -87,7 +85,7 @@ static class InstanceStopProcedure {
private static async Task WaitForSessionToEnd(InstanceContext context, InstanceProcess process) { private static async Task WaitForSessionToEnd(InstanceContext context, InstanceProcess process) {
try { try {
await process.WaitForExit(TimeSpan.FromSeconds(55)); await process.WaitForExit(TimeSpan.FromSeconds(55));
} catch (TimeoutException) { } catch (OperationCanceledException) {
try { try {
context.Logger.Warning("Waiting timed out, killing session..."); context.Logger.Warning("Waiting timed out, killing session...");
process.Kill(); process.Kill();