Skip to content

Commit e0c46d5

Browse files
author
Viktor Klang
committedSep 3, 2024
8325397: sun/java2d/Disposer/TestDisposerRace.java fails in linux-aarch64
Reviewed-by: alanb
1 parent b94c3de commit e0c46d5

File tree

3 files changed

+126
-23
lines changed

3 files changed

+126
-23
lines changed
 

‎src/java.base/share/classes/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java

+39-5
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,40 @@ private static void signalNextIfShared(Node h) {
277277
}
278278
}
279279

280+
/**
281+
* Repeatedly invokes acquire, if its execution throws an Error or a Runtime Exception,
282+
* using an Unsafe.park-based backoff
283+
* @param node which to reacquire
284+
* @param arg the acquire argument
285+
*/
286+
private final void reacquire(Node node, long arg) {
287+
try {
288+
acquire(node, arg, false, false, false, 0L);
289+
} catch (Error | RuntimeException firstEx) {
290+
// While we currently do not emit an JFR events in this situation, mainly
291+
// because the conditions under which this happens are such that it
292+
// cannot be presumed to be possible to actually allocate an event, and
293+
// using a preconstructed one would have limited value in serviceability.
294+
// Having said that, the following place would be the more appropriate
295+
// place to put such logic:
296+
// emit JFR event
297+
298+
for (long nanos = 1L;;) {
299+
U.park(false, nanos); // must use Unsafe park to sleep
300+
if (nanos < 1L << 30) // max about 1 second
301+
nanos <<= 1;
302+
303+
try {
304+
acquire(node, arg, false, false, false, 0L);
305+
} catch (Error | RuntimeException ignored) {
306+
continue;
307+
}
308+
309+
throw firstEx;
310+
}
311+
}
312+
}
313+
280314
/**
281315
* Main acquire method, invoked by all exported acquire methods.
282316
*
@@ -1299,7 +1333,7 @@ else if ((node.status & COND) != 0) {
12991333
}
13001334
LockSupport.setCurrentBlocker(null);
13011335
node.clearStatus();
1302-
acquire(node, savedState, false, false, false, 0L);
1336+
reacquire(node, savedState);
13031337
if (interrupted)
13041338
Thread.currentThread().interrupt();
13051339
}
@@ -1346,7 +1380,7 @@ public final void await() throws InterruptedException {
13461380
}
13471381
LockSupport.setCurrentBlocker(null);
13481382
node.clearStatus();
1349-
acquire(node, savedState, false, false, false, 0L);
1383+
reacquire(node, savedState);
13501384
if (interrupted) {
13511385
if (cancelled) {
13521386
unlinkCancelledWaiters(node);
@@ -1389,7 +1423,7 @@ public final long awaitNanos(long nanosTimeout)
13891423
LockSupport.parkNanos(this, nanos);
13901424
}
13911425
node.clearStatus();
1392-
acquire(node, savedState, false, false, false, 0L);
1426+
reacquire(node, savedState);
13931427
if (cancelled) {
13941428
unlinkCancelledWaiters(node);
13951429
if (interrupted)
@@ -1433,7 +1467,7 @@ public final boolean awaitUntil(Date deadline)
14331467
LockSupport.parkUntil(this, abstime);
14341468
}
14351469
node.clearStatus();
1436-
acquire(node, savedState, false, false, false, 0L);
1470+
reacquire(node, savedState);
14371471
if (cancelled) {
14381472
unlinkCancelledWaiters(node);
14391473
if (interrupted)
@@ -1478,7 +1512,7 @@ public final boolean await(long time, TimeUnit unit)
14781512
LockSupport.parkNanos(this, nanos);
14791513
}
14801514
node.clearStatus();
1481-
acquire(node, savedState, false, false, false, 0L);
1515+
reacquire(node, savedState);
14821516
if (cancelled) {
14831517
unlinkCancelledWaiters(node);
14841518
if (interrupted)

‎src/java.base/share/classes/java/util/concurrent/locks/AbstractQueuedSynchronizer.java

+39-5
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,40 @@ private static void signalNextIfShared(Node h) {
656656
}
657657
}
658658

659+
/**
660+
* Repeatedly invokes acquire, if its execution throws an Error or a Runtime Exception,
661+
* using an Unsafe.park-based backoff
662+
* @param node which to reacquire
663+
* @param arg the acquire argument
664+
*/
665+
private final void reacquire(Node node, int arg) {
666+
try {
667+
acquire(node, arg, false, false, false, 0L);
668+
} catch (Error | RuntimeException firstEx) {
669+
// While we currently do not emit an JFR events in this situation, mainly
670+
// because the conditions under which this happens are such that it
671+
// cannot be presumed to be possible to actually allocate an event, and
672+
// using a preconstructed one would have limited value in serviceability.
673+
// Having said that, the following place would be the more appropriate
674+
// place to put such logic:
675+
// emit JFR event
676+
677+
for (long nanos = 1L;;) {
678+
U.park(false, nanos); // must use Unsafe park to sleep
679+
if (nanos < 1L << 30) // max about 1 second
680+
nanos <<= 1;
681+
682+
try {
683+
acquire(node, arg, false, false, false, 0L);
684+
} catch (Error | RuntimeException ignored) {
685+
continue;
686+
}
687+
688+
throw firstEx;
689+
}
690+
}
691+
}
692+
659693
/**
660694
* Main acquire method, invoked by all exported acquire methods.
661695
*
@@ -1678,7 +1712,7 @@ else if ((node.status & COND) != 0) {
16781712
}
16791713
LockSupport.setCurrentBlocker(null);
16801714
node.clearStatus();
1681-
acquire(node, savedState, false, false, false, 0L);
1715+
reacquire(node, savedState);
16821716
if (interrupted)
16831717
Thread.currentThread().interrupt();
16841718
}
@@ -1725,7 +1759,7 @@ public final void await() throws InterruptedException {
17251759
}
17261760
LockSupport.setCurrentBlocker(null);
17271761
node.clearStatus();
1728-
acquire(node, savedState, false, false, false, 0L);
1762+
reacquire(node, savedState);
17291763
if (interrupted) {
17301764
if (cancelled) {
17311765
unlinkCancelledWaiters(node);
@@ -1768,7 +1802,7 @@ public final long awaitNanos(long nanosTimeout)
17681802
LockSupport.parkNanos(this, nanos);
17691803
}
17701804
node.clearStatus();
1771-
acquire(node, savedState, false, false, false, 0L);
1805+
reacquire(node, savedState);
17721806
if (cancelled) {
17731807
unlinkCancelledWaiters(node);
17741808
if (interrupted)
@@ -1812,7 +1846,7 @@ public final boolean awaitUntil(Date deadline)
18121846
LockSupport.parkUntil(this, abstime);
18131847
}
18141848
node.clearStatus();
1815-
acquire(node, savedState, false, false, false, 0L);
1849+
reacquire(node, savedState);
18161850
if (cancelled) {
18171851
unlinkCancelledWaiters(node);
18181852
if (interrupted)
@@ -1857,7 +1891,7 @@ public final boolean await(long time, TimeUnit unit)
18571891
LockSupport.parkNanos(this, nanos);
18581892
}
18591893
node.clearStatus();
1860-
acquire(node, savedState, false, false, false, 0L);
1894+
reacquire(node, savedState);
18611895
if (cancelled) {
18621896
unlinkCancelledWaiters(node);
18631897
if (interrupted)

‎test/jdk/sun/java2d/Disposer/TestDisposerRace.java

+48-13
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.LinkedList;
2424
import java.util.List;
2525
import java.util.concurrent.atomic.AtomicInteger;
26+
import java.util.function.Supplier;
2627
import javax.swing.SwingUtilities;
2728

2829
import sun.java2d.Disposer;
@@ -39,31 +40,38 @@ public final class TestDisposerRace {
3940
private static final AtomicInteger recordsCount = new AtomicInteger();
4041
private static volatile boolean disposerDone = false;
4142

43+
private static final String KO_OVERFLOW = "Some records have not been disposed!";
44+
private static final String KO_UNDERFLOW = "Disposed more records than were added!";
45+
4246
public static void main(String[] args) throws Exception {
43-
TestDisposerRace test = new TestDisposerRace();
44-
test.run();
47+
new TestDisposerRace().run();
4548

4649
checkRecordsCountIsSane();
4750
if (recordsCount.get() > 0) {
51+
System.err.println(KO_OVERFLOW); // In case the next line fails to allocate due to OOME
4852
throw new RuntimeException("Some records (" + recordsCount + ") have not been disposed");
4953
}
5054
}
5155

56+
interface ThrowingRunnable<E extends Exception> {
57+
void run() throws E;
58+
}
59+
5260
TestDisposerRace() {
5361
addRecordsToDisposer(30_000);
5462
}
5563

5664
void run() throws Exception {
5765
generateOOME();
5866
for (int i = 0; i < 1000; ++i) {
59-
SwingUtilities.invokeAndWait(Disposer::pollRemove);
60-
if (i % 10 == 0) {
61-
// Adding records will race with the diposer trying to remove them
67+
retryOnOOME(() -> SwingUtilities.invokeAndWait(Disposer::pollRemove));
68+
69+
// Adding records will race with the diposer trying to remove them
70+
if (i % 10 == 0)
6271
addRecordsToDisposer(1000);
63-
}
6472
}
6573

66-
Disposer.addObjectRecord(new Object(), new FinalDisposerRecord());
74+
retryOnOOME(() -> Disposer.addObjectRecord(new Object(), new FinalDisposerRecord()));
6775

6876
while (!disposerDone) {
6977
generateOOME();
@@ -72,18 +80,45 @@ void run() throws Exception {
7280

7381
private static void checkRecordsCountIsSane() {
7482
if (recordsCount.get() < 0) {
75-
throw new RuntimeException("Disposed more records than were added");
83+
throw new RuntimeException(KO_UNDERFLOW);
84+
}
85+
}
86+
87+
private static <T> T retryOnOOME(Supplier<T> allocator) {
88+
for(;;) {
89+
try {
90+
return allocator.get();
91+
} catch (OutOfMemoryError ignored1) {
92+
try {
93+
Thread.sleep(1); // Give GC a little chance to run
94+
} catch (InterruptedException ignored2) {}
95+
}
96+
}
97+
}
98+
99+
private static <E extends Exception> void retryOnOOME(ThrowingRunnable<E> tr) throws E {
100+
for(;;) {
101+
try {
102+
tr.run();
103+
break;
104+
} catch (OutOfMemoryError ignored1) {
105+
try {
106+
Thread.sleep(1); // Give GC a little chance to run
107+
} catch (InterruptedException ignored2) {}
108+
}
76109
}
77110
}
78111

79112
private void addRecordsToDisposer(int count) {
80113
checkRecordsCountIsSane();
81114

82-
recordsCount.addAndGet(count);
115+
MyDisposerRecord disposerRecord = retryOnOOME(MyDisposerRecord::new);
83116

84-
MyDisposerRecord disposerRecord = new MyDisposerRecord();
85-
for (int i = 0; i < count; i++) {
86-
Disposer.addObjectRecord(new Object(), disposerRecord);
117+
while(count > 0) {
118+
recordsCount.incrementAndGet(); // pre-add to make sure it doesn't go negative
119+
var o = retryOnOOME(Object::new);
120+
retryOnOOME(() -> Disposer.addObjectRecord(o, disposerRecord));
121+
--count;
87122
}
88123
}
89124

@@ -106,8 +141,8 @@ private static void giveGCAChance() {
106141
}
107142

108143
private static void generateOOME() throws Exception {
109-
final List<Object> leak = new LinkedList<>();
110144
try {
145+
final List<Object> leak = new LinkedList<>();
111146
while (true) {
112147
leak.add(new byte[1024 * 1024]);
113148
}

0 commit comments

Comments
 (0)
Please sign in to comment.