Backend Development 7 min read

Understanding Disruptor Shutdown Failure in High‑Consumer‑Count Performance Testing

When building a QPS performance testing model with Disruptor, the author discovered that using an excessive number of consumer threads can cause the shutdown method to fail, leaving threads alive, inflating CPU usage, and destabilizing test results.

FunTester
FunTester
FunTester
Understanding Disruptor Shutdown Failure in High‑Consumer‑Count Performance Testing

While developing a new QPS performance‑testing model based on the Disruptor framework, the author encountered many pitfalls, the most typical being that disruptor.shutdown() does not terminate all threads under certain conditions.

The issue stems from a rare scenario in which the FunTester performance‑testing framework uses Disruptor with a very large consumer‑thread pool (often >1024) to achieve 100k QPS. The design assumes each consumer thread acts as a performance‑testing thread, the consumer count must be set before the Disruptor starts and cannot be changed, and the test begins with most consumers idle or not yet started.

When the number of consumer threads far exceeds the actual demand, calling disruptor.shutdown() after the test finishes leaves many threads running, causing the program not to exit and the CPU usage to spike.

Two key observations from the analysis are:

The producer threads must finish before invoking disruptor.shutdown() .

disruptor.shutdown() must be called before any consumer threads are started.

These situations are unlikely unless deliberately constructed, but the real culprit in practice is simply having too many consumer threads.

Practical experience suggests:

Increasing the consumer count does not always improve performance; more is not better.

When the consumer pool is overly large, QPS stability degrades, with variations up to 30%.

Keeping the total thread count below 2000 helps avoid the Disruptor shutdown failure.

The data referenced were collected with a QPS of 50,000 and an average response time of 10 ms.

Overall, using Disruptor for performance testing introduces many hidden pitfalls, and the most reliable model at the ten‑thousand‑QPS level remains a straightforward thread‑based approach.

Below is a Java test script that reproduces the shutdown issue:

import com.lmax.disruptor.EventHandler;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.TimeoutBlockingWaitStrategy;
import com.lmax.disruptor.WorkHandler;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

public class DisJava {
    public static void main(String[] args) {
        ThreadFactory threadFactory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                return thread;
            }
        };
        Disruptor
disruptor = new Disruptor
(
                Event::new,
                256 * 256,
                threadFactory,
                ProducerType.MULTI,
                new TimeoutBlockingWaitStrategy(1000, TimeUnit.MILLISECONDS)
        );
        RingBuffer
ringBuffer = disruptor.getRingBuffer();
        int num = 3000;
        EventFun[] consumers = new EventFun[num];
        for (int i = 0; i < num; i++) {
            consumers[i] = new EventFun();
        }
        disruptor.handleEventsWithWorkerPool(consumers);
        disruptor.start();
        for (int i = 0; i < 10; i++) {
            ringBuffer.publishEvent((e, s) -> {
                e.setEvent("123");
                System.out.println(System.currentTimeMillis());
            });
        }
        disruptor.shutdown();
        System.out.println("结束了");
    }

    private static class EventFun implements EventHandler
, WorkHandler
{
        public EventFun() {}
        @Override
        public void onEvent(Event event) throws Exception { sleep(10); }
        @Override
        public void onEvent(Event event, long sequence, boolean endOfBatch) throws Exception { sleep(10); }
    }

    private static class Event {
        public String getEvent() { return event; }
        public void setEvent(String event) { this.event = event; }
        String event;
    }

    private static void sleep(long time) {
        try { Thread.sleep(time); } catch (InterruptedException e) {}
    }
}

Have Fun ~ Tester!

JavaConcurrencyperformance testingDisruptorQPSShutdown Issue
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.