| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
lane manages the startup, health, and graceful shutdown of one or more service components — HTTP servers, background workers, schedulers — under a unified context. Register your runners, call Run, and lane handles the rest.
Wiring SIGINT/SIGTERM handling, concurrent startup, health probes, and ordered shutdown correctly is tedious boilerplate. lane does it once, predictably, so services stay focused on their own logic.
Dependency wiring is the caller's responsibility.
package main
import (
"context"
"log/slog"
"net/http"
"os"
"time"
"github.com/rluders/lane"
"github.com/rluders/lane/runners"
)
func main() {
lane.RunHealthCheck(":8080")
log := slog.New(slog.NewTextHandler(os.Stderr, nil))
mux := http.NewServeMux()
l := lane.New(log, lane.WithShutdownTimeout(10*time.Second))
mux.Handle("GET /ready", lane.ReadinessHandler(l.Health()))
mux.Handle("GET /live", lane.LivenessHandler())
server := &http.Server{Addr: ":8080", Handler: mux}
l.AddRunner(runners.NewHTTPRunner("api", server, log))
if err := l.Run(context.Background()); err != nil {
log.Error("lane error", "error", err)
os.Exit(1)
}
}flowchart TD
A[lane.New] --> B[AddRunner x N]
B --> C[l.Run]
C --> D[Start all runners concurrently]
D --> E[health.SetReady true]
E --> F{waiting}
F -- SIGINT/SIGTERM --> G[Signal received]
F -- runner error --> H[Context cancelled]
G --> I[health.SetReady false]
H --> I
I --> J[Stop runners in LIFO order]
J --> K[Shutdown timeout context]
K --> L[Return first error or nil]
Runners are started concurrently. Shutdown is sequential in reverse registration order (LIFO), so dependents stop before the services they depend on.
Any component that can start and stop is a Runner:
type Runner interface {
Name() string
Start(ctx context.Context) error
Stop(ctx context.Context) error
}Contract:
All implementations live in the runners/ sub-package.
| Runner | Constructor | Description |
|---|---|---|
| HTTP | runners.NewHTTPRunner(name, server, log) | Wraps *http.Server. Calls Shutdown on stop. |
| HTTPS | runners.NewHTTPSRunner(name, server, certFile, keyFile, log) | Same as HTTPRunner with TLS. |
| Worker | runners.NewWorkerRunner(name, fn, log) | Runs a WorkFn in a loop until ctx is cancelled. |
| Scheduler | runners.NewSchedulerRunner(name, interval, fn, log) | Runs a JobFn on a fixed interval. Missed ticks are dropped. |
worker := runners.NewWorkerRunner("processor", func(ctx context.Context) error {
return processNextMessage(ctx)
}, log)
l.AddRunner(worker)sched := runners.NewSchedulerRunner("cleanup", 5*time.Minute, func(ctx context.Context) error {
return purgeExpiredSessions(ctx)
}, log)
l.AddRunner(sched)HealthState tracks readiness. Lane sets it to ready after all runners start, and back to not-ready at the beginning of shutdown.
mux.Handle("GET /ready", lane.ReadinessHandler(l.Health(), db.PingContext))
mux.Handle("GET /live", lane.LivenessHandler())ReadinessHandler accepts zero or more probe functions of type func(ctx context.Context) error. All probes must pass for the endpoint to return 200. If any probe fails, the endpoint returns 503.
LivenessHandler always returns 200 — a running process is a live process.
Call RunHealthCheck at the very top of main() to support Docker HEALTHCHECK CMD-based probes:
func main() {
lane.RunHealthCheck(":8080")
// ... rest of main
}When invoked as myservice healthcheck, the process exits 0 (healthy) or 1 (unhealthy) immediately without starting the service.
lane.Go runs a goroutine with structured panic recovery. On panic it logs the stack trace and calls the provided cancel function to propagate the failure up to lane.
lane.Go(ctx, log, "worker-name", cancel, func(ctx context.Context) {
// critical goroutine — a panic here cancels the application context
})RecoverMiddleware wraps HTTP handlers with panic recovery. On panic it logs structured context (method, path, stack trace) and returns HTTP 500. The service continues — a single handler panic is recoverable.
handler = lane.RecoverMiddleware(log)(handler)Default shutdown timeout is 30 seconds. Override with WithShutdownTimeout:
l := lane.New(log, lane.WithShutdownTimeout(10*time.Second))Each runner's Stop is called with a context that respects this deadline. If a runner does not stop within the timeout, shutdown proceeds without it.
Use it when you want:
Avoid it if you need:
MIT — see LICENSE.
| Back | FazBrowse Home | New Git URL |