oracle/graal · error · IllegalContinuationStateException
Suspend capabilities can only be used inside a running conti
Error message
Suspend capabilities can only be used inside a running continuation.
What it means
IllegalContinuationStateException from trySuspend when the calling thread owns the continuation but its state is not RUNNING, so the RUNNING->SUSPENDED CAS fails. The continuation already moved (e.g. suspended by another path, completed, or is mid-transition), so a second suspend is illegal.
Source
Thrown at espresso/src/org.graalvm.continuations/src/org/graalvm/continuations/ContinuationImpl.java:476
if (j < record.pointers.length - 1) {
sb.append(", ");
}
}
sb.append("\n");
}
return sb.toString();
}
/**
* Called from {@link SuspendCapability#suspend()}. Suspends this continuation. If successful,
* execution resumes back at {@link #resume()}.
*/
void trySuspend() {
if (exclusiveOwner != Thread.currentThread()) {
throw new IllegalContinuationStateException("Suspend capabilities can only be used inside a continuation.");
}
if (!updateState(State.RUNNING, State.SUSPENDED)) {
throw new IllegalContinuationStateException("Suspend capabilities can only be used inside a running continuation.");
}
try {
suspend();
} catch (IllegalContinuationStateException e) {
if (!updateState(State.SUSPENDED, State.RUNNING)) {
// force failed state and maybe assert
State badState = forceState(State.RUNNING);
if (ASSERTIONS_ENABLED) {
AssertionError assertionError = new AssertionError(badState.toString());
assertionError.addSuppressed(e);
throw assertionError;
}
}
throw e;
}
// set in #resume()
assert state == State.RUNNING : state;
}View on GitHub (pinned to a66e9ccd1d)
Solutions
- Call suspend() at most once per activation; after resume() returns from a suspend, do not suspend again until re-resumed.
- Structure the entry point so only one place performs the suspension decision.
- On this exception, check getState() and unwind instead of retrying.
- Keep suspend logic on the continuation thread only (also avoids error 595).
Example fix
// before
try {
capability.suspend();
} catch (IllegalContinuationStateException e) {
capability.suspend(); // double-suspend -> this error again
}
// after
if (continuation.getState() == Continuation.State.RUNNING) {
capability.suspend();
} else {
return; // already suspended/completed; unwind Defensive patterns
Strategy: validation
Validate before calling
if (continuation.getState() == Continuation.State.RUNNING) {
capability.suspend();
} else {
// already suspended/completed; return instead of suspending again
} Prevention
- Suspend at most once per activation.
- Consolidate the suspend decision to one place in the entry point.
- On IllegalContinuationStateException from suspend, check state and unwind.
When it happens
Trigger: Calling SuspendCapability.suspend() twice; suspending from a re-entrant callback after the state already changed to SUSPENDED; suspend racing with a state transition driven by serialization or failure handling.
Common situations: Retrying suspend in a catch block after the first attempt partially progressed; multiple suspension points triggered by the same event; nested callbacks each attempting to suspend.
Related errors
- This continuation has already completed successfully.
- This continuation has failed and must be discarded.
- You can't resume an already executing continuation.
- You can't resume a continuation while it is being serialized
- Only new or suspended continuation can be resumed
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/704423fbdb354171.
Report an issue: GitHub.