RFR: 8289208: Test DrawRotatedStringUsingRotatedFont.java occasionally crashes on MacOS

Maxim Kartashev mkartashev at openjdk.org
Mon Jul 11 10:04:33 UTC 2022


On Fri, 8 Jul 2022 22:58:08 GMT, Phil Race <prr at openjdk.org> wrote:

> The code that disposes on the rendering thread is invoked from a dispose() method
that was on the Disposer thread. It then waits for that to finish.
At that time the Disposer thread is blocked so not doing anything and the render
thread is free to add elements to deferredRecords.

Correct. Now let's suppose that the rendering thread - unblocked at the time - is adding more records such that `ArrayList` needs to grow. Now `ArrayList` starts relocating its underlying storage array at the time as `dispose()` returns and we're ready for the next iteration of 

for (int i=0;i<deferredRecords.size(); i++) {
``` 
loop. The next call to `ArrayList.get()` here

DisposerRecord rec = deferredRecords.get(i);

will be executed on the disposer thread simultaneously with the array relocation of `deferredRecords`. So some of the `get()` calls will return `null` as they read from initialized, but yet-to-be-relocated chunks, and some will read what essentially is garbage.

> Second if it is just about MT access to deferredRecords and pulling elements off
that queue then why aren't we getting a Java exception about concurrent modification
instead of a crash.

The race was between `ArrayList.get()` called from `clearDeferredRecords()` on a dedicated disposer thread and `ArrayList.add()` called from `pollRemove()` on a _different_ thread. AFAIK there are no safeguards against concurrent modification between those two methods (I check the implementation).

> And if we do somehow have two threads that end up trying to free the same data ie both executing the dispose() method of an instance

I didn't imply that `dispose()` was called twice on the same instance. I said "probably due to double-free" (or "bad" free in the bug description) simply because these are the checks that are usually implemented in the heap alloc/dealloc routines, so "double-free" is likely with the second free called from one of the `dispose()` methods; I have no idea who called the first `free()` and when.

> BTW when we run this test since it is a headless test we would never have accelerated
glyphs .. and the same is true of your new tests .. so I imagine in our test harness
and that of others too they'll all always pass.

Are you implying that `test/jdk/java/awt/Graphics2D/DrawString/DisposerTest.java` is useless in the headless mode? Then I can mark it as `headful`. I can also simply drop it.

Be that as it may, `test/jdk/sun/java2d/Disposer/TestDisposerRace.java` doesn't depend on any acceleration and is as removed from any 2D code as it can be. And it also shows the problem on Linux, making the one mentioned above somewhat superfluous.

-------------

PR: https://git.openjdk.org/jdk/pull/9362



More information about the client-libs-dev mailing list