{"record":{"id":"3a5df1cbf95e1ac0","repo":"alibaba/Sentinel","slug":"bad-async-context-state-expected-entry-s-but-a","errorCode":null,"errorMessage":"Bad async context state, expected entry: %s, but actual: %s","messagePattern":"Bad async context state, expected entry: (.+?), but actual: (.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"sentinel-core/src/main/java/com/alibaba/csp/sentinel/AsyncEntry.java","lineNumber":63,"sourceCode":"    void cleanCurrentEntryInLocal() {\n        if (context instanceof NullContext) {\n            return;\n        }\n        Context originalContext = context;\n        if (originalContext != null) {\n            Entry curEntry = originalContext.getCurEntry();\n            if (curEntry == this) {\n                Entry parent = this.parent;\n                originalContext.setCurEntry(parent);\n                if (parent != null) {\n                    ((CtEntry)parent).child = null;\n                }\n            } else {\n                String curEntryName = curEntry == null ? \"none\"\n                    : curEntry.resourceWrapper.getName() + \"@\" + curEntry.hashCode();\n                String msg = String.format(\"Bad async context state, expected entry: %s, but actual: %s\",\n                    getResourceWrapper().getName() + \"@\" + hashCode(), curEntryName);\n                throw new IllegalStateException(msg);\n            }\n        }\n    }\n\n    public Context getAsyncContext() {\n        return asyncContext;\n    }\n\n    /**\n     * The async context should not be initialized until the node for current resource has been set to current entry.\n     */\n    void initAsyncContext() {\n        if (asyncContext == null) {\n            if (context instanceof NullContext) {\n                asyncContext = context;\n                return;\n            }\n            this.asyncContext = Context.newAsyncContext(context.getEntranceNode(), context.getName())","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/alibaba/Sentinel/blob/a3f40ba8e900c8489bd520274739f17235a7721c/sentinel-core/src/main/java/com/alibaba/csp/sentinel/AsyncEntry.java#L45-L81","documentation":"AsyncEntry.exit(true) unwinds the async call stack: it expects the async context's current entry to be this AsyncEntry. If context.getCurEntry() is a different entry (or null), it builds an IllegalStateException naming the expected and actual entries. It means exits are out of order on the async context — e.g. the async entry was exited while inner entries opened on the async context were not yet closed.","triggerScenarios":"Calling asyncEntry.exit() from a different thread/context than where inner resources were entered; exiting the AsyncEntry before exiting entries created inside the async context (e.g. forgetting entry.exit() inside the completion callback); mixing SphU.asyncEntry with synchronous children without pairing exits.","commonSituations":"Reactor/async callback code that enters resources on the async context but skips exit on error paths; double-exit or exit-after-timeout logic; switching between the original and async context incorrectly via ContextUtil.runOnContext.","solutions":["Ensure every SphU.entry inside the async callback has a matching exit in a finally block","Exit the AsyncEntry only after all inner entries are closed, usually in the async completion callback","Use try-finally around each entry: try { ... } finally { entry.exit(); }","Prefer the Reactor adapter (SentinelReactorTransformer) instead of manual asyncEntry for reactive code"],"exampleFix":"// before\nAsyncEntry e = SphU.asyncEntry(\"job\");\nresult.onComplete(v -> doWork()); // inner entries never exited\ne.exit(); // too early\n\n// after\nAsyncEntry e = SphU.asyncEntry(\"job\");\ntry {\n    result.onComplete(v -> {\n        Entry inner = SphU.entry(\"step\");\n        try { doWork(); } finally { inner.exit(); }\n        e.exit();\n    });\n} catch (Throwable t) {\n    e.exit();\n    throw t;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    asyncEntry.exit();\n} catch (IllegalStateException e) {\n    // stack already unwound elsewhere; log and rebuild context if needed\n    ContextUtil.exit();\n}","preventionTips":["Pair every inner entry's exit in finally inside async callbacks before exiting the AsyncEntry","Run async body via ContextUtil.runOnContext(asyncEntry.getAsyncContext(), ...) so entries land on the right context","For reactive stacks, prefer SentinelReactorTransformer over manual asyncEntry"],"tags":["async","core","illegal-state","call-stack","entry-exit"],"backgroundTag":null,"analyzedSha":"a3f40ba8e900c8489bd520274739f17235a7721c","analyzedAt":"2026-08-14T11:10:30.678Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}