graceful package - github.com/libtnb/graceful - Go Packages

graceful

package module
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 31, 2026 License: MIT Imports: 12 Imported by: 0

README

graceful

Doc Go Release Test Report Card Stars License

Lifecycle orchestration for long-running Go components: start everything together, wait for the context to be cancelled or the first failure, drain everything in reverse order — with optional zero-downtime binary upgrades on SIGHUP via tableflip.

Features

  • Any component, not just HTTP. Add(name, start, stop) takes a pair of functions: start may block for the component's whole life (an accept loop) or return immediately after spawning its own work (a scheduler). A non-nil error from any start shuts the whole group down.
  • Listeners built for upgrades. Listen(name, addr, srv) creates the listener inside Run — through tableflip when upgrades are enabled — so an upgraded process inherits the socket without dropping connections. *http.Server satisfies the Server interface directly.
  • One shutdown story. A cancelled context, a component failure, or an upgrade handoff all funnel into the same drain: every started component is stopped in reverse registration order, under one shared deadline, and drain failures come back joined onto the returned error.
  • Zero-downtime upgrades. WithUpgrade() makes SIGHUP re-exec the binary and hand off listeners; it silently falls back to plain listeners on Windows.
  • Structured logging of every lifecycle event through log/slog.

Install

go get github.com/libtnb/graceful

Requires Go 1.27+.

Quick start

package main

import (
	"context"
	"net/http"
	"os/signal"
	"syscall"
	"time"

	"github.com/libtnb/graceful"
)

func main() {
	ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
	defer stop()

	srv := &http.Server{Handler: mux}

	g := graceful.New(
		graceful.WithUpgrade(),                      // SIGHUP = hot upgrade
		graceful.WithShutdownTimeout(30*time.Second),
	)
	g.Add("cron", cron.Start, cron.Stop)             // any start/stop pair
	g.Listen("http", ":8080", srv)                   // upgrade-aware listener

	if err := g.Run(ctx); err != nil {               // blocks until shutdown
		log.Fatal(err)
	}
}

Run starts entries in registration order and drains them in reverse: the HTTP listener above stops accepting before the scheduler is asked to finish, so in-flight requests can still schedule work.

Trigger Behavior
ctx cancelled (e.g. SIGINT/SIGTERM via signal.NotifyContext) stop accepting, drain every component, return nil
a start returns non-nil drain every component, return name: err
SIGHUP (with WithUpgrade) re-exec the binary, hand listeners to the child, drain, return nil

Design notes

  • start errors are fatal, stop errors are collected. A component that cannot run means the process is broken — everything comes down. A component that cannot stop cleanly must not block the rest from draining, so its error is logged and joined onto Run's return value instead.
  • A scheduler-style start that returns nil is a successful launch, not a failure — only non-nil errors trigger shutdown. This makes Add fit both blocking accept loops and fire-and-forget starters without adapters.
  • Registration order is the dependency order. Register infrastructure first, entry points last; reverse-order draining then closes the front door before the back office.
  • The caller owns shutdown, the group owns SIGHUP. Shutdown arrives through the context (signal.NotifyContext in main), so the group composes with any cancellation source and tests need no real signals; SIGHUP stays internal because upgrades are the group's own feature.
  • The group owns lifecycles, not resources. Database pools, log writers and the like belong to whatever built them (a DI container's cleanup); the group only coordinates starting and stopping.

License

MIT

Documentation

Overview

Package graceful orchestrates the lifecycle of long-running components. A Group starts every registered component together, waits for the context to be cancelled or the first failure, then drains everything in reverse registration order within a bounded timeout. With WithUpgrade a SIGHUP performs a zero-downtime binary upgrade via tableflip: listeners created through Listen are inherited by the new process.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Group

type Group struct {
	// contains filtered or unexported fields
}

Group runs components together. Register with Add and Listen, then call Run once; Group is not safe for concurrent registration.

func New

func New(opts ...Option) *Group

func (*Group) Add

func (g *Group) Add(name string, start func() error, stop func(ctx context.Context) error)

Add registers a component. start runs in a goroutine: it may block for the component's whole life (an accept loop) or return nil after spawning its own work (a scheduler); a non-nil error shuts the whole group down. stop runs during shutdown in reverse registration order, sharing the group's shutdown timeout.

func (*Group) Listen

func (g *Group) Listen(name, addr string, srv Server)

Listen registers srv to serve on a TCP listener bound to addr. The listener is created by Run — through tableflip under WithUpgrade, so upgraded processes inherit it. http.ErrServerClosed is a clean exit.

func (*Group) Run

func (g *Group) Run(ctx context.Context) error

Run starts every entry in registration order, then blocks until ctx is cancelled, a component fails, or an upgraded process takes over. It drains the started entries in reverse order and returns the failure that caused the shutdown joined with any drain failures; a requested shutdown that drains cleanly returns nil. Call it once.

type Option

type Option func(*options)

Option configures a Group.

func WithLogger

func WithLogger(log *slog.Logger) Option

WithLogger sets the logger for lifecycle events. Default slog.Default().

func WithShutdownTimeout

func WithShutdownTimeout(d time.Duration) Option

WithShutdownTimeout bounds the total drain time on shutdown. Default 30s.

func WithUpgrade

func WithUpgrade() Option

WithUpgrade enables zero-downtime binary upgrades on SIGHUP via tableflip. It is a no-op on Windows, where the group falls back to plain listeners.

type Server

type Server interface {
	Serve(ln net.Listener) error
	Shutdown(ctx context.Context) error
}

Server is the accepting half of an HTTP(ish) server: Serve blocks on the listener until Shutdown drains it. *http.Server satisfies it directly.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL