fix(a2a-client): stopped registry starts no watch loops
This commit is contained in:
@@ -55,6 +55,9 @@ public sealed class A2AWatchRegistry : IAsyncDisposable
|
||||
private readonly ConcurrentDictionary<string, byte> _flushRequested = new(StringComparer.Ordinal);
|
||||
private volatile IA2AResultDelivery _delivery;
|
||||
|
||||
/// <summary>Set by <see cref="StopAsync"/>: later starts and re-arms still persist their record but run no loop.</summary>
|
||||
private volatile bool _stopped;
|
||||
|
||||
public A2AWatchRegistry(TimeProvider time, HttpMessageHandler handler, ILogger? logger = null)
|
||||
{
|
||||
Time = time;
|
||||
@@ -97,6 +100,8 @@ public sealed class A2AWatchRegistry : IAsyncDisposable
|
||||
var watch = new Watch(new WatchRecord(watchId, target, agent.Entry.Name, task.Id, task.ContextId, null, Time.GetUtcNow(), source), workspace);
|
||||
using (await LockAsync(workspace, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
// A watch that is finishing (left the map, delivering) isn't seen here, so a start racing it may watch the
|
||||
// task again: a rare duplicate result, within the registry's at-least-once delivery.
|
||||
if (_watches.Values.FirstOrDefault(w => w.Record.Target == target && w.Record.TaskId == task.Id) is { } existing)
|
||||
{
|
||||
watch.DisposeStop();
|
||||
@@ -138,6 +143,13 @@ public sealed class A2AWatchRegistry : IAsyncDisposable
|
||||
watch.DisposeStop();
|
||||
throw;
|
||||
}
|
||||
if (_stopped)
|
||||
{
|
||||
// Shutting down: the record stays in pending.json for the next restore, no loop outlives the registry.
|
||||
_watches.TryRemove(new(watchId, watch));
|
||||
watch.DisposeStop();
|
||||
return watch.Record;
|
||||
}
|
||||
watch.Loop = Task.Run(() => RunAsync(watch, settings));
|
||||
return watch.Record;
|
||||
}
|
||||
@@ -174,6 +186,8 @@ public sealed class A2AWatchRegistry : IAsyncDisposable
|
||||
var watch = new Watch(record, workspace);
|
||||
if (!_watches.TryAdd(record.WatchId, watch)) { watch.DisposeStop(); continue; }
|
||||
if (!ReferenceEquals(record, stored)) PendingFile.Upsert(workspace, record);
|
||||
// Checked after the watch is in the map: a StopAsync that set the flag later sees (and stops) it.
|
||||
if (_stopped) { _watches.TryRemove(new(record.WatchId, watch)); watch.DisposeStop(); return count; }
|
||||
watch.Loop = Task.Run(() => RunAsync(watch, settings));
|
||||
count++;
|
||||
}
|
||||
@@ -255,6 +269,13 @@ public sealed class A2AWatchRegistry : IAsyncDisposable
|
||||
var watch = new Watch(record with { Undelivered = null }, workspace);
|
||||
if (!_watches.TryAdd(record.WatchId, watch)) { watch.DisposeStop(); return; }
|
||||
PendingFile.Upsert(workspace, watch.Record);
|
||||
if (_stopped)
|
||||
{
|
||||
// Kept in pending.json for the next restore; no loop after StopAsync.
|
||||
_watches.TryRemove(new(record.WatchId, watch));
|
||||
watch.DisposeStop();
|
||||
return;
|
||||
}
|
||||
watch.Loop = Task.Run(() => RunAsync(watch, settings));
|
||||
}
|
||||
}
|
||||
@@ -513,6 +534,7 @@ public sealed class A2AWatchRegistry : IAsyncDisposable
|
||||
/// </summary>
|
||||
public async Task StopAsync()
|
||||
{
|
||||
_stopped = true;
|
||||
var watches = _watches.ToArray();
|
||||
foreach (var pair in watches) _watches.TryRemove(pair);
|
||||
await Task.WhenAll(watches.Select(p => p.Value.StopAsync())).ConfigureAwait(false);
|
||||
|
||||
Reference in New Issue
Block a user