oracle/graal · error · IllegalContinuationStateException

You can't resume a continuation while it is being serialized

Error message

You can't resume a continuation while it is being serialized or deserialized.

What it means

IllegalContinuationStateException raised by resume() while the continuation is locked (state LOCKED), i.e. another thread is currently serializing or deserializing it. The state machine blocks resume during I/O on the continuation's frames to prevent corruption.

Source

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

            }
        } 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);
        }
    }

    @Override

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Externalize synchronization: hold a lock over the whole serialize-or-resume decision so the two never overlap.
  2. Suspend the continuation before serializing; only resume after serialization finishes.
  3. Route all state transitions (resume/serialize/deserialize) through a single owner/actor.
  4. On IllegalContinuationStateException with LOCKED, back off and retry after the I/O completes.

Example fix

// before
// thread A: out.writeObject(continuation);
// thread B: continuation.resume(); // may hit LOCKED

// after
Object lock = new Object();
synchronized (lock) {
    out.writeObject(continuation);
} // resume only after serialization is done, under the same lock
Defensive patterns

Strategy: retry

Validate before calling

if (continuation.getState() == Continuation.State.LOCKED) {
    // serialization in progress; wait for it to finish before resume
}

Try / catch

try {
    continuation.resume();
} catch (IllegalContinuationStateException e) {
    if (continuation.getState() == Continuation.State.LOCKED) {
        // back off, retry after serialization completes
    }
}

Prevention

When it happens

Trigger: Calling resume() concurrently with writeObject/readObject on the same continuation from different threads; deserialization code that resumes before the read completes; checkpointing (serialization) racing the scheduler's resume.

Common situations: Persistence/checkpoint layers serializing continuations while a worker thread resumes them; missing coordination between the serializer thread and execution thread.

Related errors


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