LMAX-Exchange/disruptor · error · IllegalStateException

Thread is already running

Error message

Thread is already running

What it means

Thrown by BatchEventProcessor.run() when it observes the processor is already in the RUNNING state. The processor guards its run loop with an atomic state field; run() is designed to be executed exactly once per processor lifecycle (usually by a single thread from an ExecutorService), and re-invoking it while alive is a lifecycle bug.

Source

Thrown at src/main/java/com/lmax/disruptor/BatchEventProcessor.java:135

            notifyStart();
            try
            {
                if (running.get() == RUNNING)
                {
                    processEvents();
                }
            }
            finally
            {
                notifyShutdown();
                running.set(IDLE);
            }
        }
        else
        {
            if (witnessValue == RUNNING)
            {
                throw new IllegalStateException("Thread is already running");
            }
            else
            {
                earlyExit();
            }
        }
    }

    private void processEvents()
    {
        T event = null;
        long nextSequence = sequence.get() + 1L;

        while (true)
        {
            final long startOfBatchSequence = nextSequence;
            try
            {

View on GitHub (pinned to c871ca4982)

Solutions

  1. Do not call run() yourself — let Disruptor start the processor via handleEventsWith(...) and its internal executor.
  2. If you manage threads, submit each BatchEventProcessor exactly once; to restart, build a new processor (and re-wire it) after halt().
  3. Check for code that retries failed tasks by resubmitting the same Runnable.

Example fix

// before
executor.submit(batchEventProcessor);
// later, after a hiccup:
executor.submit(batchEventProcessor); // IllegalStateException

// after
// one processor per lifecycle; to restart, create a new one
batchEventProcessor.halt();
BatchEventProcessor<MyEvent> fresh = new BatchEventProcessorBuilder().build(...);
executor.submit(fresh);
Defensive patterns

Strategy: validation

Validate before calling

if (batchEventProcessor.isRunning()) {
    // do not call run()/resubmit; halt and rebuild instead
}

Try / catch

try {
    executor.submit(processor);
} catch (IllegalStateException e) {
    if ("Thread is already running".equals(e.getMessage())) { /* processor already live; log and skip */ }
    else throw e;
}

Prevention

When it happens

Trigger: Calling run() manually on a processor that the Disruptor framework has already started; submitting the same BatchEventProcessor instance to an executor twice; wrapping the processor in your own retry/restart loop without halting it first.

Common situations: Custom wiring where the developer manages threads instead of letting Disruptor's handleEventsWith start them; restarting a 'stuck' consumer by re-running the same object; accidentally sharing one processor across two worker threads.

Related errors


AI-assisted analysis of LMAX-Exchange/disruptor@c871ca4982 (2026-08-14). Data as JSON: /api/errors/63ac3b2a7fe6fc70. Report an issue: GitHub.