{"id":"ed6bf6e21579fff1","repo":"junit-team/junit5","slug":"not-on-a-worker-thread","errorCode":null,"errorMessage":"Not on a worker thread","messagePattern":"Not on a worker thread","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/WorkerThreadPoolHierarchicalTestExecutorService.java","lineNumber":277,"sourceCode":"\n\t\t@Nullable\n\t\tWorkerLease workerLease;\n\n\t\tWorkerThread(Runnable runnable, String name) {\n\t\t\tsuper(runnable, name);\n\t\t}\n\n\t\tstatic @Nullable WorkerThread get() {\n\t\t\tif (Thread.currentThread() instanceof WorkerThread workerThread) {\n\t\t\t\treturn workerThread;\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\n\t\tstatic WorkerThread getOrThrow() {\n\t\t\tvar workerThread = get();\n\t\t\tif (workerThread == null) {\n\t\t\t\tthrow new IllegalStateException(\"Not on a worker thread\");\n\t\t\t}\n\t\t\treturn workerThread;\n\t\t}\n\n\t\tWorkerThreadPoolHierarchicalTestExecutorService executor() {\n\t\t\treturn WorkerThreadPoolHierarchicalTestExecutorService.this;\n\t\t}\n\n\t\tvoid processQueueEntries(WorkerLease workerLease, BooleanSupplier doneCondition) {\n\t\t\tthis.workerLease = workerLease;\n\t\t\twhile (!executor.isShutdown()) {\n\t\t\t\tif (doneCondition.getAsBoolean()) {\n\t\t\t\t\tlogger.trace(() -> \"yielding resource lock\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (workQueue.isEmpty()) {\n\t\t\t\t\tlogger.trace(() -> \"no queue entries available\");\n\t\t\t\t\tbreak;","sourceCodeStart":259,"sourceCodeEnd":295,"githubUrl":"https://github.com/junit-team/junit5/blob/956246301e03d757539f7b76dd5a91f298637563/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/WorkerThreadPoolHierarchicalTestExecutorService.java#L259-L295","documentation":"Thrown by WorkerThread.getOrThrow() (line 274-280) as an IllegalStateException when Thread.currentThread() is not a WorkerThread of this executor. The WorkerThreadPool executor requires invokeAll() to be called from inside a worker thread it owns (documented at line 169-184); calling from any other thread violates the work-stealing invariants.","triggerScenarios":"Invoking WorkerThreadPoolHierarchicalTestExecutorService.invokeAll(...) from the main thread, a ForkJoinPool thread, or any thread that is not a WorkerThread belonging to this executor. Also reachable via WorkerThread.getOrThrow() inside processQueueEntries/runBlocking paths if somehow invoked off-worker.","commonSituations":"A custom Node.Task or extension that captures the HierarchicalTestExecutorService and calls invokeAll from a separate (non-worker) thread; mixing the WorkerThreadPool executor with code that spawns its own threads to drive test tasks; bugs in engines that submit tasks from outside the executor.","solutions":["Never call invokeAll from outside a worker thread; submit tasks via submit(TestTask) which is safe to call from any thread (line 150-167).","If you implement Node.execute, run child tasks synchronously or via the framework-provided executor, not on threads you create.","Ensure your custom engine does not bypass the executor by invoking tasks on raw threads.","Switch the offending code path to submit() and let the executor schedule the work."],"exampleFix":"// before: calling invokeAll from a non-worker thread\nexecutorService.invokeAll(childTasks); // throws 'Not on a worker thread'\n\n// after: submit the parent task and let it invokeAll from within a worker\nexecutorService.submit(parentTestTask); // parent's execute() may call invokeAll legally","handlingStrategy":"validation","validationCode":"// WorkerThread is a nested type; expose a check via your engine if needed.\n// Prefer: only call invokeAll from within a Node.Task submitted via submit().\nboolean onWorker = Thread.currentThread().getClass().getName().endsWith(\"WorkerThread\");","typeGuard":null,"tryCatchPattern":"try {\n    executorService.invokeAll(tasks);\n} catch (IllegalStateException e) {\n    if (e.getMessage().equals(\"Not on a worker thread\")) {\n        // re-submit a parent task instead\n        executorService.submit(wrapAsParentTask(tasks));\n        return;\n    }\n    throw e;\n}","preventionTips":["Only call invokeAll from within a task submitted via submit().","Never drive test tasks on threads you create.","Implement Node.execute to let the framework schedule children."],"tags":["junit-platform","parallel-execution","worker-thread-pool","threading","engine-internal"],"analyzedSha":"956246301e03d757539f7b76dd5a91f298637563","analyzedAt":"2026-08-04T19:25:17.049Z","schemaVersion":2}