{"record":{"id":"82b5bb945e03d9bb","repo":"apache/druid","slug":"service-is-closed","errorCode":null,"errorMessage":"Service is closed.","messagePattern":"Service is closed\\.","errorType":"exception","errorClass":"RejectedExecutionException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/java/util/emitter/core/HttpPostEmitter.java","lineNumber":208,"sourceCode":"      if (!running) {\n        if (startLatch.getCount() == 0) {\n          throw new IllegalStateException(\"Already started.\");\n        }\n        running = true;\n        startLatch.countDown();\n        emittingThread.start();\n      }\n    }\n  }\n\n  private void awaitStarted()\n  {\n    try {\n      if (!startLatch.await(1, TimeUnit.SECONDS)) {\n        throw new RejectedExecutionException(\"Service is not started.\");\n      }\n      if (isTerminated()) {\n        throw new RejectedExecutionException(\"Service is closed.\");\n      }\n    }\n    catch (InterruptedException e) {\n      log.debug(\"Interrupted waiting for start\");\n      Thread.currentThread().interrupt();\n      throw new RuntimeException(e);\n    }\n  }\n\n  private boolean isTerminated()\n  {\n    return concurrentBatch.get() == null;\n  }\n\n  @Override\n  public void emit(Event event)\n  {\n    emitAndReturnBatch(event);","sourceCodeStart":190,"sourceCodeEnd":226,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/java/util/emitter/core/HttpPostEmitter.java#L190-L226","documentation":"HttpPostEmitter queues events into batches and pushes them to a remote HTTP endpoint. awaitStarted() is called on every emit/flush path; it throws RejectedExecutionException('Service is closed.') when the emitter's emitting thread has been terminated (isTerminated() true), i.e. events are being submitted after close(). The library rejects work once the service lifecycle has ended rather than silently dropping events.","triggerScenarios":"Calling emit(), flush(), or close()-time flush after HttpPostEmitter.close()/stop() has terminated the EmittingThread; a race where a producer thread emits while another thread closes the emitter; Lifecycle stop ordering where metrics emitters are shut down before components still emitting.","commonSituations":"Shutdown ordering bugs in Druid services (emitter stopped while real-time metrics still flowing); application code caching an Emitter reference after lifecycle.stop(); frameworks flushing telemetry during JVM shutdown after the emitter was closed.","solutions":["Ensure the emitter is started before use and not closed while events may still arrive: move emitter close() to the very end of shutdown sequencing.","Guard emit calls with an isTerminated()/started check or wrap producers so they stop before the emitter closes.","Catch RejectedExecutionException around emit/flush and drop or buffer events during shutdown.","If this occurs at startup instead, verify Emitter.start() was called and completed within 1 second (startLatch await)."],"exampleFix":"// before\nemitter.close();\nreportMetrics(emitter); // throws RejectedExecutionException\n// after\nreportMetrics(emitter);\nemitter.close();","handlingStrategy":"try-catch","validationCode":"if (emitter instanceof HttpPostEmitter && ((HttpPostEmitter) emitter).isTerminated()) {\n  log.warn(\"Emitter closed; skipping emit\");\n  return;\n}","typeGuard":null,"tryCatchPattern":"try {\n  emitter.emit(event);\n} catch (RejectedExecutionException e) {\n  log.debug(e, \"Emitter closed, event dropped: %s\", event);\n}","preventionTips":["Close emitters last in shutdown order","Stop all event producers before emitter.close()","Start the emitter and await start completion before emitting"],"tags":["lifecycle","emitter","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-14T05:17:10.506Z"}