apache/druid · error · IllegalStateException
failed to stop, mainThread couldn't finish.
Error message
failed to stop, mainThread couldn't finish.
What it means
Thrown by LookupReferencesManager.stop() when the main processing thread, after being interrupted, throws InterruptedException from mainThread.join(). It indicates the lookup manager's background thread could not be confirmed terminated during shutdown, so stop() aborts with an IllegalStateException rather than closing lookups from an unknown state.
Solutions
- Investigate why mainThread is unresponsive to interrupt (blocking I/O or swallowed InterruptedException) and ensure its loop exits on interrupt.
- Check for other threads repeatedly interrupting the stopping thread during shutdown.
- Ensure stop() is not called concurrently from multiple lifecycle hooks.
- If this occurs during tests, verify testMode is set correctly so the main thread path is not exercised.
Example fix
// before: main loop swallows interrupts
while (!Thread.currentThread().isInterrupted()) {
try { Thread.sleep(Long.MAX_VALUE); } catch (InterruptedException e) { /* ignore */ }
}
// after: honor interrupt and exit
while (!Thread.currentThread().isInterrupted()) {
try { Thread.sleep(Long.MAX_VALUE); } catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
} Defensive patterns
Strategy: try-catch
Validate before calling
if (manager.isStarted()) { /* ensure no concurrent stop() calls; check main thread alive before stop */ } Try / catch
try { manager.stop(); } catch (IllegalStateException e) { LOG.warn("lookup manager stop failed: %s", e.getMessage()); /* proceed with shutdown; close lookup factories manually */ } Prevention
- Ensure the lookup manager main loop exits promptly on interrupt
- Avoid other threads interrupting the thread calling stop()
- Call stop() only once from the lifecycle shutdown path
- Use testMode in unit tests to skip the main-thread join path
When it happens
Trigger: Calling stop() (LifecycleStop) in non-testMode when mainThread.interrupt() leaves the background thread alive and the caller's own interrupt status is re-set while joining, so join() throws InterruptedException.
Common situations: Coordinator/lookup node shutdown where the main loop is stuck in a long blocking call that swallows interrupts; another thread interrupts the stopping thread during shutdown; JVM shutdown hooks racing with lifecycle stop.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- gRPC query server shutdown failed
- Already shut down, not starting again
- can't stop.
- can't stop.
- can't stop.
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/ce9863515f9bbc34.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/main/java/org/apache/druid/query/lookup/LookupReferencesManager.java:249
}
@LifecycleStop
public void stop()
{
if (!lifecycleLock.canStop()) {
throw new ISE("can't stop.");
}
LOG.debug("LookupExtractorFactoryContainerProvider is stopping.");
if (!testMode) {
mainThread.interrupt();
try {
mainThread.join();
}
catch (InterruptedException ex) {
throw new ISE("failed to stop, mainThread couldn't finish.");
}
}
for (Map.Entry<String, LookupExtractorFactoryContainer> e : stateRef.get().lookupMap.entrySet()) {
try {
if (e.getValue().getLookupExtractorFactory().close()) {
LOG.info("Closed lookup [%s].", e.getKey());
} else {
LOG.error("Failed to close lookup [%s].", e.getKey());
}
}
catch (Exception ex) {
LOG.error(ex, "Failed to close lookup [%s].", e.getKey());
}
}
lookupUpdateExecutorService.shutdown();
LOG.debug("LookupExtractorFactoryContainerProvider is stopped.");
}View on GitHub (pinned to 9b90983fd2)