journal: OutboxRelay throws NullSinkError instead of crashing on a null sink (closes morph#95) - #111
Merged
Merged
Conversation
…g on a null sink (morph#95) A null sink reaching relay()'s sink-using path (drainOutbox() reporting at least one row) used to be a null-pointer virtual dispatch through sink->append() -- real UB, confirmed as a process crash, not a catchable exception. A null drainOutbox/markRelayed already throws a catchable std::bad_function_call; this makes sink consistent with that instead of the odd one out. relay() now checks sink for null right after logIfAnyDepNull() (which already logged the null-sink warning) and before any row ever reaches sink->append(), throwing the new NullSinkError (a std::runtime_error, matching this codebase's established <Domain>Error convention -- SerializationError, FileOfflineQueueError, ParseError, etc.). An empty drainOutbox() with a null sink is still a no-op, unchanged: nothing would touch sink either way. This is a deliberate, documented behavior change (per the issue's own explicit caution against silently patching this) -- docs/spec/journal/ journal.md's OutboxRelay contract is updated to match. The one previously-untestable branch (a null sink actually reaching sink->append) now has a real test instead of a comment explaining why it couldn't be tested. Full suite: 1047 test cases, 10040 assertions, all passing. Doxygen doc build (WARN_AS_ERROR=FAIL_ON_WARNINGS) passes clean for the new NullSinkError type. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
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 #95:
OutboxRelay::relay()'s null-sink pathused to be real UB (a null-pointer virtual dispatch through
sink->append(), confirmed as a process crash) with no seam to test itportably. Fixes it directly per the issue's first proposed option
(a null
sinkthrows a catchable exception), rather than adding anobservability-only hook that leaves the crash in place.
The fix
relay()now checkssinkfor null immediately afterlogIfAnyDepNull()(which already logs the warning) and before any rowever reaches
sink->append(), throwing a newNullSinkError(astd::runtime_error, matching this codebase's established<Domain>Errorconvention --SerializationError,FileOfflineQueueError,ParseError, etc.):A null
drainOutbox/markRelayedalready throws a catchablestd::bad_function_call(invoking a nullstd::function) -- this makessinkconsistent with that instead of the one member that used tocrash instead of throw. An empty
drainOutbox()with a nullsinkisstill a no-op, unchanged: nothing would touch
sinkeither way.Deliberate, documented behavior change
Per the issue's own explicit caution ("this changes documented
behavior... needs a deliberate decision, not a silent patch"):
docs/spec/journal/journal.md'sOutboxRelaycontract section isupdated to state the new throw behavior, replacing "invoking a null
member still throws (
std::bad_function_call) or crashes as usual".Tests
Replaces the block comment in
test_outbox.cppexplaining why thenull-sink-reaching-
append()case couldn't be tested with a real test:OutboxRelay::relay(): a null sink reaching a non-empty drain throws NullSinkError, not UBdrains one real row withsinkstill null,asserts
NullSinkErroris thrown, and asserts the null-sink warningstill fired.
Verification
Full
morph_testssuite: 1047 test cases, 10040 assertions, allpassing (up from 1046/10038 on master). Doxygen doc build
(
WARN_AS_ERROR=FAIL_ON_WARNINGS, per CLAUDE.md's CI notes) passesclean for the new
NullSinkErrortype.🤖 Generated with Claude Code