{"record":{"id":"bc5284912c96a56a","repo":"hibiken/asynq","slug":"asynq-server-closed","errorCode":null,"errorMessage":"asynq: Server closed","messagePattern":"asynq: Server closed","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server.go","lineNumber":654,"sourceCode":"// If the returned error is RevokeTask or an error wraps RevokeTask, the task\n// will not be retried or archived.\ntype Handler interface {\n\tProcessTask(context.Context, *Task) error\n}\n\n// The HandlerFunc type is an adapter to allow the use of\n// ordinary functions as a Handler. If f is a function\n// with the appropriate signature, HandlerFunc(f) is a\n// Handler that calls f.\ntype HandlerFunc func(context.Context, *Task) error\n\n// ProcessTask calls fn(ctx, task)\nfunc (fn HandlerFunc) ProcessTask(ctx context.Context, task *Task) error {\n\treturn fn(ctx, task)\n}\n\n// ErrServerClosed indicates that the operation is now illegal because of the server has been shutdown.\nvar ErrServerClosed = errors.New(\"asynq: Server closed\")\n\n// Run starts the task processing and blocks until\n// an os signal to exit the program is received. Once it receives\n// a signal, it gracefully shuts down all active workers and other\n// goroutines to process the tasks.\n//\n// Run returns any error encountered at server startup time.\n// If the server has already been shutdown, ErrServerClosed is returned.\nfunc (srv *Server) Run(handler Handler) error {\n\tif err := srv.Start(handler); err != nil {\n\t\treturn err\n\t}\n\tsrv.waitForSignals()\n\tsrv.Shutdown()\n\treturn nil\n}\n\n// Start starts the worker server. Once the server has started,","sourceCodeStart":636,"sourceCodeEnd":672,"githubUrl":"https://github.com/hibiken/asynq/blob/d135f1439bee74e989b7f9b41ecd542cc87f024a/server.go#L636-L672","documentation":"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.","triggerScenarios":"Calling Run/Start (or other lifecycle operations) after srv.Shutdown() has completed; the state machine maps srvStateClosed to ErrServerClosed at server.go:714.","commonSituations":"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.","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"],"exampleFix":"// before\nif err := srv.Run(handler); err != nil {\n    log.Fatal(err)\n}\n// after\nif err := srv.Run(handler); err != nil && !errors.Is(err, asynq.ErrServerClosed) {\n    log.Fatal(err)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := srv.Run(mux); err != nil && !errors.Is(err, asynq.ErrServerClosed) {\n    log.Fatalf(\"asynq server: %v\", err)\n}","preventionTips":["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"],"tags":["go","lifecycle","shutdown"],"backgroundTag":"invalid-state-transition","analyzedSha":"d135f1439bee74e989b7f9b41ecd542cc87f024a","analyzedAt":"2026-09-07T19:02:34.660Z","contentChangedAt":"2026-09-07T19:02:34.660Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}