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

  1. Never call timer.stop() from within a TimerTask — schedule the shutdown to happen on a different thread.
  2. 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.
  3. 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

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


AI-assisted analysis of apache/dubbo@3a3043227f (2026-08-14). Data as JSON: /api/errors/bf3265449bfee6ab. Report an issue: GitHub.