Skip to content

8325397: sun/java2d/Disposer/TestDisposerRace.java fails in linux-aarch64 #2103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,40 @@ private static void signalNextIfShared(Node h) {
}
}

/**
* Repeatedly invokes acquire, if its execution throws an Error or a Runtime Exception,
* using an Unsafe.park-based backoff
* @param node which to reacquire
* @param arg the acquire argument
*/
private final void reacquire(Node node, long arg) {
try {
acquire(node, arg, false, false, false, 0L);
} catch (Error | RuntimeException firstEx) {
// While we currently do not emit an JFR events in this situation, mainly
// because the conditions under which this happens are such that it
// cannot be presumed to be possible to actually allocate an event, and
// using a preconstructed one would have limited value in serviceability.
// Having said that, the following place would be the more appropriate
// place to put such logic:
// emit JFR event

for (long nanos = 1L;;) {
U.park(false, nanos); // must use Unsafe park to sleep
if (nanos < 1L << 30) // max about 1 second
nanos <<= 1;

try {
acquire(node, arg, false, false, false, 0L);
} catch (Error | RuntimeException ignored) {
continue;
}

throw firstEx;
}
}
}

/**
* Main acquire method, invoked by all exported acquire methods.
*
Expand Down Expand Up @@ -1294,7 +1328,7 @@ else if ((node.status & COND) != 0) {
}
LockSupport.setCurrentBlocker(null);
node.clearStatus();
acquire(node, savedState, false, false, false, 0L);
reacquire(node, savedState);
if (interrupted)
Thread.currentThread().interrupt();
}
Expand Down Expand Up @@ -1341,7 +1375,7 @@ public final void await() throws InterruptedException {
}
LockSupport.setCurrentBlocker(null);
node.clearStatus();
acquire(node, savedState, false, false, false, 0L);
reacquire(node, savedState);
if (interrupted) {
if (cancelled) {
unlinkCancelledWaiters(node);
Expand Down Expand Up @@ -1384,7 +1418,7 @@ public final long awaitNanos(long nanosTimeout)
LockSupport.parkNanos(this, nanos);
}
node.clearStatus();
acquire(node, savedState, false, false, false, 0L);
reacquire(node, savedState);
if (cancelled) {
unlinkCancelledWaiters(node);
if (interrupted)
Expand Down Expand Up @@ -1428,7 +1462,7 @@ public final boolean awaitUntil(Date deadline)
LockSupport.parkUntil(this, abstime);
}
node.clearStatus();
acquire(node, savedState, false, false, false, 0L);
reacquire(node, savedState);
if (cancelled) {
unlinkCancelledWaiters(node);
if (interrupted)
Expand Down Expand Up @@ -1473,7 +1507,7 @@ public final boolean await(long time, TimeUnit unit)
LockSupport.parkNanos(this, nanos);
}
node.clearStatus();
acquire(node, savedState, false, false, false, 0L);
reacquire(node, savedState);
if (cancelled) {
unlinkCancelledWaiters(node);
if (interrupted)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,40 @@ private static void signalNextIfShared(Node h) {
}
}

/**
* Repeatedly invokes acquire, if its execution throws an Error or a Runtime Exception,
* using an Unsafe.park-based backoff
* @param node which to reacquire
* @param arg the acquire argument
*/
private final void reacquire(Node node, int arg) {
try {
acquire(node, arg, false, false, false, 0L);
} catch (Error | RuntimeException firstEx) {
// While we currently do not emit an JFR events in this situation, mainly
// because the conditions under which this happens are such that it
// cannot be presumed to be possible to actually allocate an event, and
// using a preconstructed one would have limited value in serviceability.
// Having said that, the following place would be the more appropriate
// place to put such logic:
// emit JFR event

for (long nanos = 1L;;) {
U.park(false, nanos); // must use Unsafe park to sleep
if (nanos < 1L << 30) // max about 1 second
nanos <<= 1;

try {
acquire(node, arg, false, false, false, 0L);
} catch (Error | RuntimeException ignored) {
continue;
}

throw firstEx;
}
}
}

