mirror of
https://github.com/RedWizardsLab/EchoHub.git
synced 2026-09-04 08:36:11 +02:00
feat: Release v0.2.2 with shutdown improvements and connection handling fixes
This commit is contained in:
@@ -4,6 +4,7 @@ Release history for EchoHub.
|
|||||||
|
|
||||||
## Releases
|
## Releases
|
||||||
|
|
||||||
|
- [v0.2.2](v0.2.2.md) - Shutdown Fix
|
||||||
- [v0.2.1](v0.2.1.md) - Shutdown & CI Fixes
|
- [v0.2.1](v0.2.1.md) - Shutdown & CI Fixes
|
||||||
- [v0.2.0](v0.2.0.md) - IRC Gateway
|
- [v0.2.0](v0.2.0.md) - IRC Gateway
|
||||||
- [v0.1.1](v0.1.1.md) - Directory Connection Self-Healing
|
- [v0.1.1](v0.1.1.md) - Directory Connection Self-Healing
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
- name: Overview
|
- name: Overview
|
||||||
href: index.md
|
href: index.md
|
||||||
|
- name: v0.2.2
|
||||||
|
href: v0.2.2.md
|
||||||
- name: v0.2.1
|
- name: v0.2.1
|
||||||
href: v0.2.1.md
|
href: v0.2.1.md
|
||||||
- name: v0.2.0
|
- name: v0.2.0
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# v0.2.2 - Shutdown Fix
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- Actually fixed server hanging on Ctrl+C — replaced `await using` with explicit dispose bounded to 3 seconds, so a stuck `HubConnection` can no longer block shutdown
|
||||||
|
- Reduced host shutdown timeout from 30s (default) to 5s
|
||||||
|
- Caught `OperationCanceledException` in the directory service reconnect loop so cancellation exits immediately instead of propagating through dispose
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>0.2.1</Version>
|
<Version>0.2.2</Version>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ while (true)
|
|||||||
{
|
{
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
// ── Host options ────────────────────────────────────────────────────
|
||||||
|
builder.Services.Configure<HostOptions>(options =>
|
||||||
|
options.ShutdownTimeout = TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
// ── Serilog ──────────────────────────────────────────────────────────
|
// ── Serilog ──────────────────────────────────────────────────────────
|
||||||
builder.Host.UseSerilog((context, config) =>
|
builder.Host.UseSerilog((context, config) =>
|
||||||
config.ReadFrom.Configuration(context.Configuration));
|
config.ReadFrom.Configuration(context.Configuration));
|
||||||
|
|||||||
@@ -43,46 +43,57 @@ public sealed class ServerDirectoryService(
|
|||||||
// Outer loop: rebuilds the connection if automatic reconnect permanently fails
|
// Outer loop: rebuilds the connection if automatic reconnect permanently fails
|
||||||
while (!stoppingToken.IsCancellationRequested)
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
await using var connection = BuildConnection();
|
var connection = BuildConnection();
|
||||||
_connection = connection;
|
_connection = connection;
|
||||||
|
|
||||||
var connectionPermanentlyClosed = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
try
|
||||||
|
|
||||||
connection.Reconnected += async _ =>
|
|
||||||
{
|
{
|
||||||
logger.LogInformation("Reconnected to directory — re-registering server");
|
var connectionPermanentlyClosed = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
_lastReportedUserCount = -1;
|
|
||||||
|
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);
|
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;
|
return;
|
||||||
|
}
|
||||||
logger.LogInformation("Successfully connected to EchoHubSpace API at {Url}", DirectoryHubUrl);
|
finally
|
||||||
await RegisterAsync(serverName, description, host);
|
{
|
||||||
|
_connection = null;
|
||||||
// Poll user count until the connection is permanently closed or cancellation
|
await DisposeConnectionAsync(connection);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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)
|
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// Cancel ExecuteAsync first — it disposes the connection via await using
|
|
||||||
await base.StopAsync(cancellationToken);
|
await base.StopAsync(cancellationToken);
|
||||||
_connection = null;
|
_connection = null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user