Documentation
¶
Overview ¶
Package graceful provides utilities for running long-lived processes with predictable, well-behaved shutdown semantics. It wraps a user-provided function with signal handling, context cancellation, timeouts, and standardized exit codes.
On the first SIGINT/SIGTERM, the context passed to the run function is canceled, giving the process an opportunity to shut down cleanly. A second signal forces an immediate exit. Optional timeouts bound both the maximum run duration (WithRunTimeout) and the total shutdown period (WithTerminationTimeout). For scenarios requiring immediate termination on the first signal, use WithImmediateTermination to bypass the graceful shutdown phase.
Exit codes:
- 0: successful completion
- 1: run function returned an error
- 124: shutdown timeout exceeded
- 130: forced shutdown (second signal or immediate termination)
Example: HTTP server
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
graceful.Run(
graceful.ListenAndServe(server, 15*time.Second), // HTTP draining period
graceful.WithTerminationTimeout(30*time.Second), // overall shutdown limit
)
Example: batch job with a hard deadline
graceful.Run(func(ctx context.Context) error {
return processBatch(ctx)
}, graceful.WithRunTimeout(1*time.Hour))
Example: worker with both limits
graceful.Run(func(ctx context.Context) error {
return runWorker(ctx)
},
graceful.WithRunTimeout(24*time.Hour),
graceful.WithTerminationTimeout(30*time.Second),
)
Example: immediate termination on first signal
graceful.Run(func(ctx context.Context) error {
return runTask(ctx)
}, graceful.WithImmediateTermination())
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ListenAndServe ¶
ListenAndServe runs an *http.Server under the lifecycle managed by graceful.Run. It starts the server, waits for ctx cancellation (SIGINT/SIGTERM), and then performs a graceful shutdown using http.Server.Shutdown.
Shutdown behavior follows standard net/http semantics:
- new connections are refused once shutdown begins
- in-flight requests are allowed to finish normally
- shutdownGrace bounds how long the server waits for draining
ListenAndServe does not propagate the initial shutdown signal into handler contexts. Requests are only cancelled if the client disconnects or if shutdownGrace expires. This matches typical production environments and avoids mid-request interruptions.
Two timeouts are involved:
- shutdownGrace: how long the HTTP server may drain connections
- graceful.WithTerminationTimeout: the total process shutdown budget
Example:
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
graceful.Run(
graceful.ListenAndServe(server, 15*time.Second), // server draining period
graceful.WithTerminationTimeout(25*time.Second), // total shutdown limit
)
Types ¶
type Option ¶
type Option func(*config)
Option configures the Handle function.
func WithImmediateTermination ¶
func WithImmediateTermination() Option
WithImmediateTermination configures the process to exit immediately on the first interrupt signal, without waiting for a second signal. By default, graceful shutdown allows a second Ctrl+C to force immediate termination. This option disables that behavior.
When enabled, the first SIGINT/SIGTERM will cause the process to exit with code 130 immediately, without waiting for the run function to complete gracefully.
Example:
graceful.Run(fn, graceful.WithImmediateTermination())
func WithLogger ¶
WithLogger sets an optional slog.Logger for structured logging. When provided, the logger is used instead of fmt.Fprintln to stderr for all messages (shutdown notifications, errors, etc.).
To disable all logging output, pass a logger with a discard handler:
graceful.Run(fn, graceful.WithLogger(slog.New(slog.DiscardHandler)))
func WithRunTimeout ¶
WithRunTimeout sets the maximum time the run function may execute. When the timeout expires, the context passed to the run function is canceled. If the function does not exit on cancellation, it will eventually be stopped by the termination timeout or a second interrupt signal.
A zero or negative duration means no limit.
Example:
graceful.Run(processBatch, graceful.WithRunTimeout(1*time.Hour))
func WithStderr ¶
WithStderr sets the writer for error output. Defaults to os.Stderr if not specified. If a logger is configured via WithLogger, the logger takes precedence over stderr for messages.
func WithTerminationTimeout ¶
WithTerminationTimeout sets the maximum time the process may spend shutting down after the first interrupt signal. If this timeout expires, the process exits with code 124.
This bounds the total shutdown phase (server draining, cleanup, background work). A zero or negative duration means no limit.
Example:
graceful.Run(fn, graceful.WithTerminationTimeout(30*time.Second))