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

  1. Check errors.Is(err, asynq.ErrServerClosed) and treat it as a normal shutdown signal rather than a failure
  2. Create a new Server instance (NewServer) instead of reusing a closed one
  3. 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

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


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)