From 8cedea19583dce603fe9704829a2efbe19bcb427 Mon Sep 17 00:00:00 2001 From: link2xt Date: Tue, 29 Sep 2026 18:35:16 +0000 Subject: [PATCH] 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. --- .../src/deltachat_rpc_client/rpc.py | 5 ++++- deltachat-rpc-client/tests/test_something.py | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/rpc.py b/deltachat-rpc-client/src/deltachat_rpc_client/rpc.py index 237a89145..6f0ab84ae 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/rpc.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/rpc.py @@ -149,7 +149,10 @@ class Rpc: def close(self) -> None: """Terminate RPC server process and wait until the reader loop finishes.""" self.closing = True - self.stop_io_for_all_accounts() + # JSON-RPC error may happen if RPC server process has crashed already. + # We still want to shutdown all threads in this case. + with contextlib.suppress(JsonRpcError): + self.stop_io_for_all_accounts() # Let `events_loop` stop cleanly on `closing` before the pipe goes away, # otherwise it might exit through an "RPC server closed" error instead. self.events_thread.join() diff --git a/deltachat-rpc-client/tests/test_something.py b/deltachat-rpc-client/tests/test_something.py index 70743f4be..148d8cb99 100644 --- a/deltachat-rpc-client/tests/test_something.py +++ b/deltachat-rpc-client/tests/test_something.py @@ -35,6 +35,19 @@ def test_sleep(rpc) -> None: assert sleep_5_future in pending +def test_shutdown(rpc) -> None: + """Test RPC client shutdown if RPC server process is terminated. + + This is a regression test, at the time of adding it RPC client + did not terminate all threads and python process did not exit. + Shutting down all threads is tested implicitly by pytest-timeout. + """ + rpc.process.kill() + rpc.process.wait() + with pytest.raises(JsonRpcError): + rpc.get_system_info() + + def test_email_address_validity(rpc) -> None: valid_addresses = [ "email@example.com",