Skip to content

Commit 05c6b08

Browse files
fix(audience): gate FlushAsync on _sendInFlight
Two concurrent FlushAsync callers would both call ReadBatch with the same paths and double-POST. Reuse the timer-tick gate so at most one SendBatchAsync runs at a time; other callers poll cheaply until the gate clears. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 29cd319 commit 05c6b08

1 file changed

Lines changed: 18 additions & 2 deletions

File tree

src/Packages/Audience/Runtime/ImmutableAudience.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -445,9 +445,25 @@ public static async Task FlushAsync()
445445

446446
queue.FlushSync();
447447

448-
while (!transport.IsInBackoffWindow &&
449-
await transport.SendBatchAsync().ConfigureAwait(false))
448+
// Serialise SendBatchAsync via _sendInFlight. Without the gate,
449+
// two concurrent FlushAsync callers both call ReadBatch with the
450+
// same paths and double-POST. Poll cheaply while another caller
451+
// (timer SendBatch or a racing FlushAsync) holds the gate.
452+
while (Interlocked.CompareExchange(ref _sendInFlight, 1, 0) != 0)
450453
{
454+
await Task.Yield();
455+
}
456+
457+
try
458+
{
459+
while (!transport.IsInBackoffWindow &&
460+
await transport.SendBatchAsync().ConfigureAwait(false))
461+
{
462+
}
463+
}
464+
finally
465+
{
466+
Interlocked.Exchange(ref _sendInFlight, 0);
451467
}
452468
}
453469

0 commit comments

Comments
 (0)