ipc: make ipc::disconnectIncoming wait for in-progress calls to complete #35932

pull ryanofsky wants to merge 4 commits into bitcoin:master from ryanofsky:pr/diswait changing 5 files +332 −19
  1. ryanofsky commented at 2:13 PM on August 7, 2026: contributor

    This fixes an antithesis bug reported #35845 and similar bug reported in #33387 where if asynchronous IPC mining calls are made when the node is shutting down it's possible for assert(m_node.chainman) to trigger. This happens because the Ipc::disconnectIncoming method does not wait for asynchronous calls to complete after it disconnects IPC clients, so they may continue to run as the node is shutting down.

    This PR changes disconnectIncoming to wait for asynchronous calls to complete to avoid this issue. It's a draft because it depends on libmultiprocess changes, but should otherwise be ready to review.

    **This is based on https://github.com/bitcoin-core/libmultiprocess/pull/335.**

  2. ipc: add Connection::disconnect() separating teardown from destruction
    Split connection teardown out of ~Connection into an idempotent disconnect()
    method, with the destructor delegating to it. This is a behavior-neutral
    refactor: the same steps run in the same order on destruction.
    
    Having a separate disconnect() method allows severing a connection while
    keeping the Connection object alive, which the next commits use to let
    shutdown code wait for in-flight server call bodies to finish after a
    disconnect (bitcoin/bitcoin#35845). Two details are new:
    
    - disconnect() cancels the m_on_disconnect handlers before severing the
      connection. Previously they were implicitly canceled when the TaskSet
      member was destroyed. When disconnect() is called separately from
      destruction, this is required for correctness: severing the stream
      completes m_network.onDisconnect(), and the registered handlers (_Serve,
      ConnectStream) destroy the Connection object out from under the caller.
    
    - disconnect() explicitly releases m_thread_pool and m_thread_map so worker
      thread teardown happens at disconnect time whether or not the object is
      destroyed right away. Previously this happened implicitly during member
      destruction.
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
    b74630ae4c
  3. ipc: add Connection::waitDrained() to wait for in-flight server calls
    Add a per-connection ServerObjectTracker counting live ProxyServer objects,
    incremented in the ProxyServerBase constructor and decremented in its
    destructor, with Connection::waitDrained() blocking until the count reaches
    zero and Connection::pendingServerObjects() exposing it for logging.
    
    Disconnecting a connection cancels the KJ promise of an in-flight call, but a
    C++ server method body already dispatched to a worker thread runs to
    completion. Counting live server objects turns Cap'n Proto's object lifetime
    rules into a usable quiescence signal: a ProxyServer object is not destroyed
    until its outstanding calls finish (the target capability is kept alive for
    the duration of a call and pinned by post()/PassField via thisCap()), so
    after disconnect() the count drains to zero exactly when no server call body
    is still executing. Waiting for that lets shutdown code avoid freeing
    application state that a still-running call body dereferences
    (bitcoin/bitcoin#35845).
    
    The tracker is held via shared_ptr by the Connection and by every
    ProxyServer object because objects kept alive by in-flight calls can outlive
    the Connection on some teardown paths (see ~ProxyServerBase), and their
    destructors must decrement state that is still valid. It must be declared
    before m_rpc_system, whose construction creates the bootstrap server object
    that registers itself with the tracker.
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
    41fd35bc72
  4. test: cover draining in-flight server call after disconnect
    Add a deterministic mptest regression test for bitcoin/bitcoin#35845: hold a
    server method body in flight on a worker thread, call
    Connection::disconnect(), and assert that Connection::waitDrained() blocks
    until the body finishes and its server object is destroyed. Also covers
    destroying an already-disconnected connection (~Connection noticing
    disconnect() has run).
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
    1d530c058c
  5. ipc: drain in-flight server calls before shutdown frees node state
    Fix bitcoin/bitcoin#35845, an assertion failure in MinerImpl::chainman()
    during shutdown of an IPC-mining node.
    
    Shutdown() calls disconnectIncoming() before node.chainman.reset().
    Disconnecting cancels the KJ promise of an in-flight IPC server call, but a
    C++ server method body already dispatched to a libmultiprocess worker thread
    is not interrupted and runs to completion. A still-running body (an in-flight
    Mining.checkBlock) could then dereference m_node.chainman after
    chainman.reset() nulled it, aborting on Assert(m_node.chainman).
    
    Make disconnectIncoming() disconnect the non-parent incoming connections,
    wait off the event loop thread for their in-flight server call bodies to
    finish (Connection::waitDrained), and only then destroy them and return, so
    Shutdown() frees node state only once no server code is running. Log when
    the wait actually blocks so a shutdown hang here is diagnosable.
    
    No wait is needed for calls parked in waitTipChanged()/waitNext():
    Interrupt() runs before Shutdown() and notifies m_tip_block_cv after setting
    the shutdown signal, so those return before disconnectIncoming() runs.
    
    Intentional limitations, to keep the fix narrow: m_impl destructors
    scheduled on the async cleanup thread are not waited for, the kept-open
    parent connection is not drained, and new incoming connections can still be
    accepted during shutdown (preventing that needs a listener API, proposed in
    bitcoin-core/libmultiprocess#269).
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
    b0c970e305
  6. DrahtBot added the label IPC on Aug 7, 2026
  7. DrahtBot commented at 2:13 PM on August 7, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/35932.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process. A summary of reviews will appear here.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  8. DrahtBot added the label CI failed on Aug 7, 2026
  9. DrahtBot commented at 4:14 PM on August 7, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task lint: https://github.com/bitcoin/bitcoin/actions/runs/31186529513/job/92892361418</sub> <sub>LLM reason (✨ experimental): CI failed due to the lint “subtree” check detecting a subtree directory change without a corresponding subtree merge.</sub>

    <details><summary>Hints</summary>

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

    </details>


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-08-11 09:51 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me