hibiken/asynq · warning
asynq: Server closed
Error message
asynq: Server closed
What it means
ErrServerClosed is a sentinel error meaning an operation is illegal because the server has already been shut down. The server returns it from lifecycle calls when its internal state is srvStateClosed. It is analogous to net/http's ErrServerClosed: seeing it after a deliberate shutdown is normal, not a bug.
Solutions
- Check errors.Is(err, asynq.ErrServerClosed) and treat it as a normal shutdown signal rather than a failure
- Create a new Server instance (NewServer) instead of reusing a closed one
- Fix lifecycle orchestration so Run/Start is only called once, e.g. with a sync.Once or by signaling the goroutine to exit
Example fix
// before
if err := srv.Run(handler); err != nil {
log.Fatal(err)
}
// after
if err := srv.Run(handler); err != nil && !errors.Is(err, asynq.ErrServerClosed) {
log.Fatal(err)
} Defensive patterns
Strategy: try-catch
Try / catch
if err := srv.Run(mux); err != nil && !errors.Is(err, asynq.ErrServerClosed) {
log.Fatalf("asynq server: %v", err)
} Prevention
- Treat ErrServerClosed as expected after Shutdown and never restart a closed Server instance
- Create a fresh NewServer for any restart need
- Guard Run/Start with sync.Once in code paths that could race
When it happens
Trigger: Calling Run/Start (or other lifecycle operations) after srv.Shutdown() has completed; the state machine maps srvStateClosed to ErrServerClosed at server.go:714.
Common situations: Double-invocation of Run (e.g. in tests or re-exec paths after graceful shutdown); attempting to restart a closed server instead of constructing a NewServer; background goroutines still calling Start after a signal-triggered shutdown.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- asynq: the server is already running
- asynq: the server is in the stopped state. Waiting for…
- task id conflicts with another task
- testutil: redis is down
- skip retry for the task
AI-assisted analysis of hibiken/asynq@d135f1439b (2026-09-07).
Data as JSON: /api/errors/bc5284912c96a56a.
Report an issue: GitHub.
Appendix: source
Thrown at server.go:654
// If the returned error is RevokeTask or an error wraps RevokeTask, the task
// will not be retried or archived.
type Handler interface {
ProcessTask(context.Context, *Task) error
}
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as a Handler. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(context.Context, *Task) error
// ProcessTask calls fn(ctx, task)
func (fn HandlerFunc) ProcessTask(ctx context.Context, task *Task) error {
return fn(ctx, task)
}
// ErrServerClosed indicates that the operation is now illegal because of the server has been shutdown.
var ErrServerClosed = errors.New("asynq: Server closed")
// Run starts the task processing and blocks until
// an os signal to exit the program is received. Once it receives
// a signal, it gracefully shuts down all active workers and other
// goroutines to process the tasks.
//
// Run returns any error encountered at server startup time.
// If the server has already been shutdown, ErrServerClosed is returned.
func (srv *Server) Run(handler Handler) error {
if err := srv.Start(handler); err != nil {
return err
}
srv.waitForSignals()
srv.Shutdown()
return nil
}
// Start starts the worker server. Once the server has started,View on GitHub (pinned to d135f1439b)