alibaba/ARouter · error · IllegalStateException
Interceptor timeout has already been scheduled.
Error message
Interceptor timeout has already been scheduled.
What it means
InterceptorChain.scheduleTimeout arms a watchdog on the chain's timeout executor and uses a CAS flag to guarantee it is armed at most once per chain. Throwing IllegalStateException here means scheduleTimeout was invoked twice on the same chain instance, which is an internal misuse of the chain's lifecycle.
Source
Thrown at arouter-api/src/main/java/com/alibaba/android/arouter/core/InterceptorChain.java:71
ScheduledThreadPoolExecutor timeoutExecutor) {
this(interceptors, postcard, completionCallback, timeoutExecutor, DEFAULT_COMPLETION_EXECUTOR);
}
InterceptorChain(List<IInterceptor> interceptors,
Postcard postcard,
InterceptorCallback completionCallback,
ScheduledThreadPoolExecutor timeoutExecutor,
ExecutorService completionExecutor) {
this.interceptors = new ArrayList<IInterceptor>(interceptors);
this.postcard = postcard;
this.completionCallback = completionCallback;
this.timeoutExecutor = timeoutExecutor;
this.completionExecutor = completionExecutor;
}
void scheduleTimeout(long timeout, TimeUnit unit) {
if (!timeoutScheduled.compareAndSet(false, true)) {
throw new IllegalStateException("Interceptor timeout has already been scheduled.");
}
ScheduledFuture<?> future = timeoutExecutor.schedule(new Runnable() {
@Override
public void run() {
onTimeout();
}
}, timeout, unit);
timeoutFuture = future;
// A zero-length timeout may finish before schedule() returns.
if (completed.get()) {
cancelTimeout(future);
}
}
@Override
public void run() {View on GitHub (pinned to 84f451d244)
Solutions
- Arm the timeout exactly once, when the chain is created or started, and never in callback/continue paths
- Check that no racing code path (callback vs interrupt) both schedules; pick one owner for scheduling
- If you cannot be sure, drop the second call instead of relying on the throw (the CAS is a guard, not a feature)
Example fix
// before
chain.scheduleTimeout(5, TimeUnit.SECONDS);
// ...later on callback resume
chain.scheduleTimeout(5, TimeUnit.SECONDS); // throws
// after
if (!chainStarted) {
chain.scheduleTimeout(5, TimeUnit.SECONDS);
chainStarted = true;
} Defensive patterns
Strategy: try-catch
Validate before calling
if (timeoutScheduledOnce) {
throw new IllegalStateException("timeout already armed for this chain");
} Try / catch
try {
chain.scheduleTimeout(timeout, unit);
} catch (IllegalStateException e) {
// second arm attempt: ignore, the original timeout still governs the chain
Log.w(TAG, "timeout already scheduled for chain", e);
} Prevention
- Schedule the timeout once at chain creation only
- Never schedule from both callback and interrupt paths
- Wrap chains in wrappers carefully so lifecycle methods are not invoked twice
When it happens
Trigger: Calling scheduleTimeout more than once on the same InterceptorChain — e.g. scheduling a timeout both when the chain starts and again after a callback resume, or double-arming from both a racing callback path and the timeout-cancel path.
Common situations: Custom interceptor hosts or tests that wrap/forward the chain and inadvertently re-invoke scheduleTimeout; async interceptors that call continue() and then also trigger a re-schedule in their completion path.
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.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Interceptor initialization failure must have a cause.
- TimeUnit must not be null.
- More than one interceptors use same priority [%d], They are
- String.format(tipText, key)
- LogisticsCenterARouter init logistics center exception! [<fa
AI-assisted analysis of alibaba/ARouter@84f451d244 (2026-09-06).
Data as JSON: /api/errors/2f504204c73615ad.
Report an issue: GitHub.