hibiken/asynq · error
asynq: the scheduler has already been stopped
Error message
asynq: the scheduler has already been stopped
What it means
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.
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
Example fix
// before srv.Shutdown(); srv.Start(h) // restart after close // after srv.Shutdown() srv = asynq.NewScheduler(rconn, opts) srv.Start(h)
Defensive patterns
Strategy: fallback
Validate before calling
if closed.Load() {
return errors.New("scheduler already shut down; create a new instance")
} Try / catch
if err := s.Start(h); err != nil {
if strings.Contains(err.Error(), "already been stopped") {
s = asynq.NewScheduler(conn, opts) // recreate
return s.Start(h)
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
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 scheduler is already running
- PeriodicTaskConfig.Cronspec cannot be empty
- asynq
- asynq: no scheduler entry found
- asynq: the server is already running
AI-assisted analysis of hibiken/asynq@d135f1439b (2026-09-07).
Data as JSON: /api/errors/d0868f33415c107b.
Report an issue: GitHub.
Appendix: source
Thrown at scheduler.go:280
}
s.logger.Info("Scheduler starting")
s.logger.Infof("Scheduler timezone is set to %v", s.location)
s.cron.Start()
s.wg.Add(1)
go s.runHeartbeater()
return nil
}
// Checks server state and returns an error if pre-condition is not met.
// Otherwise it sets the server state to active.
func (s *Scheduler) start() error {
s.state.mu.Lock()
defer s.state.mu.Unlock()
switch s.state.value {
case srvStateActive:
return fmt.Errorf("asynq: the scheduler is already running")
case srvStateClosed:
return fmt.Errorf("asynq: the scheduler has already been stopped")
}
s.state.value = srvStateActive
return nil
}
// Shutdown stops and shuts down the scheduler.
func (s *Scheduler) Shutdown() {
s.state.mu.Lock()
if s.state.value == srvStateNew || s.state.value == srvStateClosed {
// scheduler is not running, do nothing and return.
s.state.mu.Unlock()
return
}
s.state.value = srvStateClosed
s.state.mu.Unlock()
s.logger.Info("Scheduler shutting down")
close(s.done) // signal heartbeater to stopView on GitHub (pinned to d135f1439b)