{"record":{"id":"d0868f33415c107b","repo":"hibiken/asynq","slug":"asynq-the-scheduler-has-already-been-stopped","errorCode":null,"errorMessage":"asynq: the scheduler has already been stopped","messagePattern":"asynq: the scheduler has already been stopped","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scheduler.go","lineNumber":280,"sourceCode":"\t}\n\ts.logger.Info(\"Scheduler starting\")\n\ts.logger.Infof(\"Scheduler timezone is set to %v\", s.location)\n\ts.cron.Start()\n\ts.wg.Add(1)\n\tgo s.runHeartbeater()\n\treturn nil\n}\n\n// Checks server state and returns an error if pre-condition is not met.\n// Otherwise it sets the server state to active.\nfunc (s *Scheduler) start() error {\n\ts.state.mu.Lock()\n\tdefer s.state.mu.Unlock()\n\tswitch s.state.value {\n\tcase srvStateActive:\n\t\treturn fmt.Errorf(\"asynq: the scheduler is already running\")\n\tcase srvStateClosed:\n\t\treturn fmt.Errorf(\"asynq: the scheduler has already been stopped\")\n\t}\n\ts.state.value = srvStateActive\n\treturn nil\n}\n\n// Shutdown stops and shuts down the scheduler.\nfunc (s *Scheduler) Shutdown() {\n\ts.state.mu.Lock()\n\tif s.state.value == srvStateNew || s.state.value == srvStateClosed {\n\t\t// scheduler is not running, do nothing and return.\n\t\ts.state.mu.Unlock()\n\t\treturn\n\t}\n\ts.state.value = srvStateClosed\n\ts.state.mu.Unlock()\n\n\ts.logger.Info(\"Scheduler shutting down\")\n\tclose(s.done) // signal heartbeater to stop","sourceCodeStart":262,"sourceCodeEnd":298,"githubUrl":"https://github.com/hibiken/asynq/blob/d135f1439bee74e989b7f9b41ecd542cc87f024a/scheduler.go#L262-L298","documentation":"Scheduler.start rejects the transition when the scheduler's state is srvStateClosed, meaning Shutdown/Stop was already called. A closed Scheduler cannot be restarted; asynq exposes this as an explicit error rather than panicking.","triggerScenarios":"Calling s.Start()/s.Run() after s.Shutdown() (or s.Stop()+Shutdown) on the same instance; reuse of a long-lived scheduler across config reloads that shut it down.","commonSituations":"Graceful shutdown handlers that close the scheduler, followed by retry logic that tries to restart it; embedding the scheduler in a service manager that restarts components.","solutions":["Create a new Scheduler (NewScheduler(...)) after Shutdown instead of restarting the old one","Restructure so Shutdown only happens at final teardown, not for temporary pauses (use Stop for pauses)","Treat this error as a programming bug and fix the lifecycle ownership"],"exampleFix":"// before\nsrv.Shutdown(); srv.Start(h) // restart after close\n// after\nsrv.Shutdown()\nsrv = asynq.NewScheduler(rconn, opts)\nsrv.Start(h)","handlingStrategy":"fallback","validationCode":"if closed.Load() {\n    return errors.New(\"scheduler already shut down; create a new instance\")\n}","typeGuard":null,"tryCatchPattern":"if err := s.Start(h); err != nil {\n    if strings.Contains(err.Error(), \"already been stopped\") {\n        s = asynq.NewScheduler(conn, opts) // recreate\n        return s.Start(h)\n    }\n    return err\n}","preventionTips":["After Shutdown, always construct a fresh Scheduler; never restart a closed one","Use Stop (not Shutdown) for temporary pauses within a process lifetime","Track shutdown state explicitly (atomic flag) and gate start attempts on it"],"tags":["asynq","scheduler","lifecycle","restart"],"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"}