Repository navigation
[lifecycle] No graceful shutdown, no lifecycle hooks, and run() hides the bound address #82
Description
Problem
gotcha has no shutdown story and no lifecycle hooks, and run() keeps the bound address to itself.
No graceful shutdown. GotchaApp::run ends with a bare
axum::serve(listener, router).await.map_err(GotchaError::Io)?;(gotcha-0.4.1/src/lib.rs:237; the builder does the same at gotcha-0.4.1/src/builder.rs:694). No with_graceful_shutdown, no signal handling, and — the part that actually blocks a workaround — no hook to attach either one to. A SIGTERM from a container runtime kills the process mid-request. Nothing tells long-lived connections to close: an application holding websockets, PTYs and child processes has no moment at which to tear them down, so they are simply reaped by the OS.
run() swallows the address. run binds config.server.host:config.server.port internally (lib.rs:231-236) and never surfaces the resulting SocketAddr. Port 0 is therefore unusable — which is what an integration test wants (bind an ephemeral port, then talk to it), and what an in-process spec export wants. Gotcha::listen_on takes an address but likewise returns nothing until the server stops.
Where
gotcha-0.4.1/src/lib.rs:211-239—GotchaApp::run, bareaxum::serveat:237gotcha-0.4.1/src/builder.rs:670-696—Gotcha::listen_on, same shape at:694
How remote-hand works around it
server-rs/src/app.rs does not call run at all. It reimplements it: load config, build state, call the public GotchaApp::build_router (public is the key — into_axum_router is pub(crate)), bind the listener itself, and return a Prepared { addr, listener, router, cancel, cleanups }. serve_until_signalled then drives axum::serve(..).with_graceful_shutdown(..).
server-rs/src/shutdown.rs supplies what the framework does not: a SIGINT/SIGTERM future, a CancellationToken kept in application state so a live websocket observes the shutdown at the same instant the listener stops accepting, and an ordered Cleanups teardown of feeds, terminals, code-server apps and the global watcher.
Splitting prepare() out of run() is also what lets export-openapi and the integration tests bind port 0 and learn the real address.
Suggested direction
GotchaApp::run/Gotcha::listen_onshould call.with_graceful_shutdown(..)with a default SIGINT+SIGTERM future, and offer ashutdown_signal()hook to override it.- Add an
on_shutdownhook (or hand the application aCancellationTokenthroughGotchaContext) so subsystems can be torn down in order. - Split binding from serving: a public
prepare()/bind()returning theSocketAddrbefore the serve loop starts, so port 0 works and tests can address the server.
Progress in PR #112
PR #112 adds shared shutdown_signal hooks to the builder and trait APIs, default Ctrl-C / Unix SIGTERM handling, and Axum graceful HTTP shutdown. Scheduled tasks use the same cancellation signal and have a configurable grace period independent of HTTP draining.
This issue remains open for the public prepare/address API, application cleanup hooks or context-level cancellation integration, and bounded HTTP draining. The task ownership work is tracked and closed separately by #93.
Activity