{"record":{"id":"1442864c7c2b7af5","repo":"apache/druid","slug":"executor-already-shutdown","errorCode":null,"errorMessage":"Executor already shutdown","messagePattern":"Executor already shutdown","errorType":"exception","errorClass":"RejectedExecutionException","httpStatus":null,"severity":"warning","filePath":"processing/src/main/java/org/apache/druid/java/util/common/concurrent/DirectExecutorService.java","lineNumber":151,"sourceCode":"        } else {\n          long now = System.nanoTime();\n          TimeUnit.NANOSECONDS.timedWait(lock, nanos);\n          nanos -= System.nanoTime() - now; // subtract the actual time we waited\n        }\n      }\n    }\n  }\n\n  /**\n   * Checks if the executor has been shut down and increments the running task count.\n   *\n   * @throws RejectedExecutionException if the executor has been previously shutdown\n   */\n  private void startTask()\n  {\n    synchronized (lock) {\n      if (shutdown) {\n        throw new RejectedExecutionException(\"Executor already shutdown\");\n      }\n      runningTasks++;\n    }\n  }\n\n  /**\n   * Decrements the running task count.\n   */\n  private void endTask()\n  {\n    synchronized (lock) {\n      int numRunning = --runningTasks;\n      if (numRunning == 0) {\n        lock.notifyAll();\n      }\n    }\n  }\n}","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/java/util/common/concurrent/DirectExecutorService.java#L133-L169","documentation":"DirectExecutorService.startTask() refuses new work once the executor has been shutdown, throwing RejectedExecutionException('Executor already shutdown'). DirectExecutorService runs tasks on the submitting thread under a lock, so after shutdown() any further execute() call is rejected. This mirrors Guava/ExecutorService rejection semantics.","triggerScenarios":"Calling execute(task) (via startTask) on a DirectExecutorService after shutdown() or shutdownNow() has been invoked on it.","commonSituations":"Service lifecycle races: a service's stop()/shutdown() runs while another thread (e.g. a query callback or background submitter) still submits tasks; reusing a cached executor reference after its owner shut it down.","solutions":["Check executor.isShutdown() before submitting, or use an unshutdown owner for task submission","Ensure shutdown() is called only after all producers have stopped submitting (join/await their completion first)","Catch RejectedExecutionException around execute() and treat it as benign during teardown","Restructure to use a lifecycle-managed executor (e.g. Druid's lifecycle scopes) so shutdown ordering is explicit"],"exampleFix":"// before\nexecutor.execute(task);\n// after\ntry {\n  executor.execute(task);\n} catch (RejectedExecutionException e) {\n  // executor already shut down; drop or handle the task\n}","handlingStrategy":"try-catch","validationCode":"// guard before submitting\nif (!executor.isShutdown()) {\n  executor.execute(task);\n}","typeGuard":null,"tryCatchPattern":"try {\n  executor.execute(task);\n} catch (RejectedExecutionException e) {\n  // expected during shutdown; drop task or route to a dead-letter handler\n}","preventionTips":["Establish strict shutdown ordering: producers stop before executors shut down","Track executor lifecycle in one owner class; don't leak references post-shutdown","Treat RejectedExecutionException as benign during service teardown","Use Druid lifecycle-managed executors to centralize start/stop ordering"],"tags":["concurrency","executor","rejected-execution"],"backgroundTag":"invalid-state-transition","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}