{"record":{"id":"2f504204c73615ad","repo":"alibaba/ARouter","slug":"interceptor-timeout-has-already-been-scheduled","errorCode":null,"errorMessage":"Interceptor timeout has already been scheduled.","messagePattern":"Interceptor timeout has already been scheduled\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"arouter-api/src/main/java/com/alibaba/android/arouter/core/InterceptorChain.java","lineNumber":71,"sourceCode":"                     ScheduledThreadPoolExecutor timeoutExecutor) {\n        this(interceptors, postcard, completionCallback, timeoutExecutor, DEFAULT_COMPLETION_EXECUTOR);\n    }\n\n    InterceptorChain(List<IInterceptor> interceptors,\n                     Postcard postcard,\n                     InterceptorCallback completionCallback,\n                     ScheduledThreadPoolExecutor timeoutExecutor,\n                     ExecutorService completionExecutor) {\n        this.interceptors = new ArrayList<IInterceptor>(interceptors);\n        this.postcard = postcard;\n        this.completionCallback = completionCallback;\n        this.timeoutExecutor = timeoutExecutor;\n        this.completionExecutor = completionExecutor;\n    }\n\n    void scheduleTimeout(long timeout, TimeUnit unit) {\n        if (!timeoutScheduled.compareAndSet(false, true)) {\n            throw new IllegalStateException(\"Interceptor timeout has already been scheduled.\");\n        }\n\n        ScheduledFuture<?> future = timeoutExecutor.schedule(new Runnable() {\n            @Override\n            public void run() {\n                onTimeout();\n            }\n        }, timeout, unit);\n        timeoutFuture = future;\n\n        // A zero-length timeout may finish before schedule() returns.\n        if (completed.get()) {\n            cancelTimeout(future);\n        }\n    }\n\n    @Override\n    public void run() {","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/alibaba/ARouter/blob/84f451d244e3fb1d0e37801de1b9ee2af2f81d68/arouter-api/src/main/java/com/alibaba/android/arouter/core/InterceptorChain.java#L53-L89","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":"// before\nchain.scheduleTimeout(5, TimeUnit.SECONDS);\n// ...later on callback resume\nchain.scheduleTimeout(5, TimeUnit.SECONDS); // throws\n// after\nif (!chainStarted) {\n    chain.scheduleTimeout(5, TimeUnit.SECONDS);\n    chainStarted = true;\n}","handlingStrategy":"try-catch","validationCode":"if (timeoutScheduledOnce) {\n    throw new IllegalStateException(\"timeout already armed for this chain\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    chain.scheduleTimeout(timeout, unit);\n} catch (IllegalStateException e) {\n    // second arm attempt: ignore, the original timeout still governs the chain\n    Log.w(TAG, \"timeout already scheduled for chain\", e);\n}","preventionTips":["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"],"tags":["arouter","interceptor","lifecycle","illegal-state"],"backgroundTag":"invalid-state-transition","analyzedSha":"84f451d244e3fb1d0e37801de1b9ee2af2f81d68","analyzedAt":"2026-09-06T13:30:41.084Z","contentChangedAt":"2026-09-06T13:30:41.084Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}