apache/dubbo · error · IllegalStateException
HashedWheelTimer.stop() cannot be called from TimerTask
Error message
HashedWheelTimer.stop() cannot be called from TimerTask
What it means
IllegalStateException thrown by HashedWheelTimer.stop() when the calling thread is the timer's own worker thread. Stopping the timer from within a TimerTask (which executes on the worker thread) would deadlock — stop() joins the worker thread, and joining yourself is impossible. This is a self-deadlock prevention guard. The error message names both HashedWheelTimer and TimerTask to make the cause obvious.
Source
Thrown at dubbo-common/src/main/java/org/apache/dubbo/common/timer/HashedWheelTimer.java:343
throw new IllegalStateException("cannot be started once stopped");
default:
throw new Error("Invalid WorkerState");
}
// Wait until the startTime is initialized by the worker.
while (startTime == 0) {
try {
startTimeInitialized.await();
} catch (InterruptedException ignore) {
// Ignore - it will be ready very soon.
}
}
}
@Override
public Set<Timeout> stop() {
if (Thread.currentThread() == workerThread) {
throw new IllegalStateException(
HashedWheelTimer.class.getSimpleName() +
".stop() cannot be called from " +
TimerTask.class.getSimpleName());
}
if (!WORKER_STATE_UPDATER.compareAndSet(this, WORKER_STATE_STARTED, WORKER_STATE_SHUTDOWN)) {
// workerState can be 0 or 2 at this moment - let it always be 2.
if (WORKER_STATE_UPDATER.getAndSet(this, WORKER_STATE_SHUTDOWN) != WORKER_STATE_SHUTDOWN) {
INSTANCE_COUNTER.decrementAndGet();
}
return Collections.emptySet();
}
try {
boolean interrupted = false;
while (workerThread.isAlive()) {
workerThread.interrupt();View on GitHub (pinned to 3a3043227f)
Solutions
- Never call timer.stop() from within a TimerTask — schedule the shutdown to happen on a different thread.
- If you need to stop the timer after a task completes, set a flag in the task and call stop() from a separate (non-worker) thread.
- Use a separate executor or a post-task callback mechanism to trigger timer shutdown from outside the worker thread.
Example fix
// before — stop called from inside a task (runs on worker thread)
timer.newTimeout(new TimerTask() {
public void run(Timeout t) {
timer.stop(); // throws: would deadlock
}
}, 5, TimeUnit.SECONDS);
// after — defer stop to another thread
timer.newTimeout(new TimerTask() {
public void run(Timeout t) {
separateExecutor.execute(() -> timer.stop());
}
}, 5, TimeUnit.SECONDS); Defensive patterns
Strategy: validation
Validate before calling
// Before calling stop(), verify the current thread is NOT the worker thread
public static void safeStop(HashedWheelTimer timer) {
// There is no public getter for workerThread; track it externally or
// simply never call stop() from within TimerTask.run()
if (Thread.currentThread().getName().contains("timeoutWorker")) {
throw new IllegalStateException("Cannot stop timer from worker thread");
}
timer.stop();
} Try / catch
try {
timer.stop();
} catch (IllegalStateException e) {
if (e.getMessage().contains("cannot be called from")) {
// defer to another thread
separateExecutor.execute(timer::stop);
} else {
throw e;
}
} Prevention
- Never call timer.stop() from inside a TimerTask.run() method.
- If a task must trigger shutdown, post the stop() call to a separate executor.
- Design timer tasks to be stateless and never control the timer's lifecycle.
When it happens
Trigger: A TimerTask's run(Timeout) method (or code it invokes) calls timer.stop() on the same HashedWheelTimer that is executing it. Since tasks run on the worker thread, and stop() blocks waiting for the worker thread to terminate, this would deadlock.
Common situations: A timeout callback that tries to shut down the timer as part of cleanup; a 'final' timer task that intends to stop all scheduling; business logic inside a TimerTask that triggers application teardown including the timer.
Related errors
- cannot be started once stopped
- ScopeBeanFactory is destroyed
- ExtensionDirector is destroyed
- ExtensionLoader is destroyed: ${type}
- Executor is shutdown!
AI-assisted analysis of apache/dubbo@3a3043227f (2026-08-14).
Data as JSON: /api/errors/bf3265449bfee6ab.
Report an issue: GitHub.