Skip to content

[lifecycle] No graceful shutdown, no lifecycle hooks, and run() hides the bound address #82

Open

Description

@Kilerd

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, bare axum::serve at :237
  • gotcha-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_on should call .with_graceful_shutdown(..) with a default SIGINT+SIGTERM future, and offer a shutdown_signal() hook to override it.
  • Add an on_shutdown hook (or hand the application a CancellationToken through GotchaContext) so subsystems can be torn down in order.
  • Split binding from serving: a public prepare()/bind() returning the SocketAddr before 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions