fix server does not shut down

This commit is contained in:
Vinzenz Schroeter 2024-11-12 19:14:02 +01:00
parent 53cbdd8440
commit 8b44168b66
9 changed files with 59 additions and 47 deletions

View file

@ -7,13 +7,11 @@ internal sealed class GameTickWorker(
IEnumerable<ITickStep> steps, IEnumerable<ITickStep> steps,
IHostApplicationLifetime lifetime, IHostApplicationLifetime lifetime,
ILogger<GameTickWorker> logger ILogger<GameTickWorker> logger
) : IHostedLifecycleService, IDisposable ) : BackgroundService, IDisposable
{ {
private readonly CancellationTokenSource _cancellation = new();
private readonly TaskCompletionSource _shutdownCompletion = new();
private readonly List<ITickStep> _steps = steps.ToList(); private readonly List<ITickStep> _steps = steps.ToList();
public async Task StartedAsync(CancellationToken cancellationToken) protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{ {
await Task.Yield(); await Task.Yield();
@ -23,7 +21,7 @@ internal sealed class GameTickWorker(
try try
{ {
while (!_cancellation.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
var delta = sw.Elapsed; var delta = sw.Elapsed;
sw.Restart(); sw.Restart();
@ -37,19 +35,5 @@ internal sealed class GameTickWorker(
logger.LogError(ex, "game tick service crashed"); logger.LogError(ex, "game tick service crashed");
lifetime.StopApplication(); lifetime.StopApplication();
} }
_shutdownCompletion.SetResult();
} }
public Task StoppingAsync(CancellationToken cancellationToken) => _cancellation.CancelAsync();
public Task StopAsync(CancellationToken cancellationToken) => _shutdownCompletion.Task;
public void Dispose() => _cancellation.Dispose();
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public Task StartingAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public Task StoppedAsync(CancellationToken cancellationToken) => Task.CompletedTask;
} }

View file

@ -1,4 +1,3 @@
using ServicePoint;
using TanksServer.GameLogic; using TanksServer.GameLogic;
using TanksServer.Interactivity; using TanksServer.Interactivity;

View file

@ -1,5 +1,3 @@
using ServicePoint;
namespace TanksServer.Graphics; namespace TanksServer.Graphics;
internal interface IFrameConsumer internal interface IFrameConsumer

View file

@ -1,5 +1,4 @@
using System.Net.WebSockets; using System.Net.WebSockets;
using ServicePoint;
using TanksServer.Graphics; using TanksServer.Graphics;
namespace TanksServer.Interactivity; namespace TanksServer.Interactivity;

View file

@ -1,6 +1,5 @@
using System.Buffers; using System.Buffers;
using System.Net.WebSockets; using System.Net.WebSockets;
using ServicePoint;
using TanksServer.Graphics; using TanksServer.Graphics;
namespace TanksServer.Interactivity; namespace TanksServer.Interactivity;

View file

@ -0,0 +1,42 @@
using Microsoft.Extensions.Hosting;
internal class LoggingLifecycleService(ILogger logger) : IHostedLifecycleService
{
private protected readonly ILogger Logger = logger;
public virtual Task StartAsync(CancellationToken cancellationToken)
{
Logger.LogDebug("StartAsync");
return Task.CompletedTask;
}
public virtual Task StartedAsync(CancellationToken cancellationToken)
{
Logger.LogDebug("StartedAsync");
return Task.CompletedTask;
}
public virtual Task StartingAsync(CancellationToken cancellationToken)
{
Logger.LogDebug("StartingAsync");
return Task.CompletedTask;
}
public virtual Task StopAsync(CancellationToken cancellationToken)
{
Logger.LogDebug("StopAsync");
return Task.CompletedTask;
}
public virtual Task StoppedAsync(CancellationToken cancellationToken)
{
Logger.LogDebug("StoppedAsync");
return Task.CompletedTask;
}
public virtual Task StoppingAsync(CancellationToken cancellationToken)
{
Logger.LogDebug("StoppingAsync");
return Task.CompletedTask;
}
}

View file

@ -1,7 +1,6 @@
using System.Diagnostics; using System.Diagnostics;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using ServicePoint;
using TanksServer.GameLogic; using TanksServer.GameLogic;
using TanksServer.Graphics; using TanksServer.Graphics;
@ -42,13 +41,13 @@ internal sealed class SendToServicePointDisplay : IFrameConsumer, IDisposable
var localIp = GetLocalIPv4(displayConfig.Value).Split('.'); var localIp = GetLocalIPv4(displayConfig.Value).Split('.');
Debug.Assert(localIp.Length == 4); Debug.Assert(localIp.Length == 4);
_scoresBuffer = new CharGrid(12, 20); _scoresBuffer = new CharGrid(ScoresWidth, ScoresHeight);
_scoresBuffer.SetRow(00, "== TANKS! =="); _scoresBuffer.SetRow(00, "== TANKS! ==");
_scoresBuffer.SetRow(01, "-- scores --"); _scoresBuffer.SetRow(01, "-- scores --");
_scoresBuffer.SetRow(17, "-- join --"); _scoresBuffer.SetRow(17, "-- join --");
_scoresBuffer.SetRow(18, string.Join('.', localIp[..2])); _scoresBuffer.SetRow(18, $"{localIp[0]}.{localIp[1]}".PadRight(ScoresWidth));
_scoresBuffer.SetRow(19, string.Join('.', localIp[2..])); _scoresBuffer.SetRow(19, $"{localIp[2]}.{localIp[3]}".PadRight(ScoresWidth));
} }
public async Task OnFrameDoneAsync(GamePixelGrid gamePixelGrid, Bitmap observerPixels) public async Task OnFrameDoneAsync(GamePixelGrid gamePixelGrid, Bitmap observerPixels)
@ -101,7 +100,7 @@ internal sealed class SendToServicePointDisplay : IFrameConsumer, IDisposable
for (; row < 16; row++) for (; row < 16; row++)
_scoresBuffer.SetRow(row, new string(' ', ScoresWidth)); _scoresBuffer.SetRow(row, new string(' ', ScoresWidth));
_scoresBuffer.SetRow(16, _mapService.Current.Name[..(Math.Min(ScoresWidth, _mapService.Current.Name.Length) - 1)]); _scoresBuffer.SetRow(16, _mapService.Current.Name[..(Math.Min(ScoresWidth, _mapService.Current.Name.Length) - 1)].PadRight(ScoresWidth));
} }
private static string GetLocalIPv4(DisplayConfiguration configuration) private static string GetLocalIPv4(DisplayConfiguration configuration)

