fix(communication): cancel-only CTS replacement, deferred channel disposal, single-endpoint debug stream (arch-review 2026-08-19 P3-1 + P3-2 + P3-3)
This commit is contained in:
@@ -120,7 +120,7 @@ public class SiteStreamGrpcClientFactory : IAsyncDisposable, IDisposable
|
||||
foreach (var key in _clients.Keys.Where(k => k.Site == siteIdentifier).ToList())
|
||||
{
|
||||
if (_clients.TryRemove(key, out var client))
|
||||
await client.DisposeAsync();
|
||||
await DisposeClientAsync(client, key);
|
||||
}
|
||||
|
||||
// Drop the cached preshared key too, so a site removed and re-added under the same
|
||||
@@ -129,16 +129,39 @@ public class SiteStreamGrpcClientFactory : IAsyncDisposable, IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously disposes all cached clients and clears the cache.
|
||||
/// Asynchronously disposes all cached clients and clears the cache. Each client's disposal
|
||||
/// is fault-isolated: one throwing client must not abort the loop and strand every later
|
||||
/// client's channel undisposed (arch-review 2026-08-19 P3-1).
|
||||
/// </summary>
|
||||
/// <returns>A value task that completes when all clients have been disposed.</returns>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
foreach (var client in _clients.Values)
|
||||
foreach (var entry in _clients)
|
||||
{
|
||||
await DisposeClientAsync(entry.Value, entry.Key);
|
||||
}
|
||||
_clients.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes one client, logging and swallowing any failure so the caller's disposal loop
|
||||
/// continues to the remaining clients.
|
||||
/// </summary>
|
||||
/// <param name="client">The client to dispose.</param>
|
||||
/// <param name="key">Cache key of the client, for diagnostics.</param>
|
||||
/// <returns>A task that completes when the client has been disposed (or its failure logged).</returns>
|
||||
private async Task DisposeClientAsync(SiteStreamGrpcClient client, (string Site, string Endpoint) key)
|
||||
{
|
||||
try
|
||||
{
|
||||
await client.DisposeAsync();
|
||||
}
|
||||
_clients.Clear();
|
||||
catch (Exception ex)
|
||||
{
|
||||
_loggerFactory.CreateLogger<SiteStreamGrpcClientFactory>().LogWarning(
|
||||
ex, "Disposing the site stream gRPC client for site {SiteId} endpoint {Endpoint} failed",
|
||||
key.Site, key.Endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -147,12 +170,22 @@ public class SiteStreamGrpcClientFactory : IAsyncDisposable, IDisposable
|
||||
/// a stall/deadlock risk during host shutdown). Each
|
||||
/// <see cref="SiteStreamGrpcClient"/> releases all of its resources
|
||||
/// synchronously, so we dispose them directly with no async path.
|
||||
/// Fault-isolated per client, exactly as <see cref="DisposeAsync"/> is.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var client in _clients.Values)
|
||||
foreach (var entry in _clients)
|
||||
{
|
||||
client.Dispose();
|
||||
try
|
||||
{
|
||||
entry.Value.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_loggerFactory.CreateLogger<SiteStreamGrpcClientFactory>().LogWarning(
|
||||
ex, "Disposing the site stream gRPC client for site {SiteId} endpoint {Endpoint} failed",
|
||||
entry.Key.Site, entry.Key.Endpoint);
|
||||
}
|
||||
}
|
||||
_clients.Clear();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user