From fbd5a3a661dad23804b6fc8c00950a84571521e8 Mon Sep 17 00:00:00 2001 From: Julian Bright Date: Wed, 22 Jul 2026 19:37:13 -0700 Subject: [PATCH 1/4] handle signal registration outside the main thread --- python/restate/server.py | 2 +- tests/server.py | 62 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 tests/server.py diff --git a/python/restate/server.py b/python/restate/server.py index 70e3aba..219e705 100644 --- a/python/restate/server.py +++ b/python/restate/server.py @@ -228,7 +228,7 @@ async def app(scope: Scope, receive: Receive, send: Send): loop = asyncio.get_running_loop() try: loop.add_signal_handler(signal.SIGTERM, _on_sigterm) - except (NotImplementedError, RuntimeError): + except (NotImplementedError, RuntimeError, ValueError): pass # Windows or non-main thread sigterm_installed = True try: diff --git a/tests/server.py b/tests/server.py new file mode 100644 index 0000000..a83f167 --- /dev/null +++ b/tests/server.py @@ -0,0 +1,62 @@ +# +# Copyright (c) 2023-2025 - Restate Software, Inc., Restate GmbH +# +# This file is part of the Restate SDK for Python, +# which is released under the MIT license. +# +# You can find a copy of the license in file LICENSE in the root +# directory of this repository or package, or at +# https://github.com/restatedev/sdk-typescript/blob/main/LICENSE +# +import asyncio +from unittest.mock import Mock + +import pytest + +from restate.server import Endpoint + + +@pytest.fixture(scope="session") +def anyio_backend(): + return "asyncio" + + +pytestmark = [pytest.mark.anyio] + + +async def test_signal_handler_rejection_does_not_fail_request(monkeypatch: pytest.MonkeyPatch): + loop = asyncio.get_running_loop() + monkeypatch.setattr( + loop, + "add_signal_handler", + Mock(side_effect=ValueError("add_signal_handler() can only be called from the main thread")), + ) + + app = Endpoint().app() + sent = [] + + async def receive(): + return {"type": "http.request", "body": b"", "more_body": False} + + async def send(message): + sent.append(message) + + await app( + { + "type": "http", + "asgi": {"version": "3.0"}, + "http_version": "1.1", + "method": "GET", + "scheme": "http", + "path": "/restate/health", + "raw_path": b"/restate/health", + "query_string": b"", + "headers": [], + "client": ("127.0.0.1", 1234), + "server": ("127.0.0.1", 9080), + }, + receive, + send, + ) + + assert {message.get("status") for message in sent} == {200} From 6d1a8b42f8dff70b3fc46c955088285ac4a5965b Mon Sep 17 00:00:00 2001 From: Julian Bright Date: Wed, 22 Jul 2026 19:42:02 -0700 Subject: [PATCH 2/4] fix signal handler exception indentation --- python/restate/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/restate/server.py b/python/restate/server.py index 219e705..195ea5f 100644 --- a/python/restate/server.py +++ b/python/restate/server.py @@ -228,7 +228,7 @@ async def app(scope: Scope, receive: Receive, send: Send): loop = asyncio.get_running_loop() try: loop.add_signal_handler(signal.SIGTERM, _on_sigterm) - except (NotImplementedError, RuntimeError, ValueError): + except (NotImplementedError, RuntimeError, ValueError): pass # Windows or non-main thread sigterm_installed = True try: From 1892014dc63444a6e1c29b64470e4436e8af3121 Mon Sep 17 00:00:00 2001 From: Julian Bright Date: Wed, 22 Jul 2026 19:44:17 -0700 Subject: [PATCH 3/4] use public Endpoint import in test --- tests/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/server.py b/tests/server.py index a83f167..d908a3d 100644 --- a/tests/server.py +++ b/tests/server.py @@ -13,7 +13,7 @@ import pytest -from restate.server import Endpoint +from restate.endpoint import Endpoint @pytest.fixture(scope="session") From 860badc97c2bc431c0feefdc904c726e71d787e0 Mon Sep 17 00:00:00 2001 From: Julian Bright Date: Wed, 22 Jul 2026 19:49:19 -0700 Subject: [PATCH 4/4] assert ASGI response start status --- tests/server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/server.py b/tests/server.py index d908a3d..83bd314 100644 --- a/tests/server.py +++ b/tests/server.py @@ -59,4 +59,5 @@ async def send(message): send, ) - assert {message.get("status") for message in sent} == {200} + response_starts = [message for message in sent if message["type"] == "http.response.start"] + assert [message["status"] for message in response_starts] == [200]