feat: Release v0.2.2 with shutdown improvements and connection handling fixes

This commit is contained in:
HueByte
2026-02-19 13:01:51 +01:00
parent 4c192717de
commit 552fe6afa3
6 changed files with 72 additions and 35 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>0.2.1</Version>
<Version>0.2.2</Version>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);CS1591</NoWarn>
</PropertyGroup>
+4
View File
@@ -35,6 +35,10 @@ while (true)
{
var builder = WebApplication.CreateBuilder(args);
// ── Host options ────────────────────────────────────────────────────
builder.Services.Configure<HostOptions>(options =>
options.ShutdownTimeout = TimeSpan.FromSeconds(5));
// ── Serilog ──────────────────────────────────────────────────────────
builder.Host.UseSerilog((context, config) =>
config.ReadFrom.Configuration(context.Configuration));
@@ -43,46 +43,57 @@ public sealed class ServerDirectoryService(
// Outer loop: rebuilds the connection if automatic reconnect permanently fails
while (!stoppingToken.IsCancellationRequested)
{
await using var connection = BuildConnection();
var connection = BuildConnection();
_connection = connection;
var connectionPermanentlyClosed = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
connection.Reconnected += async _ =>
try
{
logger.LogInformation("Reconnected to directory — re-registering server");
_lastReportedUserCount = -1;
var connectionPermanentlyClosed = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
connection.Reconnected += async _ =>
{
logger.LogInformation("Reconnected to directory — re-registering server");
_lastReportedUserCount = -1;
await RegisterAsync(serverName, description, host);
};
connection.Closed += ex =>
{
if (ex is not null)
logger.LogWarning(ex, "Directory connection permanently closed — will rebuild");
else
logger.LogWarning("Directory connection permanently closed — will rebuild");
connectionPermanentlyClosed.TrySetResult();
return Task.CompletedTask;
};
// Connect with retry
if (!await ConnectWithRetryAsync(connection, stoppingToken))
return;
logger.LogInformation("Successfully connected to EchoHubSpace API at {Url}", DirectoryHubUrl);
await RegisterAsync(serverName, description, host);
};
connection.Closed += ex =>
// Poll user count until the connection is permanently closed or cancellation
await PollUserCountAsync(connection, connectionPermanentlyClosed.Task, stoppingToken);
if (stoppingToken.IsCancellationRequested)
return;
// Connection was permanently closed — wait briefly then rebuild
logger.LogInformation("Rebuilding directory connection...");
await Task.Delay(ReconnectBaseDelay, stoppingToken);
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
if (ex is not null)
logger.LogWarning(ex, "Directory connection permanently closed — will rebuild");
else
logger.LogWarning("Directory connection permanently closed — will rebuild");
connectionPermanentlyClosed.TrySetResult();
return Task.CompletedTask;
};
// Connect with retry
if (!await ConnectWithRetryAsync(connection, stoppingToken))
return;
logger.LogInformation("Successfully connected to EchoHubSpace API at {Url}", DirectoryHubUrl);
await RegisterAsync(serverName, description, host);
// Poll user count until the connection is permanently closed or cancellation
await PollUserCountAsync(connection, connectionPermanentlyClosed.Task, stoppingToken);
if (stoppingToken.IsCancellationRequested)
return;
// Connection was permanently closed — wait briefly then rebuild
_connection = null;
logger.LogInformation("Rebuilding directory connection...");
await Task.Delay(ReconnectBaseDelay, stoppingToken);
}
finally
{
_connection = null;
await DisposeConnectionAsync(connection);
}
}
}
@@ -175,9 +186,21 @@ public sealed class ServerDirectoryService(
}
}
private static async Task DisposeConnectionAsync(HubConnection connection)
{
try
{
await connection.DisposeAsync()
.AsTask().WaitAsync(TimeSpan.FromSeconds(3));
}
catch
{
// Don't let a slow dispose block shutdown
}
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
// Cancel ExecuteAsync first — it disposes the connection via await using
await base.StopAsync(cancellationToken);
_connection = null;
}