oracle/graal · error · IllegalContinuationStateException

This continuation has already completed successfully.

Error message

This continuation has already completed successfully.

What it means

IllegalContinuationStateException raised by resume() when the continuation has already run to completion (state COMPLETED). A finished continuation holds no suspended frames and cannot be restarted; a new Continuation must be created instead.

Source

Thrown at espresso/src/org.graalvm.continuations/src/org/graalvm/continuations/ContinuationImpl.java:378

            } 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

  1. Honour the boolean result of resume(): true means finished; stop resuming.
  2. Check isCompleted() before calling resume().
  3. Create a fresh Continuation for each new run of the workload.
  4. Model repeated work as suspend/resume inside one lifetime, not as repeated resumes after completion.

Example fix

// before
while (true) {
    continuation.resume(); // keeps throwing once completed
}

// after
while (!continuation.resume()) {
    // suspended again; do scheduling work
}
// resume() returned true: completed, do not resume again
Defensive patterns

Strategy: validation

Validate before calling

if (continuation.isCompleted()) {
    // finished; create a new Continuation if the work must run again
}

Prevention

When it happens

Trigger: Calling resume() after resume() previously returned true (completion); looping 'while (!result) resume()' and ignoring the completion result; retry logic that blindly resumes after the entry point finished.

Common situations: Task-runner loops that resume until done but miss the terminal return value; re-running a one-shot job implemented as a continuation.

Related errors


AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14). Data as JSON: /api/errors/805aa184a2b6a66f. Report an issue: GitHub.