test: Updates needed after bitcoin-core/libmultiprocess#240
What changed, and why it matters
This commit updates Bitcoin Core's automated multiprocess (IPC) tests to match fixes made in an upstream library. The test changes describe that two previously crash-causing IPC scenarios—disconnecting during a call and overloading a server thread—now behave gracefully. A third change documents a macOS-specific exception-handling quirk. The commit itself only changes test code, not the production Bitcoin Core code that handles IPC, so it does not introduce or fix a direct vulnerability in the shipped software. It is evidence that related crash bugs were fixed elsewhere.
No immediate action is required for this commit because it only changes tests. Reviewers should verify that the upstream libmultiprocess fixes (PR #240 and related PR #218) are included in the build dependencies before these tests are run, and consider whether the macOS exception-type quirk needs a tracking issue or runtime fix.
Security signals we found
Test updated because upstream library fixed crash-on-disconnect during IPC call
Test updated because upstream library fixed thread-busy crash/exception
Test now expects graceful cancellation instead of node termination
macOS-specific exception-type recognition issue noted in test comment
Evidence from the diff
The diff modifies two Python functional tests (test/functional/interface_ipc.py and test/functional/interface_ipc_mining.py). It removes expectations of node crashes (negative exit codes, stderr regex matching, restart logic) and replaces them with assertions that the node logs cancellation messages and continues running. It also updates the ‘thread busy’ test to expect queued execution of multiple waitNext calls rather than a ‘thread busy’ exception. In interface_ipc_mining.py it replaces a workaround for a crash-on-disconnect with a platform-specific assertion for a generic kj::Exception on Darwin. The actual fixes are in bitcoin-core/libmultiprocess#240 and #218, not in this Bitcoin Core commit.
Changed components
test/functional/interface_ipc.pytest/functional/interface_ipc_mining.pybitcoin-core/libmultiprocess (upstream dependency, not changed in this commit)Inspect captured patch +25 / −36
diff --git a/test/functional/interface_ipc.py b/test/functional/interface_ipc.py
index f90f72b0..1e279e00 100755
--- a/test/functional/interface_ipc.py
+++ b/test/functional/interface_ipc.py
@@ -4,8 +4,6 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the IPC (multiprocess) interface."""
import asyncio
-import http.client
-import re
from contextlib import ExitStack
from test_framework.test_framework import BitcoinTestFramework
@@ -86,9 +84,9 @@ class IPCInterfaceTest(BitcoinTestFramework):
def run_unclean_disconnect_test(self):
"""Test behavior when disconnecting during an IPC call that later
- returns a non-null interface pointer. Currently this behavior causes a
- crash as reported https://github.com/bitcoin/bitcoin/issues/34250, but a
- followup will change this behavior."""
+ returns a non-null interface pointer. This used to cause a crash as
+ reported https://github.com/bitcoin/bitcoin/issues/34250, but now just
+ results in a cancellation log message"""
node = self.nodes[0]
self.log.info("Running disconnect during BlockTemplate.waitNext")
timeout = self.rpc_timeout * 1000.0
@@ -110,28 +108,22 @@ class IPCInterfaceTest(BitcoinTestFramework):
with node.assert_debug_log(expected_msgs=["BlockTemplate.waitNext", "IPC server post request"], timeout=2):
promise = template.waitNext(ctx, waitoptions)
await asyncio.sleep(0.1)
- disconnected_log_check.enter_context(node.assert_debug_log(expected_msgs=["IPC server: socket disconnected"], timeout=2))
+ disconnected_log_check.enter_context(node.assert_debug_log(expected_msgs=["IPC server: socket disconnected", "canceled while executing"], timeout=2))
del promise
asyncio.run(capnp.run(async_routine()))
# Wait for socket disconnected log message, then generate a block to
- # cause the waitNext() call to return a new template. This will cause a
- # crash and disconnect with error output.
- disconnected_log_check.close()
- try:
+ # cause the waitNext() call to return a new template. Look for a
+ # canceled IPC log message after waitNext returns.
+ with node.assert_debug_log(expected_msgs=["interrupted (canceled)"], timeout=2):
+ disconnected_log_check.close()
self.generate(node, 1)
- except (http.client.RemoteDisconnected, BrokenPipeError, ConnectionResetError):
- pass
- node.wait_until_stopped(expected_ret_code=(-11, -6, 1, 66), expected_stderr=re.compile(""))
- self.start_node(0)
def run_thread_busy_test(self):
"""Test behavior when sending multiple calls to the same server thread
which used to cause a crash as reported
- https://github.com/bitcoin/bitcoin/issues/33923 and currently causes a
- thread busy error. A future change will make this just queue the calls
- for execution and not trigger any error"""
+ https://github.com/bitcoin/bitcoin/issues/33923."""
node = self.nodes[0]
self.log.info("Running thread busy test")
timeout = self.rpc_timeout * 1000.0
@@ -151,27 +143,26 @@ class IPCInterfaceTest(BitcoinTestFramework):
waitoptions.feeThreshold = 1
# Make multiple waitNext calls where the first will start to
- # execute, the second will be posted waiting to execute, and the
- # third will fail to execute because the execution thread is busy.
+ # execute, and the second and third will be posted waiting to
+ # execute. Previously, the third call would fail calling
+ # mp::Waiter::post() because the waiting function slot is occupied,
+ # but now posts are queued.
with node.assert_debug_log(expected_msgs=["BlockTemplate.waitNext", "IPC server post request"], timeout=2):
promise1 = template.waitNext(ctx, waitoptions)
await asyncio.sleep(0.1)
with node.assert_debug_log(expected_msgs=["BlockTemplate.waitNext", "IPC server post request"], timeout=2):
promise2 = template.waitNext(ctx, waitoptions)
await asyncio.sleep(0.1)
- try:
- await template.waitNext(ctx, waitoptions)
- except capnp.lib.capnp.KjException as e:
- assert_equal(e.description, "remote exception: std::exception: thread busy")
- assert_equal(e.type, "FAILED")
- else:
- raise AssertionError("Expected thread busy exception")
+ with node.assert_debug_log(expected_msgs=["BlockTemplate.waitNext", "IPC server post request"], timeout=2):
+ promise3 = template.waitNext(ctx, waitoptions)
+ await asyncio.sleep(0.1)
# Generate a new block to make the active waitNext calls return, then clean up.
with node.assert_debug_log(expected_msgs=["IPC server send response"], timeout=2):
self.generate(node, 1, sync_fun=self.no_op)
await ((await promise1).result).destroy(ctx)
await ((await promise2).result).destroy(ctx)
+ await ((await promise3).result).destroy(ctx)
await template.destroy(ctx)
asyncio.run(capnp.run(async_routine()))
diff --git a/test/functional/interface_ipc_mining.py b/test/functional/interface_ipc_mining.py
index 2221d462..72919b0c 100755
--- a/test/functional/interface_ipc_mining.py
+++ b/test/functional/interface_ipc_mining.py
@@ -6,7 +6,7 @@
import asyncio
from contextlib import AsyncExitStack
from io import BytesIO
-import re
+import platform
from test_framework.blocktools import NULL_OUTPOINT
from test_framework.messages import (
MAX_BLOCK_WEIGHT,
@@ -245,17 +245,15 @@ class IPCMiningTest(BitcoinTestFramework):
await mining.createNewBlock(ctx, opts)
raise AssertionError("createNewBlock unexpectedly succeeded")
except capnp.lib.capnp.KjException as e:
- if e.type == "DISCONNECTED":
- # The remote exception isn't caught currently and leads to a
- # std::terminate call. Just detect and restart in this case.
- # This bug is fixed with
- # https://github.com/bitcoin-core/libmultiprocess/pull/218
- assert_equal(e.description, "Peer disconnected.")
- self.nodes[0].wait_until_stopped(expected_ret_code=(-11, -6, 1, 66), expected_stderr=re.compile(""))
- self.start_node(0)
+ if e.description == "remote exception: unknown non-KJ exception of type: kj::Exception":
+ # macOS + REDUCE_EXPORTS bug: Cap'n Proto fails to recognize
+ # its own exception type and returns a generic error instead.
+ # https://github.com/bitcoin/bitcoin/pull/34422#discussion_r2863852691
+ # Assert this only occurs on Darwin until fixed.
+ assert_equal(platform.system(), "Darwin")
else:
assert_equal(e.description, "remote exception: std::exception: block_reserved_weight (0) must be at least 2000 weight units")
- assert_equal(e.type, "FAILED")
+ assert_equal(e.type, "FAILED")
asyncio.run(capnp.run(async_routine()))
Why this scored 26/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.