# launcher Application lifecycle manager for Go services. | | | |---|---| | **Module** | `code.nochebuena.dev/go/launcher` | | **Tier** | 2 — depends on `logz` | | **Go** | 1.25 | | **Dependencies** | `code.nochebuena.dev/go/logz` | ## Overview `launcher` orchestrates a repeatable lifecycle sequence — init, assemble, start, wait, shutdown — across any number of registered infrastructure components. It does **not** manage dependency injection, provide a service locator, or define what your components are; it only drives them through a consistent set of phases. ## Installation ```sh go get code.nochebuena.dev/go/launcher ``` ## Quick start ```go logger := logz.New(logz.Options{}) lc := launcher.New(logger) lc.Append(db, cache, server) lc.BeforeStart(func() error { return server.RegisterRoutes(db, cache) }) if err := lc.Run(); err != nil { logger.Error("launcher failed", err) os.Exit(1) } ``` ## Usage ### Implementing `Component` Any infrastructure piece implements the three-method interface: ```go type Component interface { OnInit() error // open connections, allocate resources OnStart() error // start goroutines, begin serving OnStop() error // stop and release all resources } ``` ### Lifecycle sequence ``` OnInit (all components, in registration order) BeforeStart hooks (in registration order) OnStart (all components, in registration order) ── application is running ── OnStop (all components, in reverse registration order) ``` `Run` blocks at the "application is running" stage until SIGINT/SIGTERM is received or `Shutdown` is called. If any `OnInit`, hook, or `OnStart` returns an error, `Run` returns that error immediately (triggering `stopAll` for `OnStart` failures). ### `BeforeStart` hooks Hooks run after all `OnInit` calls and before any `OnStart` call. Use them to wire dependencies that require all components to be initialized first: ```go lc.BeforeStart(func() error { // db and cache are both initialized at this point return server.RegisterRoutes(db, cache) }) ``` ### Programmatic shutdown `Shutdown` triggers graceful shutdown and waits for `Run` to return. Use it in tests or when your application needs to shut down without an OS signal: ```go ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := lc.Shutdown(ctx); err != nil { // ctx expired before Run returned } ``` `Shutdown` is idempotent — safe to call multiple times. ### Customising the stop timeout ```go lc := launcher.New(logger, launcher.Options{ ComponentStopTimeout: 5 * time.Second, }) ``` The default is 15 seconds per component. The timeout controls how long `stopAll` waits for each individual `OnStop` call; it is independent of the `ctx` passed to `Shutdown`. ## Design decisions - **`Run()` returns `error` instead of calling `os.Exit`.** The original implementation called `os.Exit(1)` directly, making the package untestable. Returning the error lets the caller (typically `main`) decide what to do. - **`shutdownCh` + `doneCh` channel pair.** `shutdownCh` is closed by `Shutdown` to unblock `Run`'s wait loop. `doneCh` is closed by `Run` (via `defer`) before it returns. This guarantees that `Shutdown` unblocks even when `Run` returns early due to an error in `OnInit` or a hook. - **Components stopped in reverse registration order.** This mirrors the LIFO teardown convention: the last component registered (typically the outermost layer, e.g. HTTP server) is stopped first, allowing inner layers (e.g. database) to remain available until no longer needed. - **Per-component timeout, not a global one.** Each `OnStop` gets its own independent timeout window. A single slow component does not consume the budget for all others. ## Ecosystem ``` Tier 0 xerrors rbac Tier 1 logz valid Tier 2 ▶ launcher Tier 3 postgres mysql sqlite valkey firebase httpclient worker Tier 4 httputil httpmw httpauth-firebase httpserver Tier 5 telemetry ``` `launcher` is the lifecycle backbone of the framework. Every Tier 3+ module implements `launcher.Component`; `Run()` drives them all through init → start → stop. ## License MIT