Fix shadow recovery subscriber registrations#96
Closed
cursor[bot] wants to merge 2 commits into
Closed
Conversation
Co-authored-by: dallison <dallison@users.noreply.github.com>
Co-authored-by: dallison <dallison@users.noreply.github.com>
6 tasks
dallison
added a commit
that referenced
this pull request
Jul 16, 2026
…ss validation, shadow recovery) (#109) * Fix streaming RPC cancel watcher lifetimes Streaming RPC handlers in the rpc, asio_rpc, coro_rpc and co20_rpc server backends spawned a detached cancel watcher that captured the per-request AnyStreamWriter and RpcRequest stack objects by reference. After a stream completed normally or errored, a later cancel/shutdown wakeup could resume the watcher and access freed stack memory, risking server crashes or request-id corruption. AnyStreamWriter also stored the RpcRequest by reference, extending the dangling-reference hazard. Make AnyStreamWriter own a copy of the RpcRequest and share an atomic cancellation state, have watchers capture only stable request/session IDs plus the shared state and a per-stream stop pipe, and wake each watcher when the handler returns so normal completion leaves no suspended watcher behind. Combines cursor bot PR #90. * Fix Asio wait timeouts and cancellation-slot crashes Two problems in the optional Boost.Asio coroutine backend: - async::WaitEither had no timeout support, so client wrappers such as Subscriber::Wait(fd, timeout, ctx) and reliable publisher waits discarded the caller-provided timeout and could hang forever when neither fd became ready. Added timeout support to WaitEither (with a non-suspending fast path) and passed the client deadlines through. - Asio wait/socket helpers unconditionally installed cancellation handlers on the yield_context cancellation slot. Plain boost::asio::spawn(..., detached) coroutines have no connected cancellation slot, so ordinary async pub/sub paths could abort inside Boost.Asio. Guard cancellation-slot assignment/clear with is_connected(). Combines cursor bot PR #92 (supersedes the cancellation-slot portion of PR #99). * Fix bridge retirement lifetime races Bridged reliable channels with retirement notifications could crash or wedge when retirement messages raced with bridge teardown or arrived immediately after a bridged send: - The retirement receiver was spawned (via SpawnOnNewStrand, which posts work asynchronously) with a reference to a parent coroutine's stack-owned listener socket, and the retirement notifier captured a stack-owned transmitter. Shutdown or early bridge teardown could leave spawned coroutines using destroyed sockets. - Outstanding bridged ActiveMessage tracking was updated only after the network send and shared without synchronization, so a fast peer could retire a slot before it was recorded, causing a null dereference (the old code did (*active_retirement_msgs)[slot_id]->DecRef() with no null check) or a permanently pinned reliable slot. Move the retirement listener/transmitter sockets to shared ownership and close them when parent bridge coroutines unwind so helpers wake safely. Add a synchronized BridgeRetirementState that tracks bridged ActiveMessages before sending, releases duplicate/late/failed retirements safely, and cleans up outstanding slots on teardown. Combines cursor bot PR #107 (supersedes duplicate PRs #100, #102, #106 and the bridge portions of #93 and #99). * Validate bridged channel address payloads Malformed discovery/bridge protobufs could set a channel address family while providing fewer than four address bytes. The server decoded those bytes with unchecked memcpy/reinterpret_cast in bridge subscribe and retirement handling, letting a network peer trigger out-of-bounds reads/undefined behavior and potential server crashes. Add a shared ParseChannelAddress helper that validates address length and family before decoding, and use it for both the subscribe receiver and retirement socket parsing. Combines cursor bot PR #97. * Rebuild CCB subscriber registrations on shadow recovery When a server recovered channel state from a shadow, RecoverFromShadow re-created the subscriber users but never rebuilt the recovered channel's CCB subscriber registrations, so a channel could come back with a stale or zero subscriber count until the next remap. Call RegisterExistingSubscribers() after re-adding the recovered subscribers, matching the RemapChannel path. Combines cursor bot PR #96. * Update bazel lock file * Confine bridge retirement state to one strand, drop its mutex The BridgeRetirementState vector was shared between the bridge transmitter and its retirement-reader coroutine, which ran on two separate strands, so it needed a mutex to stay correct under a multi-threaded io_context. Spawn the retirement reader on the transmitter's own strand (new AsyncRuntime::SpawnOnCurrentStrand) so the two are serialized by the strand and the mutex can be removed. They still interleave cooperatively at every suspension point, so the reader keeps draining retirements while the transmitter is parked on backpressure.
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.
Summary
Validation
npx --yes @bazel/bazelisk test //shadow:shadow_test --test_filter=ShadowRecoveryTest.RecoveryRebuildsCcbSubscriberRegistrationsnpx --yes @bazel/bazelisk test //shadow:shadow_test