Skip to content

Commit 1fea3ba

Browse files
committed
ipc, test: Add tests for unclean disconnect and thread busy behavior
Upcoming libmultiprocess changes are expected to alter this behavior (#34250 (comment)), making test coverage useful for documenting current behavior and validating the intended changes.
1 parent a2fd558 commit 1fea3ba

1 file changed

Lines changed: 97 additions & 0 deletions

File tree

test/functional/interface_ipc.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
"""Test the IPC (multiprocess) interface."""
66
import asyncio
7+
import http.client
8+
import re
79

10+
from contextlib import ExitStack
811
from test_framework.test_framework import BitcoinTestFramework
912
from test_framework.util import assert_equal
1013
from test_framework.ipc_util import (
@@ -81,10 +84,104 @@ async def async_routine():
8184
assert_equal(e.type, "FAILED")
8285
asyncio.run(capnp.run(async_routine()))
8386

87+
def run_unclean_disconnect_test(self):
88+
"""Test behavior when disconnecting during an IPC call that later
89+
returns a non-null interface pointer. Currently this behavior causes a
90+
crash as reported https://github.com/bitcoin/bitcoin/issues/34250, but a
91+
followup will change this behavior."""
92+
node = self.nodes[0]
93+
self.log.info("Running disconnect during BlockTemplate.waitNext")
94+
timeout = self.rpc_timeout * 1000.0
95+
disconnected_log_check = ExitStack()
96+
97+
async def async_routine():
98+
ctx, init = await make_capnp_init_ctx(self)
99+
self.log.debug("Create Mining proxy object")
100+
mining = init.makeMining(ctx).result
101+
102+
self.log.debug("Create a template")
103+
opts = self.capnp_modules['mining'].BlockCreateOptions()
104+
template = (await mining.createNewBlock(ctx, opts)).result
105+
106+
self.log.debug("Wait for a new template")
107+
waitoptions = self.capnp_modules['mining'].BlockWaitOptions()
108+
waitoptions.timeout = timeout
109+
waitoptions.feeThreshold = 1
110+
with node.assert_debug_log(expected_msgs=["BlockTemplate.waitNext", "IPC server post request"], timeout=2):
111+
promise = template.waitNext(ctx, waitoptions)
112+
await asyncio.sleep(0.1)
113+
disconnected_log_check.enter_context(node.assert_debug_log(expected_msgs=["IPC server: socket disconnected"], timeout=2))
114+
del promise
115+
116+
asyncio.run(capnp.run(async_routine()))
117+
118+
# Wait for socket disconnected log message, then generate a block to
119+
# cause the waitNext() call to return a new template. This will cause a
120+
# crash and disconnect with error output.
121+
disconnected_log_check.close()
122+
try:
123+
self.generate(node, 1)
124+
except (http.client.RemoteDisconnected, BrokenPipeError, ConnectionResetError):
125+
pass
126+
node.wait_until_stopped(expected_ret_code=(-11, -6, 1, 66), expected_stderr=re.compile(""))
127+
self.start_node(0)
128+
129+
def run_thread_busy_test(self):
130+
"""Test behavior when sending multiple calls to the same server thread
131+
which used to cause a crash as reported
132+
https://github.com/bitcoin/bitcoin/issues/33923 and currently causes a
133+
thread busy error. A future change will make this just queue the calls
134+
for execution and not trigger any error"""
135+
node = self.nodes[0]
136+
self.log.info("Running thread busy test")
137+
timeout = self.rpc_timeout * 1000.0
138+
139+
async def async_routine():
140+
ctx, init = await make_capnp_init_ctx(self)
141+
self.log.debug("Create Mining proxy object")
142+
mining = init.makeMining(ctx).result
143+
144+
self.log.debug("Create a template")
145+
opts = self.capnp_modules['mining'].BlockCreateOptions()
146+
template = (await mining.createNewBlock(ctx, opts)).result
147+
148+
self.log.debug("Wait for a new template")
149+
waitoptions = self.capnp_modules['mining'].BlockWaitOptions()
150+
waitoptions.timeout = timeout
151+
waitoptions.feeThreshold = 1
152+
153+
# Make multiple waitNext calls where the first will start to
154+
# execute, the second will be posted waiting to execute, and the
155+
# third will fail to execute because the execution thread is busy.
156+
with node.assert_debug_log(expected_msgs=["BlockTemplate.waitNext", "IPC server post request"], timeout=2):
157+
promise1 = template.waitNext(ctx, waitoptions)
158+
await asyncio.sleep(0.1)
159+
with node.assert_debug_log(expected_msgs=["BlockTemplate.waitNext", "IPC server post request"], timeout=2):
160+
promise2 = template.waitNext(ctx, waitoptions)
161+
await asyncio.sleep(0.1)
162+
try:
163+
await template.waitNext(ctx, waitoptions)
164+
except capnp.lib.capnp.KjException as e:
165+
assert_equal(e.description, "remote exception: std::exception: thread busy")
166+
assert_equal(e.type, "FAILED")
167+
else:
168+
raise AssertionError("Expected thread busy exception")
169+
170+
# Generate a new block to make the active waitNext calls return, then clean up.
171+
with node.assert_debug_log(expected_msgs=["IPC server send response"], timeout=2):
172+
self.generate(node, 1, sync_fun=self.no_op)
173+
await ((await promise1).result).destroy(ctx)
174+
await ((await promise2).result).destroy(ctx)
175+
await template.destroy(ctx)
176+
177+
asyncio.run(capnp.run(async_routine()))
178+
84179
def run_test(self):
85180
self.run_echo_test()
86181
self.run_mining_test()
87182
self.run_deprecated_mining_test()
183+
self.run_unclean_disconnect_test()
184+
self.run_thread_busy_test()
88185

89186
if __name__ == '__main__':
90187
IPCInterfaceTest(__file__).main()

0 commit comments

Comments
 (0)