Repository navigation
Add restate.serve: serve an app over HTTP/2 - #266
Conversation
…te.connect
asyncio.run(restate.serve(app))
serves on $PORT, or 9080, with the cleartext HTTP/2 Restate connects
with (uvicorn 0.53+ with zttp), and stops within a few seconds of SIGINT
or SIGTERM. It needs the new serve extra (Python 3.11+); without it,
it raises an ImportError naming the extra.
The app installs its SIGTERM hook on its first request through
install_shutdown_hook, which keeps one asyncio handler per signal and
would replace uvicorn's; so serve registers the server's exit through
the same hook first, and uvicorn captures no signals itself.
The README serves its example with restate.serve, and keeps hypercorn
as the alternative (which also works on Python 3.10).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
ℹ️ No critical issues — three small rough edges inline.
Reviewed changes
Reviewed the full PR: a new restate.serve entrypoint on uvicorn+zttp HTTP/2, a serve extra, README updates, and tests. I also checked it locally with uvicorn 0.54 and zttp 0.0.35 on Python 3.12. /discover is served over HTTP/2, and both SIGTERM and SIGINT stop the process in about 0.25s while an h2 connection is still open. The shared-hook ordering (server exit first, then the app's _on_sigterm) works as the comment describes.
restate.serve— lazily imports uvicorn/zttp, checks for uvicorn ≥ 0.53, and serves withhttp="zttp",http2=True,lifespan="off"and a 3s graceful shutdown.- Signal handling — sends uvicorn's
handle_exitthroughinstall_shutdown_hook, so it shares one asyncio dispatcher with the app's SIGTERM hook, and turns off uvicorn's owncapture_signals. serveextra —uvicorn>=0.53andzttp>=0.0.34, both limited to Python 3.11+.uv.locknow uses uvicorn 0.38 below 3.11 and 0.54 from 3.11 on.- README — the example now uses
restate.serve, with hypercorn still listed as the alternative (including for 3.10). - Tests — a subprocess check of the missing-extra
ImportError, plus a Docker end-to-end test: register, checkHTTP/2.0, invoke, then SIGTERM.
claude-opus-5-5 | 𝕏
…ort=0; stop under load - serve only turns off uvicorn's own signal capture once its shutdown hooks are installed: on Windows (no add_signal_handler) uvicorn keeps handling Ctrl+C itself. - An explicit port=0 (one the OS picks) is no longer taken for unset. - The end-to-end test sends SIGTERM while Restate still holds its HTTP/2 connections open, and stops Restate only after the program exited. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
✅ No new issues found. All three earlier findings are fixed.
Reviewed changes
Reviewed bcbdb31, the follow-up commit since the last Pullfrog review.
- Signal fallback —
server.capture_signalsis now replaced only after bothinstall_shutdown_hookcalls succeed. On Windows or in a non-main thread, the firstadd_signal_handlercall raises, so uvicorn keeps its own signal handling and no hook is left half-installed. port=0— an explicit0now reaches uvicorn as an OS-assigned port instead of falling back to$PORT/9080. The docstring now says this.- Shutdown test — SIGTERM is now sent while Restate still holds its HTTP/2 connections. The test waits up to 10s for the program to exit and only then stops the container in
finally, sodocker stoptime doesn't count against the budget.
claude-opus-5-5 | 𝕏
… wait until it listens The program's port was chosen before the Restate container started, and Docker could map one of Restate's ports onto it before the program bound it. Now the container starts first, the test waits until the program accepts connections before registering it, and a failure shows what the program printed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… port, and wait until it listens As in tests/serve.py (#266): Docker could map one of the Restate container's ports onto the port chosen for the example before the example bound it. The container now starts first, the test waits until the example accepts connections, and SIGTERM comes while Restate still holds its connections open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
✅ No new issues found. The new commit makes the end-to-end test steadier and easier to debug when it fails.
Reviewed changes
Reviewed 97fea5e, a test-only commit to tests/serve.py since the last Pullfrog review.
- Container-first ordering —
RestateContainernow starts beforefind_free_port(). Docker's host-port mappings are then already taken and can't collide with the program's port. - Readiness wait — a new
listening()helper polls a TCP connect on127.0.0.1:{port}(up to about 10s) and fails early with the program's output if the program exits. This replaces the 100-try registration retry loop, so registration now happens once against a server that is already listening. - Program output capture — stdout and stderr go to
tmp_path/program.logand are attached to the readiness and shutdown assertion messages. Before, they were sent toDEVNULL.
claude-opus-5-5 | 𝕏

restate.serveA one-line way to serve a Restate app, as the counterpart of
restate.connect:async def serve(app, *, host="0.0.0.0", port=None)listens with the cleartext HTTP/2 that Restate connects with, on uvicorn 0.53+ withzttp. It stops within a few seconds of SIGINT or SIGTERM.serveextra:pip install "restate-sdk[serve]"brings uvicorn and zttp on Python 3.11+. Without the extra,serveraises anImportErrornaming it, and ordinary installs get no new dependencies.install_shutdown_hook. That keeps one asyncio handler per signal and would replace uvicorn's handlers, soserveregisters the server's exit through the same hook first and turns off uvicorn's own signal capture. The server and the app's hook then both run.restate.serve; hypercorn stays as the alternative, including for Python 3.10.Tests
tests/serve.py:serveraises anImportErrornaming it (checked in a subprocess with uvicorn hidden).restate.serve:HTTP/2.0for the deployment;#262 (session objects and agents) will use this in place of its own
sessions.serve.🤖 Generated with Claude Code