Fixes a livelock in AbstractQueuedSynchronizer

This commit is contained in:
Viktor Klang 2026-06-08 16:38:35 +02:00
parent 21482fa7db
commit 1f0ce1e735
2 changed files with 39 additions and 1 deletions

View File

@ -832,7 +832,8 @@ public abstract class AbstractQueuedSynchronizer
if (q.status < 0) { // cancelled
if ((s == null ? casTail(q, p) : s.casPrev(q, p)) &&
q.prev == p) {
p.casNext(q, s); // OK if fails
if (s != null)
p.casNext(q, s); // OK if fails
if (p.prev == null)
signalNext(p);
}

View File

@ -33,11 +33,15 @@
* Pat Fisher, Mike Judd.
*/
import static java.util.concurrent.TimeUnit.MICROSECONDS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
import junit.framework.Test;
import junit.framework.TestSuite;
@ -672,4 +676,37 @@ public class SemaphoreTest extends JSR166TestCase {
assertTrue(s.toString().contains("Permits = -2"));
}
/**
* test scenario for JDK-8386085
*/
public void testShortTimeoutAcquisition() throws InterruptedException {
final int width = Runtime.getRuntime().availableProcessors();
try (var pool = Executors.newFixedThreadPool(width)) {
// Setup
final AtomicBoolean done = new AtomicBoolean(false);
final CountDownLatch waitingToRun = new CountDownLatch(width);
final Semaphore s = new Semaphore(0);
final Callable<Void> c = () -> {
waitingToRun.countDown();
do {
s.tryAcquire(1, MICROSECONDS); // acquisition storm
} while (!done.get());
return null;
};
// Task creation
for(int i = 0; i < width; ++i)
pool.submit(c);
waitingToRun.await(); // Wait for all threads to have launched their tasks
Thread.sleep(3000); // Give the workers a bit of time to run acquisitions
s.release(width); // Hand out permits
Thread.sleep(1000); // Give the workers a bit of time to react to the availability of permits
final int permitsAvailable = s.availablePermits();
done.set(true); // Try to make sure that for the successful cases the workers can exit cleanly
assertTrue(permitsAvailable < width); // Some permits should've been taken
}
}
}