alibaba/Sentinel · error · IllegalStateException

Bad async context state, expected entry: %s, but actual: %s

Error message

Bad async context state, expected entry: %s, but actual: %s

What it means

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.

Source

Thrown at sentinel-core/src/main/java/com/alibaba/csp/sentinel/AsyncEntry.java:63

    void cleanCurrentEntryInLocal() {
        if (context instanceof NullContext) {
            return;
        }
        Context originalContext = context;
        if (originalContext != null) {
            Entry curEntry = originalContext.getCurEntry();
            if (curEntry == this) {
                Entry parent = this.parent;
                originalContext.setCurEntry(parent);
                if (parent != null) {
                    ((CtEntry)parent).child = null;
                }
            } else {
                String curEntryName = curEntry == null ? "none"
                    : curEntry.resourceWrapper.getName() + "@" + curEntry.hashCode();
                String msg = String.format("Bad async context state, expected entry: %s, but actual: %s",
                    getResourceWrapper().getName() + "@" + hashCode(), curEntryName);
                throw new IllegalStateException(msg);
            }
        }
    }

    public Context getAsyncContext() {
        return asyncContext;
    }

    /**
     * The async context should not be initialized until the node for current resource has been set to current entry.
     */
    void initAsyncContext() {
        if (asyncContext == null) {
            if (context instanceof NullContext) {
                asyncContext = context;
                return;
            }
            this.asyncContext = Context.newAsyncContext(context.getEntranceNode(), context.getName())

View on GitHub (pinned to a3f40ba8e9)

Solutions

  1. Ensure every SphU.entry inside the async callback has a matching exit in a finally block
  2. Exit the AsyncEntry only after all inner entries are closed, usually in the async completion callback
  3. Use try-finally around each entry: try { ... } finally { entry.exit(); }
  4. Prefer the Reactor adapter (SentinelReactorTransformer) instead of manual asyncEntry for reactive code

Example fix

// before
AsyncEntry e = SphU.asyncEntry("job");
result.onComplete(v -> doWork()); // inner entries never exited
e.exit(); // too early

// after
AsyncEntry e = SphU.asyncEntry("job");
try {
    result.onComplete(v -> {
        Entry inner = SphU.entry("step");
        try { doWork(); } finally { inner.exit(); }
        e.exit();
    });
} catch (Throwable t) {
    e.exit();
    throw t;
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
    asyncEntry.exit();
} catch (IllegalStateException e) {
    // stack already unwound elsewhere; log and rebuild context if needed
    ContextUtil.exit();
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of alibaba/Sentinel@a3f40ba8e9 (2026-08-14). Data as JSON: /api/errors/3a5df1cbf95e1ac0. Report an issue: GitHub.