oracle/graal · error · IllegalMonitorStateException
Cannot signal lock that is not held
Error message
Cannot signal lock that is not held
What it means
EspressoLock.signal() (the guest-side Condition.signal / Object.notify path) verifies the lock is held by the current thread before signalling waiters; if not, it throws IllegalMonitorStateException with the message 'Cannot signal lock that is not held'.
Source
Thrown at espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/blocking/EspressoLock.java:369
enterWaitInterruptible(interruptible);
return interruptible.getResult();
}
@Override
public boolean awaitUntil(Date deadline) throws GuestInterruptedException {
if (!isHeldByCurrentThread()) {
throw new IllegalMonitorStateException();
}
WaitUntilInterruptible interruptible = new WaitUntilInterruptible(deadline);
enterWaitInterruptible(interruptible);
return interruptible.getResult();
}
@Override
@TruffleBoundary
public void signal() {
if (!isHeldByCurrentThread()) {
throw new IllegalMonitorStateException("Cannot signal lock that is not held");
}
ensureWaitLockInitialized();
waitLock.lock();
try {
signals = Math.min(signals + 1, waiters);
waitCondition.signal();
} finally {
waitLock.unlock();
}
}
@Override
@TruffleBoundary
public void signalAll() {
if (!isHeldByCurrentThread()) {
throw new IllegalMonitorStateException("Cannot signal lock that is not held");
}
ensureWaitLockInitialized();View on GitHub (pinned to a66e9ccd1d)
Solutions
- Acquire the lock (synchronized block or lock.lock()) before signal/notify.
- Keep notify/signal inside the same critical section that mutates the waited-upon state.
- Use @TruffleBoundary-aware host code paths rather than ad-hoc signalling from arbitrary threads.
Example fix
// before
sharedState = value;
obj.notify();
// after
synchronized (obj) {
sharedState = value;
obj.notify();
} Defensive patterns
Strategy: validation
Validate before calling
if (lock.isHeldByCurrentThread()) {
lock.signal();
} Try / catch
synchronized (obj) {
try { obj.notify(); }
catch (IllegalMonitorStateException e) { throw e; /* scope bug */ }
} Prevention
- Signal only from within the critical section that changed the state.
- Check isHeldByCurrentThread() in debug builds before signal paths.
When it happens
Trigger: Calling signal()/notify() without holding the corresponding EspressoLock - e.g. notify() outside synchronized, or signal() after lock release.
Common situations: Producer/consumer code where notify is in a different method than the synchronized block; refactoring that split lock scope; callback invoked from another thread that never acquired the lock.
Related errors
- OSR with locks disabled.
- Unbalanced monitor enter-exit in OSR compilation with locks:
- Espresso cannot evaluate Java sources directly, only a few s
- --java.SpecCompliance: Mode can be 'strict' or 'hotspot'.
- -Xverify: Mode can be 'none', 'remote' or 'all'.
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/f380e76b95076569.
Report an issue: GitHub.