/**
* Main acquire method, invoked by all exported acquire methods.
*
Expand Down Expand Up @@ -1673,7 +1707,7 @@ else if ((node.status & COND) != 0) {
}
LockSupport.setCurrentBlocker(null);
node.clearStatus();
acquire(node, savedState, false, false, false, 0L);
reacquire(node, savedState);
if (interrupted)
Thread.currentThread().interrupt();
}
Expand Down Expand Up @@ -1720,7 +1754,7 @@ public final void await() throws InterruptedException {
}
LockSupport.setCurrentBlocker(null);
node.clearStatus();
acquire(node, savedState, false, false, false, 0L);
reacquire(node, savedState);
if (interrupted) {
if (cancelled) {
unlinkCancelledWaiters(node);
Expand Down Expand Up @@ -1763,7 +1797,7 @@ public final long awaitNanos(long nanosTimeout)
LockSupport.parkNanos(this, nanos);
}
node.clearStatus();
acquire(node, savedState, false, false, false, 0L);
reacquire(node, savedState);
if (cancelled) {
unlinkCancelledWaiters(node);
if (interrupted)
Expand Down Expand Up @@ -1807,7 +1841,7 @@ public final boolean awaitUntil(Date deadline)
LockSupport.parkUntil(this, abstime);
}
node.clearStatus();
acquire(node, savedState, false, false, false, 0L);
reacquire(node, savedState);
if (cancelled) {
unlinkCancelledWaiters(node);
if (interrupted)
Expand Down Expand Up @@ -1852,7 +1886,7 @@ public final boolean await(long time, TimeUnit unit)
LockSupport.parkNanos(this, nanos);
}
node.clearStatus();
acquire(node, savedState, false, false, false, 0L);
reacquire(node, savedState);
if (cancelled) {
unlinkCancelledWaiters(node);
if (interrupted)
Expand Down
61 changes: 48 additions & 13 deletions test/jdk/sun/java2d/Disposer/TestDisposerRace.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import javax.swing.SwingUtilities;

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

private static final String KO_OVERFLOW = "Some records have not been disposed!";
private static final String KO_UNDERFLOW = "Disposed more records than were added!";

public static void main(String[] args) throws Exception {
TestDisposerRace test = new TestDisposerRace();
test.run();
new TestDisposerRace().run();

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

interface ThrowingRunnable<E extends Exception> {
void run() throws E;
}

TestDisposerRace() {
addRecordsToDisposer(30_000);
}

void run() throws Exception {
generateOOME();
for (int i = 0; i < 1000; ++i) {
SwingUtilities.invokeAndWait(Disposer::pollRemove);
if (i % 10 == 0) {
// Adding records will race with the diposer trying to remove them
retryOnOOME(() -> SwingUtilities.invokeAndWait(Disposer::pollRemove));

// Adding records will race with the diposer trying to remove them
if (i % 10 == 0)
addRecordsToDisposer(1000);
}
}

Disposer.addObjectRecord(new Object(), new FinalDisposerRecord());
retryOnOOME(() -> Disposer.addObjectRecord(new Object(), new FinalDisposerRecord()));

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

private static void checkRecordsCountIsSane() {
if (recordsCount.get() < 0) {
throw new RuntimeException("Disposed more records than were added");
throw new RuntimeException(KO_UNDERFLOW);
}
}

private static <T> T retryOnOOME(Supplier<T> allocator) {
for(;;) {
try {
return allocator.get();
} catch (OutOfMemoryError ignored1) {
try {
Thread.sleep(1); // Give GC a little chance to run
} catch (InterruptedException ignored2) {}
}
}
}

private static <E extends Exception> void retryOnOOME(ThrowingRunnable<E> tr) throws E {
for(;;) {
try {
tr.run();
break;
} catch (OutOfMemoryError ignored1) {
try {
Thread.sleep(1); // Give GC a little chance to run
} catch (InterruptedException ignored2) {}
}
}
}

private void addRecordsToDisposer(int count) {
checkRecordsCountIsSane();

recordsCount.addAndGet(count);
MyDisposerRecord disposerRecord = retryOnOOME(MyDisposerRecord::new);

MyDisposerRecord disposerRecord = new MyDisposerRecord();
for (int i = 0; i < count; i++) {
Disposer.addObjectRecord(new Object(), disposerRecord);
while(count > 0) {
recordsCount.incrementAndGet(); // pre-add to make sure it doesn't go negative
var o = retryOnOOME(Object::new);
retryOnOOME(() -> Disposer.addObjectRecord(o, disposerRecord));
--count;
}
}

Expand All @@ -106,8 +141,8 @@ private static void giveGCAChance() {
}

private static void generateOOME() throws Exception {
final List<Object> leak = new LinkedList<>();
try {
final List<Object> leak = new LinkedList<>();
while (true) {
leak.add(new byte[1024 * 1024]);
}
Expand Down