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

  1. Call Start (or Run) only once per Server instance; keep the returned state and reuse it.
  2. If you need to restart, call Stop() (and eventually ShutDown()) first, and create a new Server if it was closed.
  3. Track server lifecycle with a separate mutex/flag in application code, or check srv state before starting.
  4. 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

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


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)