LMAX-Exchange/disruptor · error · IllegalStateException

Thread is already running

Error message

Thread is already running

What it means

Defensive check in RingBufferFields' constructor: bufferSize taken from the sequencer is less than 1. In practice this is nearly unreachable because AbstractSequencer already validated the size (error 0); it exists to protect against a custom Sequencer implementation whose getBufferSize() returns garbage.

Source

Thrown at src/main/java/com/lmax/disruptor/NoOpEventProcessor.java:63

    @Override
    public void halt()
    {
        running.set(false);
    }

    @Override
    public boolean isRunning()
    {
        return running.get();
    }

    @Override
    public void run()
    {
        if (!running.compareAndSet(false, true))
        {
            throw new IllegalStateException("Thread is already running");
        }
    }

    /**
     * Sequence that follows (by wrapping) another sequence
     */
    private static final class SequencerFollowingSequence extends Sequence
    {
        private final RingBuffer<?> sequencer;

        private SequencerFollowingSequence(final RingBuffer<?> sequencer)
        {
            super(Sequencer.INITIAL_CURSOR_VALUE);
            this.sequencer = sequencer;
        }

        @Override
        public long get()

View on GitHub (pinned to c871ca4982)

Solutions

  1. If you implement Sequencer yourself, make getBufferSize() return the same validated positive power-of-2 size AbstractSequencer enforces.
  2. Prefer extending AbstractSequencer rather than implementing Sequencer from scratch so the built-in validation applies.
  3. Fix mocks: stub getBufferSize() to return a valid size like 1024.

Example fix

// before
class MySequencer implements Sequencer {
    public int getBufferSize() { return bufferSize; } // bufferSize field never set -> 0
}

// after
class MySequencer extends AbstractSequencer {
    MySequencer(int size, WaitStrategy ws) { super(size, ws); } // validated here
}
Defensive patterns

Strategy: validation

Validate before calling

int size = customSequencer.getBufferSize();
if (size < 1) throw new IllegalArgumentException("custom sequencer reports invalid bufferSize " + size);

Prevention

When it happens

Trigger: Supplying a hand-written Sequencer implementation to new RingBuffer<>(factory, customSequencer) whose getBufferSize() returns 0 or a negative value; subclassing RingBuffer/RingBufferFields with a broken size.

Common situations: Custom sequencer subclasses added for instrumentation or testing that forget to initialise the bufferSize field; mocking a Sequencer without stubbing getBufferSize() (default mock returns 0).

Related errors


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