Daemon block observability: loud Block<T> failures, fault semantics, narrowed teardown catches (GH-506, GH-507)#509
Merged
Conversation
…iring + narrowed teardown catches Closes #506, closes #507. Block<T> (#506): - The default OnError sink is no longer Debug.WriteLine (compiled away in release builds = /dev/null in production); it now writes to Console.Error and Trace so an unwired block failure is always visible somewhere. - The error callback can no longer kill the consumer loop; if the callback itself throws, error handling is terminally broken and the block faults. - A faulted block is observable by its producers: Post/PostAsync throw with the terminal failure as inner exception, the channel is completed with the failure so a producer block-waiting on a full bounded channel is released instead of hanging forever, and Failure exposes the terminal exception. - OnError is now part of IBlock<T>; BlockSet delegates to its top block and BatchingChannel to its inner block so PushUpstream chains can be wired. Daemon wiring (#506): - GroupedProjectionExecution, SubscriptionExecutionBase, ProjectionExecution wire OnError on their blocks (both the processing block and the upstream grouping block) to the daemon logger plus ReportCriticalFailureAsync. - SubscriptionAgent's command block and ShardStateTracker's publish block log escaped exceptions instead of losing them. Teardown catches (#507): - The state-based `catch when (_cancellation.IsCancellationRequested)` clauses in GroupedProjectionExecution, ProjectionExecution, and SubscriptionExecutionBase now classify the exception: genuine cancellation side effects (OCE incl. wrapped/aggregated, ObjectDisposedException, and DbException SqlStates 57014/08xxx) stay silent, anything else is logged before the teardown discards it. A 23514 missing-partition failure is a schema problem regardless of the CTS state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #506. Closes #507. Part of the community open-issue sweep; companion to #508 (GH-505) — the three ride the next release together.
Block (GH-506)
OnErroris no longerDebug.WriteLine— that call is compiled away in release builds, which made any exception escaping a block action invisible in production (the two-day silent projection outage in Sharded + tenant-partitioned daemon: per-tenant subscription agents silently never start / composite agent silently stalls at a fixed sequence (no log output) marten#4941). The last-resort default now writes toConsole.Error+Trace; real hosts should (and the daemon now does) assignOnErrorto actual logging.OnErrorand the loop keeps consuming. If the callback itself throws, error handling is terminally broken and the block faults.ChannelClosedExceptioninstead of hanging forever — the post-2.19.1 backpressure interaction called out on the issue),Post/PostAsyncthrowInvalidOperationExceptionwith the terminal failure as inner exception, andBlock<T>.Failureexposes it.WaitForCompletionAsyncstays quiet on a faulted block so teardown paths don't blow up.OnErrormoves ontoIBlock<T>(virtual default onBlockBase<T>, so downstreamBlockBasesubclasses — e.g. Wolverine's — compile unchanged);BlockSetdelegates to its top block,BatchingChannelto its inner block, which makesPushUpstreamchains wireable.RetryBlockbehavior is unchanged: it remains deliberately unbounded (fix(blocks): Post no longer silently drops items on a full bounded channel #485 self-repost deadlock) and its action-level try/catch means it never faults its inner block.Daemon OnError wiring (GH-506)
GroupedProjectionExecution(processing + upstream grouping block),SubscriptionExecutionBase, andProjectionExecutionwireOnErrorto the daemon logger plusReportCriticalFailureAsync;SubscriptionAgent's command block logs + reports critical failure;ShardStateTracker's publish block logs. Nothing escaping a daemon block can be invisible anymore.Narrowed teardown catches (GH-507)
The state-based
catch when (_cancellation.IsCancellationRequested)clauses (andSubscriptionExecutionBase's unconditional OCE swallow, which had drifted even wider than the issue described) now classify the caught exception via the newCancellationExceptions.IsCancellationLike:OperationCanceledExceptionincl. wrapped/aggregated,ObjectDisposedException, andDbExceptions whoseSqlStateis57014(query_canceled) or class08(connection teardown). Provider-agnostic viaDbException.SqlState(.NET 5+), no Npgsql reference needed; wrapped provider exceptions (e.g.MartenCommandException) are handled by the inner-exception walk.23514missing-partition failure is a schema problem regardless of the CTS state. Logged at Information per the teardown context — the same failure outside teardown still takes the LogError + critical-failure path.ProjectionExecution.processRangeAsynchad the opposite defect (no teardown clause at all — it critical-failed shards on teardown side effects); it gets the same classified clause.Tests
CoreTests/Blocks/BlockErrorHandlingTests(5): OnError receives item+exception and the loop survives; throwing callback faults the block andPost/PostAsyncthrow; a fault releases a producer parked on a full bounded channel (TCS-gated, no sleeps in the arrange);WaitForCompletionAsyncquiet on fault; the default sink survives release builds (Console.Error capture).EventTests/Daemon/TeardownExceptionHandlingTests(15):IsCancellationLikeclassification matrix (OCE/wrapped/aggregated/ODE/SqlState theory incl. the incident's23514), plusGroupedProjectionExecutionend-to-end: a fakeDbException("23514")thrown while the shard CTS fires mid-batch-build is logged and not promoted toReportCriticalFailureAsync; a genuine OCE in the same window stays silent.Full CoreTests 458/458, EventTests 498/498, EventStoreTests 72/72, solution build clean.
🤖 Generated with Claude Code