{"record":{"id":"5ab333c73fb15370","repo":"apache/cassandra","slug":"maximum-pool-size-has-been-changed-while-resizing","errorCode":null,"errorMessage":"Maximum pool size has been changed while resizing","messagePattern":"Maximum pool size has been changed while resizing","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/concurrent/SEPExecutor.java","lineNumber":381,"sourceCode":"    public int getMaximumPoolSize()\n    {\n        return maximumPoolSize.get();\n    }\n\n    @Override\n    public synchronized void setMaximumPoolSize(int newMaximumPoolSize)\n    {\n        final int oldMaximumPoolSize = maximumPoolSize.get();\n\n        if (newMaximumPoolSize < 0)\n        {\n            throw new IllegalArgumentException(\"Maximum number of workers must not be negative\");\n        }\n\n        int deltaWorkPermits = newMaximumPoolSize - oldMaximumPoolSize;\n        if (!maximumPoolSize.compareAndSet(oldMaximumPoolSize, newMaximumPoolSize))\n        {\n            throw new IllegalStateException(\"Maximum pool size has been changed while resizing\");\n        }\n\n        if (deltaWorkPermits == 0)\n            return;\n\n        permits.updateAndGet(cur -> updateWorkPermits(cur, workPermits(cur) + deltaWorkPermits));\n        logger.info(\"Resized {} maximum pool size from {} to {}\", name, oldMaximumPoolSize, newMaximumPoolSize);\n\n        // If we we have more work permits than before we should spin up a worker now rather than waiting\n        // until either a new task is enqueued (if all workers are descheduled) or a spinning worker calls\n        // maybeSchedule().\n        pool.maybeStartSpinningWorker();\n\n        maximumPoolSizeListener.onUpdateMaximumPoolSize(newMaximumPoolSize);\n    }\n\n    private static int taskPermits(long both)\n    {","sourceCodeStart":363,"sourceCodeEnd":399,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/concurrent/SEPExecutor.java#L363-L399","documentation":"setMaximumPoolSize resizes the worker-permit pool with a compareAndSet on the current maximum; if another thread changed maximumPoolSize between the read and the CAS, the resize is abandoned and an IllegalStateException is thrown rather than silently overwriting the concurrent update. It is a lost-update detection mechanism for concurrent resizes.","triggerScenarios":"Two threads (e.g. two JMX calls, an admin tool and a background resizer) calling setMaximumPoolSize on the same SEPExecutor concurrently; a retry loop that re-reads stale size and collides with another resize in flight.","commonSituations":"Operational tooling resizing pools concurrently during load; automated tuners racing with manual JMX changes; test code resizing pools from multiple threads.","solutions":["Retry the resize: re-read the new maximumPoolSize (getMaximumPoolSize) and call setMaximumPoolSize again until it succeeds","Serialize resizes: perform all setMaximumPoolSize calls from a single thread or under a shared lock","Check whether multiple components (JMX + config applier) are resizing the same executor and let only one own it","Catch IllegalStateException around the call and retry with backoff if concurrent resizes are expected"],"exampleFix":"// before\nexecutor.setMaximumPoolSize(newSize); // IllegalStateException on race\n// after\nboolean resized = false;\nwhile (!resized)\n{\n    try { executor.setMaximumPoolSize(newSize); resized = true; }\n    catch (IllegalStateException e) { Thread.yield(); }\n}","handlingStrategy":"retry","validationCode":"// detect concurrent resizers before attempting\nif (resizeInProgress.compareAndSet(false, true))\n    try { executor.setMaximumPoolSize(newSize); } finally { resizeInProgress.set(false); }","typeGuard":null,"tryCatchPattern":"try\n{\n    executor.setMaximumPoolSize(newSize);\n}\ncatch (IllegalStateException e)\n{\n    // someone else resized concurrently; re-read and retry\n    newSize = executor.getMaximumPoolSize();\n}","preventionTips":["Serialize all pool resizes through a single owner (thread or lock)","Avoid overlapping JMX and automated resizes on the same executor","Re-read getMaximumPoolSize() before each retry","Add jitter/backoff when retrying contended resizes"],"tags":["concurrency","executor","race-condition"],"backgroundTag":"invalid-state-transition","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}