View file

@ -1,23 +1,23 @@
using System.Diagnostics; using System.Diagnostics;
using Microsoft.Extensions.Hosting;
namespace TanksServer.Interactivity; namespace TanksServer.Interactivity;
internal abstract class WebsocketServer<T>( internal abstract class WebsocketServer<T>(
ILogger logger ILogger logger
) : IHostedLifecycleService ) : LoggingLifecycleService(logger)
where T : WebsocketServerConnection where T : WebsocketServerConnection
{ {
private bool _closing; private bool _closing;
private readonly ConcurrentDictionary<T, byte> _connections = []; private readonly ConcurrentDictionary<T, byte> _connections = [];
public async Task StoppingAsync(CancellationToken cancellationToken) public async override Task StoppingAsync(CancellationToken cancellationToken)
{ {
await base.StoppingAsync(cancellationToken);
_closing = true; _closing = true;
logger.LogInformation("closing connections"); Logger.LogInformation("closing connections");
await _connections.Keys.Select(c => c.CloseAsync()) await _connections.Keys.Select(c => c.CloseAsync())
.WhenAll(); .WhenAll();
logger.LogInformation("closed connections"); Logger.LogInformation("closed connections");
} }
protected IEnumerable<T> Connections => _connections.Keys; protected IEnumerable<T> Connections => _connections.Keys;
@ -26,7 +26,7 @@ internal abstract class WebsocketServer<T>(
{ {
if (_closing) if (_closing)
{ {
logger.LogWarning("refusing connection because server is shutting down"); Logger.LogWarning("refusing connection because server is shutting down");
await connection.CloseAsync(); await connection.CloseAsync();
return; return;
} }
@ -39,14 +39,4 @@ internal abstract class WebsocketServer<T>(
_ = _connections.TryRemove(connection, out _); _ = _connections.TryRemove(connection, out _);
connection.Dispose(); connection.Dispose();
} }
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public Task StartedAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public Task StartingAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public Task StoppedAsync(CancellationToken cancellationToken) => Task.CompletedTask;
} }

View file

@ -67,8 +67,8 @@ public static class Program
builder.Services.AddSingleton<UpdatesPerSecondCounter>(); builder.Services.AddSingleton<UpdatesPerSecondCounter>();
builder.Services.AddHostedService<GameTickWorker>(); builder.Services.AddHostedService<GameTickWorker>();
builder.Services.AddHostedService(sp => sp.GetRequiredService<ControlsServer>()); builder.Services.AddHostedService(FromServices<ControlsServer>);
builder.Services.AddHostedService(sp => sp.GetRequiredService<ClientScreenServer>()); builder.Services.AddHostedService(FromServices<ClientScreenServer>);
builder.Services.AddSingleton<ITickStep, ChangeToRequestedMap>(sp => builder.Services.AddSingleton<ITickStep, ChangeToRequestedMap>(sp =>
sp.GetRequiredService<ChangeToRequestedMap>()); sp.GetRequiredService<ChangeToRequestedMap>());
@ -113,4 +113,6 @@ public static class Program
return app; return app;
} }
private static T FromServices<T>(IServiceProvider sp) where T : notnull => sp.GetRequiredService<T>();
} }