Skip to content

Commit d4e8af9

Browse files
committed
chore: trim .server-changes file to release-note length
1 parent f28f53f commit d4e8af9

1 file changed

Lines changed: 1 addition & 44 deletions

File tree

.server-changes/batch-stream-phase2-retry-idempotency.md

Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,4 @@ area: webapp
33
type: fix
44
---
55

6-
Tighten Phase 2 batch-stream idempotency across all three branches of
7-
`StreamBatchItemsService.call` so a successful original request, or a
8-
retry of one, returns `sealed:true` instead of the customer-visible
9-
422/`sealed:false` responses that surfaced as `BatchTriggerError` in
10-
production.
11-
12-
Three related modes are now handled uniformly:
13-
14-
1. **TRI-9944 (lost-response retry)**: SDK retries Phase 2 after a
15-
network blip ate the response. The original sealed the batch; the
16-
retry hits a non-`PENDING` status and the pre-loop check threw 422.
17-
18-
2. **Sealed-with-callback PENDING**: the V2 `batchCompletionCallback`
19-
resets status from `PROCESSING` back to `PENDING` when every run
20-
was created cleanly, without touching `sealed`. The seal-failed
21-
race branch threw "unexpected state" on this perfectly legitimate
22-
state.
23-
24-
3. **Cleanup-race (customer 4-item batchTriggerAndWait reports)**:
25-
BatchQueue rushes through every item before the loop finishes its
26-
seal step, the callback fires (setting `processingCompletedAt`),
27-
`cleanup()` deletes the Redis metadata, then the service's
28-
`getBatchEnqueuedCount` returns 0 ≠ `runCount`. The count-mismatch
29-
branch returned `sealed:false` because `sealed=false + PENDING`
30-
wasn't distinguishable from "client should stream more items". The
31-
SDK then retried the stream against the cleaned-up batch, the
32-
engine threw `Batch not found or not initialized`, retries
33-
exhausted, customer saw `BatchTriggerError` despite every child run
34-
completing successfully.
35-
36-
The pre-loop check, the count-mismatch handler, and the seal-failed
37-
handler now all call a single `isIdempotentRetrySuccess(status, sealed,
38-
processingCompletedAt)` helper. `processingCompletedAt` is the
39-
discriminator that fixes mode (3) — it's set exclusively by the V2
40-
completion callback, so `(status=PENDING) && (sealed || processingCompletedAt
41-
!= null)` cleanly separates "callback fired, work is done" from "client
42-
should stream more items".
43-
44-
`ABORTED` (zero TaskRun records — every run-creation attempt failed
45-
*and* the pre-failed-TaskRun fallback also failed) is explicitly
46-
excluded from the idempotent-success path in all three branches: the
47-
customer has nothing to monitor at the run level, so the trigger call
48-
must throw to give their `batchTrigger()` retry the chance to create a
49-
fresh batch.
6+
Stop spurious `BatchTriggerError` failures when a fast-completing `batchTrigger`/`batchTriggerAndWait` raced the stream finalisation - the API now treats these as successes instead of 422s.

0 commit comments

Comments
 (0)