hibiken/asynq · error
asynq: the server is already running
Error message
asynq: the server is already running
What it means
Server.start enforces a single active server per Server instance. If Start (or Run) is invoked while the server's state is already Active, this error is returned because a server can process tasks from only one handler loop at a time.
Solutions
- Call Start (or Run) only once per Server instance; keep the returned state and reuse it.
- If you need to restart, call Stop() (and eventually ShutDown()) first, and create a new Server if it was closed.
- Track server lifecycle with a separate mutex/flag in application code, or check srv state before starting.
- Create a new asynq.Server instead of reusing a running one.
Example fix
// before
srv.Start(handler)
...
srv.Start(handler) // panics: already running
// after
if err := srv.Start(handler); err != nil && !strings.Contains(err.Error(), "already running") {
log.Fatal(err)
} Defensive patterns
Strategy: validation
Validate before calling
var startOnce sync.Once
func ensureStarted(srv *asynq.Server, h asynq.Handler) error {
var err error
startOnce.Do(func() { err = srv.Start(h) })
return err
} Try / catch
if err := srv.Start(handler); err != nil {
if strings.Contains(err.Error(), "already running") {
return nil // idempotent start
}
return err
} Prevention
- Start each Server exactly once (sync.Once or a lifecycle manager).
- Never share one asynq.Server across independent start paths.
- On restart, always create a new Server instance.
When it happens
Trigger: Calling srv.Start(handler) or srv.Run(handler) a second time without calling Stop/ShutDown first; calling both Run and Start on the same Server; restart logic that forgot the previous Start succeeded.
Common situations: Code paths that construct-or-reuse a shared Server singleton and call Start on every request; hot-reload logic restarting the server; duplicate startup in tests with shared fixtures.
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
- asynq: the server is in the stopped state. Waiting for…
- asynq: Server closed
- batch enqueue does not support group tasks
- batch enqueue does not support unique tasks
AI-assisted analysis of hibiken/asynq@d135f1439b (2026-09-07).
Data as JSON: /api/errors/24811ad3f94b0048.
Report an issue: GitHub.
Appendix: source
Thrown at server.go:710
srv.healthchecker.start(&srv.wg)
srv.subscriber.start(&srv.wg)
srv.syncer.start(&srv.wg)
srv.recoverer.start(&srv.wg)
srv.forwarder.start(&srv.wg)
srv.processor.start(&srv.wg)
srv.janitor.start(&srv.wg)
srv.aggregator.start(&srv.wg)
return nil
}
// Checks server state and returns an error if pre-condition is not met.
// Otherwise it sets the server state to active.
func (srv *Server) start() error {
srv.state.mu.Lock()
defer srv.state.mu.Unlock()
switch srv.state.value {
case srvStateActive:
return fmt.Errorf("asynq: the server is already running")
case srvStateStopped:
return fmt.Errorf("asynq: the server is in the stopped state. Waiting for shutdown.")
case srvStateClosed:
return ErrServerClosed
}
srv.state.value = srvStateActive
return nil
}
// Shutdown gracefully shuts down the server.
// It gracefully closes all active workers. The server will wait for
// active workers to finish processing tasks for duration specified in Config.ShutdownTimeout.
// If worker didn't finish processing a task during the timeout, the task will be pushed back to Redis.
func (srv *Server) Shutdown() {
srv.state.mu.Lock()
if srv.state.value == srvStateNew || srv.state.value == srvStateClosed {
srv.state.mu.Unlock()
// server is not running, do nothing and return.View on GitHub (pinned to d135f1439b)