quarkusio/quarkus · error
Thread not suspended
Error message
Thread not suspended
What it means
RemoteThread.evaluateInRenderThread() executes an evaluation on a paused render thread, so it requires state == SUSPENDED. It throws IllegalStateException('Thread not suspended') when the render thread is still running (or otherwise not suspended), because evaluating expressions against a running render would race with template rendering.
Source
Thrown at independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/agent/RemoteThread.java:362
*
* <p>
* <b>Threading model:</b><br>
* This method must be called from the debugger control thread (not the render thread itself).
* The provided callable will be executed synchronously within the render thread context, ensuring
* compatibility with request-bound state.
* </p>
*
* @param action the task to execute within the render thread context.
* It should return a {@link CompletableFuture} representing the computation result.
* @return a {@link CompletableFuture} containing the result of the executed task
* @throws IllegalStateException if the thread is not currently suspended or if another
* evaluation task is already pending
*/
public CompletableFuture<Object> evaluateInRenderThread(Callable<CompletableFuture<Object>> action) {
synchronized (lock) {
// Ensure the render thread is suspended before executing any task
if (this.state != DebuggerState.SUSPENDED) {
throw new IllegalStateException("Thread not suspended");
}
// Prevent concurrent evaluation tasks from overlapping
if (pendingTask != null) {
throw new IllegalStateException("A task is already pending");
}
// Schedule the evaluation task to be executed by the render thread
pendingTask = action;
taskResult = null;
// Wake up the suspended render thread so it can pick up and execute the task
lock.notifyAll();
// Wait for the render thread to process and complete the pending task
boolean intr = false;
try {
while (pendingTask != null) {View on GitHub (pinned to e1c734241f)
Solutions
- Only call evaluateInRenderThread() after the thread reports SUSPENDED (e.g. after a breakpoint or pause completes).
- Await the CompletableFuture from pause()/breakpoint suspension before evaluating.
- Catch IllegalStateException and retry once the suspended event arrives.
- In DAP clients, gate evaluate requests on the 'stopped' event for that thread.
Example fix
// before
Object val = thread.evaluateInRenderThread(action).get();
// after
if (thread.getState() == DebuggerState.SUSPENDED) {
Object val = thread.evaluateInRenderThread(action).get();
} else {
// wait for suspension event first
} Defensive patterns
Strategy: validation
Validate before calling
if (thread.getState() != DebuggerState.SUSPENDED) {
throw new IllegalStateException("Wait for suspension before evaluating");
}
Object v = thread.evaluateInRenderThread(action).get(); Type guard
boolean canEvaluate(RemoteThread t) {
return t.getState() == DebuggerState.SUSPENDED;
} Try / catch
try {
return thread.evaluateInRenderThread(action).get();
} catch (IllegalStateException e) {
if ("Thread not suspended".equals(e.getMessage())) {
// wait for suspension event, then retry once
}
throw e;
} Prevention
- Gate evaluate calls on the thread-suspended event
- Never evaluate against a running render thread
- In DAP clients, bind evaluate to the stopped state
When it happens
Trigger: Calling evaluateInRenderThread() while the render thread is RUNNING — e.g. a DAP evaluate request issued without waiting for the thread to hit a breakpoint/suspension, or after the thread resumed.
Common situations: Debug clients sending evaluateRequests immediately after attach; evaluate calls racing with a continue; tests invoking evaluation before triggering a suspension.
Related errors
- A task is already pending
- No template variant found
- Tracing not enabled
- Not a virtual method: [typeInfo: ]
- Expression is not a literal:
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/8f2f8106591fe80f.
Report an issue: GitHub.