{"record":{"id":"8f2f8106591fe80f","repo":"quarkusio/quarkus","slug":"thread-not-suspended","errorCode":null,"errorMessage":"Thread not suspended","messagePattern":"Thread not suspended","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/agent/RemoteThread.java","lineNumber":362,"sourceCode":"     *\n     * <p>\n     * <b>Threading model:</b><br>\n     * This method must be called from the debugger control thread (not the render thread itself).\n     * The provided callable will be executed synchronously within the render thread context, ensuring\n     * compatibility with request-bound state.\n     * </p>\n     *\n     * @param action the task to execute within the render thread context.\n     *        It should return a {@link CompletableFuture} representing the computation result.\n     * @return a {@link CompletableFuture} containing the result of the executed task\n     * @throws IllegalStateException if the thread is not currently suspended or if another\n     *         evaluation task is already pending\n     */\n    public CompletableFuture<Object> evaluateInRenderThread(Callable<CompletableFuture<Object>> action) {\n        synchronized (lock) {\n            // Ensure the render thread is suspended before executing any task\n            if (this.state != DebuggerState.SUSPENDED) {\n                throw new IllegalStateException(\"Thread not suspended\");\n            }\n\n            // Prevent concurrent evaluation tasks from overlapping\n            if (pendingTask != null) {\n                throw new IllegalStateException(\"A task is already pending\");\n            }\n\n            // Schedule the evaluation task to be executed by the render thread\n            pendingTask = action;\n            taskResult = null;\n\n            // Wake up the suspended render thread so it can pick up and execute the task\n            lock.notifyAll();\n\n            // Wait for the render thread to process and complete the pending task\n            boolean intr = false;\n            try {\n                while (pendingTask != null) {","sourceCodeStart":344,"sourceCodeEnd":380,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/agent/RemoteThread.java#L344-L380","documentation":"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.","triggerScenarios":"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.","commonSituations":"Debug clients sending evaluateRequests immediately after attach; evaluate calls racing with a continue; tests invoking evaluation before triggering a suspension.","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."],"exampleFix":"// before\nObject val = thread.evaluateInRenderThread(action).get();\n// after\nif (thread.getState() == DebuggerState.SUSPENDED) {\n    Object val = thread.evaluateInRenderThread(action).get();\n} else {\n    // wait for suspension event first\n}","handlingStrategy":"validation","validationCode":"if (thread.getState() != DebuggerState.SUSPENDED) {\n    throw new IllegalStateException(\"Wait for suspension before evaluating\");\n}\nObject v = thread.evaluateInRenderThread(action).get();","typeGuard":"boolean canEvaluate(RemoteThread t) {\n    return t.getState() == DebuggerState.SUSPENDED;\n}","tryCatchPattern":"try {\n    return thread.evaluateInRenderThread(action).get();\n} catch (IllegalStateException e) {\n    if (\"Thread not suspended\".equals(e.getMessage())) {\n        // wait for suspension event, then retry once\n    }\n    throw e;\n}","preventionTips":["Gate evaluate calls on the thread-suspended event","Never evaluate against a running render thread","In DAP clients, bind evaluate to the stopped state"],"tags":["qute","debugger","evaluate","illegal-state","suspension"],"backgroundTag":"thread-not-suspended","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}