Skip to content

Commit 8cedea1

Browse files
committed
fix(deltachat-rpc-client): shutdown all threads after RPC server crash
RPC server subprocess may exit for various reasons, e.g. because of panic or because of debug_assert! during testing, or because the process is killed manually or by OOM killer. We still want to shutdown all threads cleanly in this case and not fail in Rpc.close(). Otherwise Python process cannot exit as it waits forever for all threads to terminate. Without the fix, when added test is run in pytest, pytest prints "ERROR at teardown of test_shutdown" and gets stuck. When pytest is manually terminated with ^C, the following error is printed: ``` Exception ignored while joining a thread in _thread._shutdown(): Traceback (most recent call last): File "/usr/lib/python3.14/threading.py", line 1583, in _shutdown _thread_shutdown() KeyboardInterrupt: ``` _thread_shutdown() is the function waiting for all non-daemon threads to finish.
1 parent 7073049 commit 8cedea1

2 files changed

Lines changed: 17 additions & 1 deletion

File tree

‎deltachat-rpc-client/src/deltachat_rpc_client/rpc.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,10 @@ def start(self) -> None:
149149
def close(self) -> None:
150150
"""Terminate RPC server process and wait until the reader loop finishes."""
151151
self.closing = True
152-
self.stop_io_for_all_accounts()
152+
# JSON-RPC error may happen if RPC server process has crashed already.
153+
# We still want to shutdown all threads in this case.
154+
with contextlib.suppress(JsonRpcError):
155+
self.stop_io_for_all_accounts()
153156
# Let `events_loop` stop cleanly on `closing` before the pipe goes away,
154157
# otherwise it might exit through an "RPC server closed" error instead.
155158
self.events_thread.join()

‎deltachat-rpc-client/tests/test_something.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ def test_sleep(rpc) -> None:
3535
assert sleep_5_future in pending
3636

3737

38+
def test_shutdown(rpc) -> None:
39+
"""Test RPC client shutdown if RPC server process is terminated.
40+
41+
This is a regression test, at the time of adding it RPC client
42+
did not terminate all threads and python process did not exit.
43+
Shutting down all threads is tested implicitly by pytest-timeout.
44+
"""
45+
rpc.process.kill()
46+
rpc.process.wait()
47+
with pytest.raises(JsonRpcError):
48+
rpc.get_system_info()
49+
50+
3851
def test_email_address_validity(rpc) -> None:
3952
valid_addresses = [
4053
"email@example.com",

0 commit comments

Comments
 (0)