a2aproject / a2aproject/a2a-dotnet
A2AServer streaming persistence failures are converted to normal EOF
- 主要言語
- C#
- スター
- 262
- フォーク
- 64
- 平均マージ
- 5日 2時間
- マージ済み PR(30日)
- 31
説明
## Summary
`A2AServer.SendStreamingMessageAsync` converts failures from `ApplyEventAsync` into normal end-of-stream. A task-store read or write failure can therefore appear to the caller as successful stream completion.
## Current behavior
The streaming loop catches every exception raised while applying an event and exits the iterator:
```csharp
try
{
await ApplyEventAsync(response, context!, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
A2ADiagnostics.ErrorCount.Add(1);
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
RecordException(activity, ex);
yield break;
}
```
`ApplyEventAsync` calls both `ITaskStore.GetTaskAsync` and `ITaskStore.SaveTaskAsync`. If either operation fails, consumers only observe EOF. This is indistinguishable from normal stream completion unless they perform a separate authoritative read, which may itself fail.
The behavior also applies when a persistence failure happens after one or more responses have already been emitted: the next iterator move returns `false` instead of throwing the persistence exception.
## Expected behavior
A failure to read or persist authoritative task state must not become normal EOF. The SDK should either:
1. rethrow the event-application exception from the async iterator; or
2. expose a host-configurable failure policy that allows authoritative persistence failures to propagate.
Caller-requested cancellation should continue to propagate according to the caller cancellation token rather than being classified only by exception type.
## Reproduction
1. Configure an `ITaskStore` whose `SaveTaskAsync` throws after receiving a streaming task event.
2. Call `A2AServer.SendStreamingMessageAsync` and enumerate the response stream.
3. Observe that enumeration completes normally instead of throwing the store exception.
4. Repeat with a failure after an earlier response has been emitted; the subsequent `MoveNextAsync` returns `false`.
The same result can be reproduced with a failing `GetTaskAsync` inside `ApplyEventAsync`.
## Impact
- Clients can interpret a failed authoritative write as successful streaming completion.
- The persisted task may remain stale or non-terminal.
- Hosts must add SDK-specific exception-capture wrappers to preserve failure semantics.
## Suggested validation
Add tests covering:
- first-event `GetTaskAsync` and `SaveTaskAsync` failure;
- persistence failure after at least one emitted response;
- internal `OperationCanceledException` while the caller token is not canceled;
- caller-requested cancellation with a canceled caller token;
- unchanged successful and empty-stream behavior.
## Downstream context
WorkIQ currently carries a temporary wrapper around the SDK iterator to recapture these failures for its legacy SDK-managed persistence path. Its newer protocol-neutral task-management path does not use the SDK complete-snapshot persistence loop, so this is being tracked as an SDK defect rather than a WorkIQ feature requirement.
コントリビューションガイド
調査の方向性
The issue is in A2AServer.SendStreamingMessageAsync, specifically the try-catch block that yields break on any exception from ApplyEventAsync. ApplyEventAsync calls ITaskStore.GetTaskAsync and ITaskStore.SaveTaskAsync. Start by examining the streaming loop in the A2AServer class. Look for tests related to streaming persistence failures to understand the expected behavior. The fix involves modifying the exception handling to rethrow or propagate persistence failures instead of converting them to EOF.
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- csharp
- 領域
- api, backend
- issue の種類
- バグ
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 活発さ
- 活発
- 明瞭さ
- 明確に書かれている
- 初心者へのやさしさ
- 45/100