{"record":{"id":"63ac3b2a7fe6fc70","repo":"LMAX-Exchange/disruptor","slug":"thread-is-already-running","errorCode":null,"errorMessage":"Thread is already running","messagePattern":"Thread is already running","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"src/main/java/com/lmax/disruptor/BatchEventProcessor.java","lineNumber":135,"sourceCode":"            notifyStart();\n            try\n            {\n                if (running.get() == RUNNING)\n                {\n                    processEvents();\n                }\n            }\n            finally\n            {\n                notifyShutdown();\n                running.set(IDLE);\n            }\n        }\n        else\n        {\n            if (witnessValue == RUNNING)\n            {\n                throw new IllegalStateException(\"Thread is already running\");\n            }\n            else\n            {\n                earlyExit();\n            }\n        }\n    }\n\n    private void processEvents()\n    {\n        T event = null;\n        long nextSequence = sequence.get() + 1L;\n\n        while (true)\n        {\n            final long startOfBatchSequence = nextSequence;\n            try\n            {","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/LMAX-Exchange/disruptor/blob/c871ca49826a6be7ada6957f6fbafcfecf7b1f87/src/main/java/com/lmax/disruptor/BatchEventProcessor.java#L117-L153","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Do not call run() yourself — let Disruptor start the processor via handleEventsWith(...) and its internal executor.","If you manage threads, submit each BatchEventProcessor exactly once; to restart, build a new processor (and re-wire it) after halt().","Check for code that retries failed tasks by resubmitting the same Runnable."],"exampleFix":"// before\nexecutor.submit(batchEventProcessor);\n// later, after a hiccup:\nexecutor.submit(batchEventProcessor); // IllegalStateException\n\n// after\n// one processor per lifecycle; to restart, create a new one\nbatchEventProcessor.halt();\nBatchEventProcessor<MyEvent> fresh = new BatchEventProcessorBuilder().build(...);\nexecutor.submit(fresh);","handlingStrategy":"validation","validationCode":"if (batchEventProcessor.isRunning()) {\n    // do not call run()/resubmit; halt and rebuild instead\n}","typeGuard":null,"tryCatchPattern":"try {\n    executor.submit(processor);\n} catch (IllegalStateException e) {\n    if (\"Thread is already running\".equals(e.getMessage())) { /* processor already live; log and skip */ }\n    else throw e;\n}","preventionTips":["Let Disruptor's DSL own processor start; never call run() manually.","Track submitted processors in a set to make double-submission impossible."],"tags":["disruptor","lifecycle","concurrency","event-processor","illegal-state"],"backgroundTag":null,"analyzedSha":"c871ca49826a6be7ada6957f6fbafcfecf7b1f87","analyzedAt":"2026-08-14T14:22:36.358Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}