[DB-2159] Fault V2 projections on partition processor errors - #5666
[DB-2159] Fault V2 projections on partition processor errors#5666George-Payne wants to merge 3 commits into
Conversation
A state-handler exception faults the partition processor's task, but nothing observes partition tasks while the read loop runs, so a continuous projection never faults: it reports Running forever with no checkpoints, no persistence, and nothing logged. Two tests: a plain processor throw, and a throw with a checkpoint marker in flight - a marker the dead partition never acks holds the coordinator's checkpoint lock forever, wedging the shutdown path too. The tests assert before disposing because DisposeAsync on a wedged engine never returns: the read loop's final checkpoint marker blocks forever on the dead partition's full channel or its held checkpoint lock. These are deliberately red; the fix follows.
A state-handler exception faulted the partition processor's task, but partition tasks were only awaited after the read loop - which never completes for a continuous projection - and IsFaulted only watched the read-loop task. The projection wedged: Running forever, no checkpoints, no state or emitted-event persistence (the checkpoint write needs every partition to ack), nothing logged, and DisposeAsync hung forever. - Race the read loop against the first partition exit; on a processor fault, log it, stop the read loop, complete the channels, and fault the engine with the processor's exception so the management poll publishes Faulted. - Govern the read loop's final checkpoint marker with a drain token: live on graceful shutdown (ct is already cancelled there but the final checkpoint must still be written), cancelled on the fault path so it cannot wait on a channel the dead partition stopped reading or a checkpoint lock it will never release. - Wrap state-handler invocations in PartitionProcessingException so the fault reason surfaced as stateReason is debuggable: projection name, handler type, event position, and the handler's message (the same format V1 reports, so tooling sees one fault-reason shape regardless of engine version). Infrastructure failures (persisted-state reads, cache writes) propagate raw rather than blaming the user's handler. - Log each distinct sibling processor failure during drain (WhenAll only surfaces its first fault) without replacing the primary.
PR Summary by QodoFault V2 projections when partition processors throw (DB-2159)
AI Description
Diagram
High-Level Assessment
Files changed (4)
|
Code Review by Qodo
1. Complete before readloop stops
|
The old comment gave the pre-drain-token rationale (blocking on a full channel). The tokens are what stop the read loop now; completion is what lets sibling processors exit, and completing before awaiting the read loop is deliberate redundancy so a future await that misses a token cannot wedge the fault path.
a V2 projection whose event processing throws never faults. The partition processor's task dies unobserved - partition tasks are only awaited after the read loop, which never completes for a continuous projection, and
IsFaultedonly watches the read-loop task - so the projection reportsRunningforever: no checkpoint is written, no state or emitted events are persisted (the checkpoint write needs every partition to ack), nothing is logged, andDisposeAsynchangs on the dead partition's full channel. The identical query under V1 faults immediately.ProjectionEngineV2races the read loop against the first partition exit: on a processor fault it logs, cancels the read loop, completes the channels, and faults the engine with the processor's exception - the existingCoreProjectionV2poll then publishesFaultedwith the reason. The read loop's final checkpoint marker is governed by a drain token: live on graceful shutdown (the engine token is already cancelled there but the final checkpoint must still be written), cancelled on the fault path so it cannot wait on a channel the dead partition stopped reading or a checkpoint lock it will never release. Sibling processor failures during drain are logged without replacing the primary fault.PartitionProcessorwraps state-handler invocations in a newPartitionProcessingExceptionso the fault reason surfaced asstateReasonis debuggable: projection name, handler type, event position, and the handler's message - the same format V1 reports, so operators and tooling see one fault-reason shape regardless of engine version. Infrastructure failures (persisted-state reads, cache writes) propagate raw rather than blaming the user's handler.The fault-message format string is duplicated from
EventProcessingProjectionProcessingPhaserather than extracted to a shared location, to keep this change out of V1 code.