grace

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 6 Imported by: 0

README

grace

Coordinate graceful startup and shutdown of concurrent, long-running tasks in Go.

grace runs a set of tasks together under a shared context. When any task fails, or the process receives an interrupt, every task is signaled to stop and grace waits for them all to return before exiting.

A companion httpserver package adapts net/http to this model with sane timeouts and connection draining.

Concepts

A Runner is any long-running task that blocks until its context is canceled or it hits an error which should trigger a graceful shutdown:

type Runner interface {
	Run(ctx context.Context) error
}

grace.Run starts each Runner concurrently on a shared context. It blocks until either the parent context is canceled or any Runner returns a non-nil error, then cancels the shared context so the rest can shut down and waits for all of them to return. A clean, cancellation-triggered shutdown reports success (nil error); otherwise the first real error is returned.

grace.StopOnInterrupt derives a context that is canceled on SIGINT (Ctrl-C) or SIGTERM, giving you a one-line signal handler:

ctx, stop := grace.StopOnInterrupt(context.Background())
defer stop()

Usage

package main

import (
	"context"
	"log/slog"
	"net/http"
	"os"

	"github.com/quells/grace"
	"github.com/quells/grace/pkg/httpserver"
)

func main() {
	ctx, stop := grace.StopOnInterrupt(context.Background())
	defer stop()

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("hello"))
	})

	api := httpserver.New(":8080", handler,
		httpserver.WithLogger(slog.Default()),
		httpserver.WithLoggerLabel("api"),
	)

	// A separate server for pprof, bound to loopback only.
	debug := httpserver.New("127.0.0.1:8888", httpserver.DebugHandler(),
		httpserver.WithLogger(slog.Default()),
		httpserver.WithLoggerLabel("debug"),
	)

	if err := grace.Run(ctx, api, debug); err != nil {
		slog.Error("server error", slog.String("error", err.Error()))
		os.Exit(1)
	}
}

grace.Run blocks until you press Ctrl-C (or send SIGTERM), then drains both servers within their grace periods and returns.

A complete, testable variant lives in examples/httpserver-example.

httpserver

httpserver.New builds an HTTP server that satisfies grace.Runner. On shutdown it stops accepting new connections and waits up to a grace period for in-flight requests to finish before closing.

srv := httpserver.New(addr, handler, opts...)

Defaults: 15s ReadHeaderTimeout, 30s WriteTimeout, and a 5s shutdown grace period. If addr uses port 0, a listener is bound immediately so the assigned port is known before Run is called.

Debug endpoints

httpserver.DebugHandler returns a mux wired to the net/http/pprof endpoints under /debug/pprof/. Serve it on a private, loopback-only address — never expose it publicly.

Endpoints served by DebugHandler can stream for the full profiling duration; give the debug server a longer WithWriteTimeout (e.g. several minutes) so profiles aren't truncated.

Integrating with other long-running task types

The same Runner pattern can be applied to servers of other protocols (gRPC, etc.), task workers (e.g. Temporal workers), and more. Integrating those into the grace.Run lifecycle is just a matter of writing a wrapper implementing the Runner interface and adding it to the list of runners.

License

Released under the MIT License.

Documentation

Overview

Package grace coordinates graceful shutdown of concurrent, long-running tasks.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Run

func Run(ctx context.Context, runners ...Runner) error

Run starts each Runner concurrently and blocks until any of them return a non-nil error OR the parent context is canceled, AND blocks until all of them acknowledge the resulting canceled context.

Returns the first non-nil error which was not a context cancellation.

func StopOnInterrupt

func StopOnInterrupt(ctx context.Context) (context.Context, func())

StopOnInterrupt returns a copy of the context which will be canceled when the process receives a shutdown interrupt. The returned stop function should be called when the context is no longer needed, usually via defer.

Types

type Runner

type Runner interface {
	Run(ctx context.Context) error
}

Runner is a long-running task.

Run should block until its context is canceled or it encounters an error.

Directories

Path Synopsis
examples
pkg
httpserver
Package httpserver provides an HTTP server wrapper with sensible defaults which conforms to grace.Runner
Package httpserver provides an HTTP server wrapper with sensible defaults which conforms to grace.Runner

Jump to

Keyboard shortcuts

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