Serialize InFlightScriptStore reads on the per-task lock stripe
Summary
-
InFlightScriptStore.TryGetTicketAsync(the crash-resume re-attach probe) queried the shared scopedIRepository/DbContext without taking the per-serverTaskIdlock stripe that the writes (RecordDispatchedAsync/ClearAsync) already use. - A parallel batch runs every target's re-attach probe on the one DbContext the Hangfire worker owns for that task. Two concurrent probes — or a probe racing a record/clear — drove two operations onto the same context, and EF threw
A second operation was started on this context instance. Intermittent crash in multi-target deployments using crash-resume re-attach. - Fix: gate the read with the same stripe as the writes, so all of a task's DbContext access serialises onto one writer.
Why this surfaced now
The KubernetesResumeCheckpointE2ETests/TentacleMixedModeE2ETests.MixedMode_SingleDeployment_BothTargetsExecute E2E (two targets in parallel) hits this reattach path and exposed the race intermittently — it's a narrow timing window, so most CI runs win it. Pre-existing on main; not introduced by any recent change.
Non-breaking + generic
- Same method signature and return; the read just serialises on the existing stripe mechanism. No API/behavior change for callers — only correctness under concurrency.
- The write path (
MutateAsync) is untouched; the fix mirrors its existing inline gate pattern. - The stripe is bounded (64, no per-task growth) and a task always maps to the same stripe, so a task's reads + writes fully serialise; distinct tasks colliding on a stripe serialise harmlessly.
Test plan
-
New integration regression InFlightScriptStoreTests.ConcurrentReadsAndWrites_OnOneSharedContext_DoNotThrow— resolves one store (one DbContext) and fires concurrent reads+writes. Verified red→green: without the fix it fails with the exact production error (A second operation was started on this context instance); with the fix it passes. (The other tests here use a freshRunscope per call and so never share a context — this one deliberately doesn't.) -
Checkpoint + resume-reattach integration sweep: 11/11. InFlightScript unit: 11/11. -
dotnet build— 0 errors.