oracle/graal · error · IllegalContinuationStateException
You can't resume an already executing continuation.
Error message
You can't resume an already executing continuation.
What it means
IllegalContinuationStateException raised by resume() when the continuation's state is RUNNING. A continuation that is currently executing on a thread cannot be re-entered; resume is only legal from the NEW or SUSPENDED states. This typically means resume() was called re-entrantly or concurrently.
Source
Thrown at espresso/src/org.graalvm.continuations/src/org/graalvm/continuations/ContinuationImpl.java:376
try {
return start0();
} finally {
clearExclusiveOwner();
}
} else if (updateState(State.SUSPENDED, State.RUNNING)) {
setExclusiveOwner();
try {
// We are ready to resume, make sure the VM has the most up-to-date frames
ensureDematerialized();
assert stackFrameHead == null;
return resume0();
} finally {
clearExclusiveOwner();
}
} else {
// illegal state for resume: neither suspended nor new
switch (state) {
case RUNNING -> throw new IllegalContinuationStateException("You can't resume an already executing continuation.");
case COMPLETED ->
throw new IllegalContinuationStateException("This continuation has already completed successfully.");
case FAILED -> throw new IllegalContinuationStateException("This continuation has failed and must be discarded.");
case LOCKED -> throw new IllegalContinuationStateException("You can't resume a continuation while it is being serialized or deserialized.");
// this is racy so ensure we have a general error message in those case
default -> throw new IllegalContinuationStateException("Only new or suspended continuation can be resumed");
}
}
}
@Override
public synchronized StackTraceElement[] getRecordedFrames() {
State currentState = lock();
try {
ensureDematerialized();
return getRecordedFrames0();
} finally {
unlock(currentState);View on GitHub (pinned to a66e9ccd1d)
Solutions
- Only call resume() after the previous resume() returned (i.e. after suspend or completion).
- Serialize access to the continuation with a per-continuation lock or single-owner thread.
- If self-resume is intended, restructure so the entry point suspends instead of re-resuming.
- Check getState() == SUSPENDED before resume as a cheap pre-condition (still race-prone; keep the try-catch).
Example fix
// before
// thread A and thread B both do:
continuation.resume(); // one hits 'already executing'
// after
synchronized (continuation) {
if (continuation.getState() == Continuation.State.SUSPENDED) {
continuation.resume();
}
} Defensive patterns
Strategy: validation
Validate before calling
if (continuation.getState() != Continuation.State.SUSPENDED && continuation.getState() != Continuation.State.NEW) {
// do not resume now
} Try / catch
try {
continuation.resume();
} catch (IllegalContinuationStateException e) {
// another thread is running it; back off and reschedule
} Prevention
- Give each continuation a single owning thread/executor.
- Never call resume() from inside the continuation body.
- Guard resume with an external lock when shared.
When it happens
Trigger: Calling resume() from inside the continuation's own entry point (self-resume); a second thread calling resume() while the first is still inside resume(); resume() invoked from a listener/callback fired during continuation execution.
Common situations: Wrapping resume() in a scheduler that retries on failure without waiting for completion; recursive task schedulers where a task triggers its own continuation; missing synchronization around a shared continuation object.
Related errors
- You can't resume a continuation while it is being serialized
- Only new or suspended continuation can be resumed
- Suspend capabilities can only be used inside a continuation.
- This continuation has already completed successfully.
- This continuation has failed and must be discarded.
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/10da6b2697723a39.
Report an issue: GitHub.