Impossible (?) code path resulting in IllegalStateException on jdk14+

Dawid Weiss dawid.weiss at gmail.com
Fri Dec 11 14:56:36 UTC 2020


So, I know what this is. Sort of.

This isn't a compiler error (sigh of relief). It's a kind of interplay
between parallel streams (which uses the common ForkJoinPool) and the
queue's blocking operations.

In our code streams use an op closure which sends items to an
ArrayBlockingQueue. This queue is being drained by a separate thread.
Everything works up until a certain moment when this happens on
queue.put() -- I've modified JDK 16 source code and recompiled it to
see what's happening:

Suppressed: java.util.concurrent.RejectedExecutionException: Thread
limit exceeded replacing blocked worker
at java.base/java.util.concurrent.ForkJoinPool.tryCompensate(ForkJoinPool.java:1579)
at java.base/java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3124)
at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1614)
at java.base/java.util.concurrent.ArrayBlockingQueue.put(ArrayBlockingQueue.java:371)

This exception causes the try-finally block in ArrayBlockingQueue to
hit the finally block unexpectedly (without the attempt to re-acquire
the lock!), eventually leading to the original odd exception I
reported:

Caused by: java.lang.IllegalMonitorStateException
at java.base/java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:175)
at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1006)
at java.base/java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:494)
at java.base/java.util.concurrent.ArrayBlockingQueue.put(ArrayBlockingQueue.java:378)

This is then propagated up to the caller of queue.put()

A full simplified repro (without streams) is here:
https://gist.github.com/dweiss/4787b7aa503ef5702e94d73178c3c842

When you run it under JDK14+, it'll result in:

java.lang.IllegalMonitorStateException
        at java.base/java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:175)
...

This code works on JDK11, by the way. What I find a bit
counterintuitive is that the original test (code) doesn't make any
explicit use of ForkJoinPool - it just collects items from a parallel
stream and involves throwing random exceptions from ops on that
stream... This was a bit unexpected.

Dawid


On Thu, Dec 10, 2020 at 5:25 PM Dawid Weiss <dawid.weiss at gmail.com> wrote:
>
> Hello,
>
> I'm scratching my head again over a bug we encountered in randomized
> tests. The code is quite complex so before I start to try to isolate I
> thought I'd ask if it rings a bell for anybody.
>
> The bug is reproducible for certain seeds but happens only after some
> VM warmup - the same test is executed a few dozen times, then the
> problem starts showing up. This only happens on jdk 14 and jdk 15
> (didn't test on jdk 16 branch). Looks like something related to OSR/
> compilation races.
>
> In the end, we get this exception:
>
> java.lang.IllegalMonitorStateException
> at java.base/java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:175)
> at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1006)
> at java.base/java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:494)
> at java.base/java.util.concurrent.ArrayBlockingQueue.put(ArrayBlockingQueue.java:373)
> [stack omitted]
>
> This doesn't seem possible from Java code alone -- it's this snippet
> in ArrayBlockingQueue:
>
> lock.lockInterruptibly();
> try {
>     while (count == items.length)
>         notFull.await();
>     enqueue(e);
> } finally {
>    lock.unlock();     // <<< bam...
> }
>
> If the code entered the lock-protected block it should never throw
> IllegalMonitorStateException, right?
>
> I'll start digging in spare moments but hints at how to isolate this/
> verify what this can be (other than bisecting git repo) would be very
> welcome!
>
> Dawid


More information about the hotspot-dev mailing list