From 8fe91f37194edcca1b7dfdd06bd0d4f5b2154e9b Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Tue, 20 Jan 2026 23:36:00 -0500 Subject: [PATCH] test: Updates needed after bitcoin-core/libmultiprocess#240 Upstream PR bitcoin-core/libmultiprocess#240 fixed various issues which require updates to python IPC tests. Those changes are made in this commit. --- test/functional/interface_ipc.py | 43 ++++++++++--------------- test/functional/interface_ipc_mining.py | 18 +++++------ 2 files changed, 25 insertions(+), 36 deletions(-) diff --git a/test/functional/interface_ipc.py b/test/functional/interface_ipc.py index f90f72b0ece..1e279e00cb3 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 2221d462879..72919b0cb50 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()))