Go HTTP server conventions: the lifecycle example drops the exit code, double-handles signals, and exits from a goroutine #86

Closed
opened 2026-10-04 04:59:11 +02:00 by clawbot · 2 comments
Collaborator

The server lifecycle example in prompts/GO_HTTP_SERVER_CONVENTIONS.md has three defects that a repo copying it inherits. pixa had all three and fixed them in sneak/pixa#169 (for sneak/pixa#86).

  1. serve() computes an exit code, but nothing passes it to os.Exit: the process exits 0 even when it stopped because of an error, so a supervisor sees a crash as a clean stop.
  2. serve() installs its own SIGINT/SIGTERM handler while the fx app's Run() installs another. Both fire on one signal and race to shut down.
  3. When Sentry cannot start, the example calls os.Exit(1) from a goroutine, so no fx stop hook runs (the database and work in progress are abandoned).

The shape pixa now uses: main calls Run() on the fx app and lets fx own signals; a listen error asks fx to shut down with exit code 1 (fx.Shutdowner with fx.ExitCode(1)); the server's stop hook shuts the HTTP server down within its time limit and fails if work is unfinished; a Sentry start failure is returned from the start hook, so fx reports it and exits 1. fx's Run() exits with the code the shutdown carried.

Done when the example no longer has these three defects, and the conventions describe how exit codes and signals are owned.

Model: opus-5-5

The server lifecycle example in `prompts/GO_HTTP_SERVER_CONVENTIONS.md` has three defects that a repo copying it inherits. pixa had all three and fixed them in https://git.eeqj.de/sneak/pixa/pulls/169 (for https://git.eeqj.de/sneak/pixa/issues/86). 1. `serve()` computes an exit code, but nothing passes it to `os.Exit`: the process exits 0 even when it stopped because of an error, so a supervisor sees a crash as a clean stop. 2. `serve()` installs its own SIGINT/SIGTERM handler while the fx app's `Run()` installs another. Both fire on one signal and race to shut down. 3. When Sentry cannot start, the example calls `os.Exit(1)` from a goroutine, so no fx stop hook runs (the database and work in progress are abandoned). The shape pixa now uses: `main` calls `Run()` on the fx app and lets fx own signals; a listen error asks fx to shut down with exit code 1 (`fx.Shutdowner` with `fx.ExitCode(1)`); the server's stop hook shuts the HTTP server down within its time limit and fails if work is unfinished; a Sentry start failure is returned from the start hook, so fx reports it and exits 1. fx's `Run()` exits with the code the shutdown carried. Done when the example no longer has these three defects, and the conventions describe how exit codes and signals are owned. Model: opus-5-5
Author
Collaborator

Plan. Rewrite the lifecycle example in prompts/GO_HTTP_SERVER_CONVENTIONS.md to the shape above: main calls Run() on the fx app and fx owns signals; a listen error shuts fx down with fx.ExitCode(1) through fx.Shutdowner; the stop hook shuts the HTTP server down within its time limit; a Sentry start failure is returned from the start hook. Drop the example's own signal.Notify, its exitCode field and the os.Exit in enableSentry(). Fix every other place in the document, and in prompts/CODE_STYLEGUIDE_GO.md if it restates the lifecycle, and add one short paragraph saying who owns signals and the exit code.

Done when a throwaway module built from the example as written exits 0 on SIGTERM, exits 1 after its stop hooks run on a listen error (port taken) and on a Sentry start failure (invalid DSN), and make check passes.

Model: opus-5-5

Plan. Rewrite the lifecycle example in `prompts/GO_HTTP_SERVER_CONVENTIONS.md` to the shape above: `main` calls `Run()` on the fx app and fx owns signals; a listen error shuts fx down with `fx.ExitCode(1)` through `fx.Shutdowner`; the stop hook shuts the HTTP server down within its time limit; a Sentry start failure is returned from the start hook. Drop the example's own `signal.Notify`, its `exitCode` field and the `os.Exit` in `enableSentry()`. Fix every other place in the document, and in `prompts/CODE_STYLEGUIDE_GO.md` if it restates the lifecycle, and add one short paragraph saying who owns signals and the exit code. Done when a throwaway module built from the example as written exits 0 on SIGTERM, exits 1 after its stop hooks run on a listen error (port taken) and on a Sentry start failure (invalid DSN), and `make check` passes. Model: opus-5-5
clawbot self-assigned this 2026-10-04 08:49:37 +02:00
Author
Collaborator

Done in #94: the server example now lets fx own SIGINT, SIGTERM and the exit code, a listen error shuts fx down with exit code 1, a Sentry start failure is returned from the start hook, and the stop hook fails when requests outlive its 5 seconds. A new paragraph says who owns signals and the exit code.

Model: opus-5-5

Done in https://git.eeqj.de/sneak/prompts/pulls/94: the server example now lets fx own SIGINT, SIGTERM and the exit code, a listen error shuts fx down with exit code 1, a Sentry start failure is returned from the start hook, and the stop hook fails when requests outlive its 5 seconds. A new paragraph says who owns signals and the exit code. Model: opus-5-5
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/prompts#86