fix(client): failover runs under the subscription gate; ConnectAsync refuses when connected
R08 4.5: every subscription operation that awaits I/O and the failover's teardown + reconnect + replay share one async gate, and every read/write of the subscription fields (failover's null-outs included) is under _subscriptionLock. A subscribe in flight when a failover starts is recorded first and then replayed onto the new session instead of landing on the dying session's subscription. The dead session is disposed before the gate is taken so a blocked call fails fast. A DisconnectAsync during a failover is no longer undone by it. ConnectAsync on a connected or reconnecting client throws instead of overwriting (and leaking) the live session. Refs #747 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
@@ -167,15 +167,31 @@ internal sealed class FakeSessionAdapter : ISessionAdapter
|
||||
return Task.FromResult<IReadOnlyList<DataValue>>(HistoryReadAggregateResponse);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optional gate that, when set, holds <see cref="CreateSubscriptionAsync" /> open until completed, so a test can keep
|
||||
/// a subscribe in flight while something else (a failover) happens. Signalled via <see cref="CreateSubscriptionEntered" />.
|
||||
/// </summary>
|
||||
public TaskCompletionSource? CreateSubscriptionGate { get; set; }
|
||||
|
||||
/// <summary>Completed when <see cref="CreateSubscriptionAsync" /> is entered while a gate is set.</summary>
|
||||
public TaskCompletionSource CreateSubscriptionEntered { get; } =
|
||||
new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ISubscriptionAdapter> CreateSubscriptionAsync(int publishingIntervalMs, CancellationToken ct)
|
||||
public async Task<ISubscriptionAdapter> CreateSubscriptionAsync(int publishingIntervalMs, CancellationToken ct)
|
||||
{
|
||||
if (CreateSubscriptionGate is { } gate)
|
||||
{
|
||||
CreateSubscriptionEntered.TrySetResult();
|
||||
await gate.Task;
|
||||
}
|
||||
|
||||
lock (_createdSubscriptions)
|
||||
{
|
||||
var sub = NextSubscription ?? new FakeSubscriptionAdapter();
|
||||
NextSubscription = null;
|
||||
_createdSubscriptions.Add(sub);
|
||||
return Task.FromResult<ISubscriptionAdapter>(sub);
|
||||
return sub;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user