Impossible (?) code path resulting in IllegalStateException on jdk14+
David Holmes
david.holmes at oracle.com
Mon Dec 14 02:57:32 UTC 2020
Hi Martin,
On 14/12/2020 12:48 pm, Martin Buchholz wrote:
> 1. JDK-8258187 <https://bugs.openjdk.java.net/browse/JDK-8258187>
>
>
> IllegalMonitorStateException in ArrayBlockingQueue
>
>
>
> It's surprising that you can have a repro which is essentially just
> simple producer-consumer. Can we remove the forkjoinpool from the repro
> (just threads?)
AFAICS the FJP is critical here as it is the unexpected exception from
managedBlock() that causes the problem.
Cheers,
David
> On Sun, Dec 13, 2020 at 6:28 PM David Holmes <david.holmes at oracle.com
> <mailto:david.holmes at oracle.com>> wrote:
>
> Hi Dawid,
>
> This appears to be a bug in AbstractQueuedSynchronizer and FJP
> interaction, so cc'ing core-libs as this is not a hotspot issue. Also
> cc'ing Martin and Doug as this is a j.u.c bug.
>
> Cheers,
> David
>
> On 12/12/2020 12:56 am, Dawid Weiss wrote:
> > 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
> <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 <mailto: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 core-libs-dev
mailing list