fix(transport): contain callback exceptions at C ABI boundary (#67)#71
Conversation
📝 WalkthroughWalkthroughZenoh transport callback handlers now contain exceptions from Get sinks and Queryable callbacks, reject empty Queryable callbacks, add atomic query activity gating, and include integration tests covering safe failure behavior and continued session operation. ChangesCallback safety
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Zenoh as zenoh-c
participant Transport as OnGetReply / OnQueryable
participant Callback as User callback
participant Query as TransportQuery
Zenoh->>Transport: Invoke callback thunk
Transport->>Callback: Deliver reply or query
Callback-->>Transport: Return or throw
Transport->>Query: Update stop or active state
Query->>Zenoh: Reply only while active
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/transport/zenoh_transport.cpp`:
- Around line 197-208: Move the exception guard in OnGetReply and the queryable
closure to encompass all callback operations, including string and encoding
setup, make_shared, and key-expression conversion. Ensure any exception escaping
those preparation steps is caught at the outermost zenoh C callback boundary and
sets the existing stop flag without propagating.
In `@tests/integration/transport_test.cpp`:
- Around line 124-141: Update the test around transport_->Get to wait for the
queryable/callback processing to complete before asserting result and
sink_called. Add an explicit completion signal, synchronize access to
sink_called when callbacks may run on another thread, and ensure the test still
verifies Get succeeds without invoking the sink after the throwing queryable.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a1b622ea-0e7e-4553-87bb-a59b9a5a9c08
📒 Files selected for processing (2)
src/transport/zenoh_transport.cpptests/integration/transport_test.cpp
- Wrap Get reply sink invocation in try/catch so user exceptions cannot escape the zenoh-c reply closure; stop further replies on throw - Wrap queryable callback invocation in try/catch in the query closure - Reject empty queryable callbacks at declaration time with an empty handle - Preserve owned-resource cleanup (slice drop, callback_state deactivation) on both normal and exceptional paths - Add regression tests: QueryableCallbackExceptionIsContained, GetSinkExceptionIsContained, EmptyQueryableCallbackIsSafe
- Rebase #67 onto main after the #66 slice-conversion guard merge - Catch all C++ exceptions across complete Get and queryable C callback thunks - Use RAII for reply slices so successful conversions drop on every path - Extract the queryable thunk to a named callback and deactivate it on throw - Synchronize callback-containment test completion and sink observations
0719ea2 to
40b2b14
Compare
- wait until the throwing Get sink is actually invoked - verify the zenoh session remains usable after containment
|



Closes #67
Scope
Changes
src/transport/zenoh_transport.cpptry/catch(...). On throw, stop further replies for this Get (c->stop = true) so the closure drains without re-entering a throwing sink. AZenohOwned<z_owned_slice_t>RAII owner releases a successfully created slice on both normal and exceptional paths.OnQueryableas a named callback and wrap the complete thunk intry/catch(...). Atomiccallback_state->activedeactivation runs on normal, exceptional, and inactive-queryable paths.std::bad_function_callon every query.tests/integration/transport_test.cppQueryableCallbackExceptionIsContained: a throwing queryable callback does not crash the process; the session remains usable (round-trips a second working queryable afterward).GetSinkExceptionIsContained: confirms the throwing Get result sink is invoked, contains its exception, and verifies the session remains usable afterward.EmptyQueryableCallbackIsSafe: an empty callback yields a safe empty handle.AC Verification
OnGetReplyandOnQueryablewrap their complete callback thunks intry/catch(...).DeclareQueryablereturns an empty handle for an empty callback; the thunk also guardsif (state->callback).QueryableRoundTrip,GetWithoutQueryableDoesNotInvokeSink,PutAndDeleteReturnOk.RED Phase
Confirmed before the fix (current
main):QueryableCallbackExceptionIsContained: Timeout — the thrown exception escaped the zenoh-c closure, disrupting the internal runtime thread so the session stopped responding.GetSinkExceptionIsContained: Failed —C++ exception with description "sink boom" thrown in the test body(escaped the C ABI boundary into the test runner).EmptyQueryableCallbackIsSafe: passed (declaration did not crash even before the guard).After the fix all three pass and the full suite is green (97/97).
Notes
std::bad_function_callfrom an empty callback is now impossible because empty callbacks are rejected atDeclareQueryabletime; theif (state->callback)guard in the thunk is a defensive second layer.