oracle/graal · error · RuntimeException
Could not detach thread correctly
Error message
Could not detach thread correctly
What it means
Thrown by EspressoContext.disposeThread when the underlying JNI call DetachCurrentThread does not return JNI_OK. Espresso (Java-on-Truffle) attaches host threads to the guest VM; detaching releases guest-side thread state (JNI env, monitors, TLS). If the native detach fails, the runtime cannot guarantee a clean teardown, so it throws a RuntimeException after which the thread is still unregistered in a finally block.
Source
Thrown at espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/EspressoContext.java:1077
}
public void disposeThread(Thread hostThread) {
StaticObject guestThread = getGuestThreadFromHost(hostThread);
if (guestThread == null) {
return;
}
try {
// Cannot run guest code after finalizeContext was called (GR-35712).
if (isFinalized()) {
return;
}
if (hostThread != Thread.currentThread()) {
String guestName = threads.getThreadName(guestThread);
getLogger().warning("unimplemented: disposeThread for non-current thread: " + hostThread + " / " + guestName + ". Called from thread: " + Thread.currentThread());
return;
}
if (vm.DetachCurrentThread(this, getLanguage()) != JNI_OK) {
throw new RuntimeException("Could not detach thread correctly");
}
} finally {
unregisterThread(guestThread);
}
}
public StaticObject getGuestThreadFromHost(Thread host) {
return espressoEnv.getThreadRegistry().getGuestThreadFromHost(host);
}
public void registerCurrentThread(StaticObject guestThread) {
getLanguage().getThreadLocalState().initializeCurrentThread(guestThread);
}
public StaticObject getCurrentPlatformThread() {
return getLanguage().getThreadLocalState().getCurrentPlatformThread(this);
}
View on GitHub (pinned to a66e9ccd1d)
Solutions
- Ensure the thread that disposes is the same host thread that attached: call disposeThread from within the attached thread before it exits.
- Detach/attach guest threads symmetrically: pair every attach with exactly one dispose, and do not dispose twice.
- Avoid closing the polyglot Context while guest threads are still running guest code; join guest threads first, then close.
- If the failure reproduces, collect a repro and report it to the GraalVM Espresso project (https://github.com/oracle/graal/issues) since JNI_OK failure paths usually indicate a VM bug.
Example fix
// before (wrong thread disposes)
Thread t = Thread.attach(context);
// ... later, from main thread:
context.disposeThread(t); // warns and skips, or detach races
// after (thread disposes itself)
try {
// work on guest thread
} finally {
context.disposeThread(t); // same host thread
} Defensive patterns
Strategy: try-catch
Validate before calling
// Before disposing, confirm same host thread to avoid the detach path entirely
if (thread == Thread.currentThread() && !context.isFinalized()) {
context.disposeThread(thread);
} Try / catch
try {
context.disposeThread(thread);
} catch (RuntimeException e) {
if (e.getMessage().contains("Could not detach thread")) {
// log and continue; thread is already unregistered in finally
} else {
throw e;
}
} Prevention
- Dispose guest threads from the thread itself before it exits.
- Join all guest threads before closing the Context.
- Pair every attach with exactly one dispose; never dispose twice.
When it happens
Trigger: Calling context.disposeThread() (or polyglot Context.close() / Engine cleanup that disposes attached threads) when the current thread's JNI detach fails: e.g. detach of a thread that still holds guest monitors, a thread attached via a different JNI version, or shutdown racing with finalizeContext/thread teardown.
Common situations: Polyglot embedding where user code attaches threads with Thread.attach(...) and closes contexts concurrently; teardown during context finalization (GR-35712 related races); running with native access restrictions that make DetachCurrentThread fail; calling disposeThread from a different thread (which is separately warned about, not thrown).
Related errors
- Could not parse bytes for type annotations
- The VM does not expose the required Graal capability %s.
- Label used by instructions at following offsets has not been
- Calling unimplemented mokapot %s
- expected %s, got %s
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/2990ce7f2350b161.
Report an issue: GitHub.