diff --git a/src/java.base/share/classes/java/util/concurrent/locks/StampedLock.java b/src/java.base/share/classes/java/util/concurrent/locks/StampedLock.java index b9f0e9340b7d9..3fbfad875d66d 100644 --- a/src/java.base/share/classes/java/util/concurrent/locks/StampedLock.java +++ b/src/java.base/share/classes/java/util/concurrent/locks/StampedLock.java @@ -289,7 +289,8 @@ public class StampedLock implements java.io.Serializable { * Nearly all of these mechanics are carried out in methods * acquireWrite and acquireRead, that, as typical of such code, * sprawl out because actions and retries rely on consistent sets - * of locally cached reads. + * of locally cached reads, and include fall-backs for exceptional + * cases including OutOfMemoryErrors and JVM exceptions. * * For an explanation of the use of acquireFence, see * http://gee.cs.oswego.edu/dl/html/j9mm.html as well as Boehm's @@ -1182,8 +1183,7 @@ private boolean casTail(Node c, Node v) { } /** tries once to CAS a new dummy node for head */ - private void tryInitializeHead() { - Node h = new WriterNode(); + private void tryInitializeHead(Node h) { if (U.compareAndSetReference(this, HEAD, null, h)) tail = h; } @@ -1203,6 +1203,7 @@ private long acquireWrite(boolean interruptible, boolean timed, long time) { boolean interrupted = false, first = false; WriterNode node = null; Node pred = null; + long nanos = 0L; for (long s, nextState;;) { if (!first && (pred = (node == null) ? null : node.prev) != null && !(first = (head == pred))) { @@ -1227,12 +1228,23 @@ private long acquireWrite(boolean interruptible, boolean timed, long time) { } return nextState; } else if (node == null) { // retry before enqueuing - node = new WriterNode(); + try { + node = new WriterNode(); + } catch (OutOfMemoryError oome) { + return spinLockOnOOME(true, interruptible, timed, time); + } } else if (pred == null) { // try to enqueue Node t = tail; node.setPrevRelaxed(t); - if (t == null) - tryInitializeHead(); + if (t == null) { // try to initialize + Node h; + try { + h = new WriterNode(); + } catch (OutOfMemoryError oome) { + return spinLockOnOOME(true, interruptible, timed, time); + } + tryInitializeHead(h); + } else if (!casTail(t, node)) node.setPrevRelaxed(null); // back out else @@ -1244,21 +1256,25 @@ else if (!casTail(t, node)) if (node.waiter == null) node.waiter = Thread.currentThread(); node.status = WAITING; - } else { - long nanos; - spins = postSpins = (byte)((postSpins << 1) | 1); - if (!timed) - LockSupport.park(this); - else if ((nanos = time - System.nanoTime()) > 0L) - LockSupport.parkNanos(this, nanos); - else - break; + } else if (!timed || (nanos = time - System.nanoTime()) > 0L) { + try { + if (!timed) + LockSupport.park(this); + else + LockSupport.parkNanos(this, nanos); + } catch (Error | RuntimeException ex) { + cancelAcquire(node); + throw ex; + } node.clearStatus(); if ((interrupted |= Thread.interrupted()) && interruptible) break; - } + spins = postSpins = (byte)((postSpins << 1) | 1); + } else + break; } - return cancelAcquire(node, interrupted); + cancelAcquire(node); + return (interrupted || Thread.interrupted()) ? INTERRUPTED : 0L; } /** @@ -1285,11 +1301,23 @@ private long acquireRead(boolean interruptible, boolean timed, long time) { if ((t == null || (tailPred = t.prev) == null) && (nextState = tryAcquireRead()) != 0L) // try now if empty return nextState; - else if (t == null) - tryInitializeHead(); + else if (t == null) { + Node h; + try { + h = new WriterNode(); + } catch (OutOfMemoryError oome) { + return spinLockOnOOME(false, interruptible, timed, time); + } + tryInitializeHead(h); + } else if (tailPred == null || !(t instanceof ReaderNode)) { - if (node == null) - node = new ReaderNode(); + if (node == null) { + try { + node = new ReaderNode(); + } catch (OutOfMemoryError oome) { + return spinLockOnOOME(false, interruptible, timed, time); + } + } if (tail == t) { node.setPrevRelaxed(t); if (casTail(t, node)) { @@ -1302,8 +1330,13 @@ else if (tailPred == null || !(t instanceof ReaderNode)) { for (boolean attached = false;;) { if (leader.status < 0 || leader.prev == null) break; - else if (node == null) - node = new ReaderNode(); + else if (node == null) { + try { + node = new ReaderNode(); + } catch (OutOfMemoryError oome) { + return spinLockOnOOME(false, interruptible, timed, time); + } + } else if (node.waiter == null) node.waiter = Thread.currentThread(); else if (!attached) { @@ -1313,15 +1346,22 @@ else if (!attached) { if (!attached) node.setCowaitersRelaxed(null); } else { - long nanos = 0L; - if (!timed) - LockSupport.park(this); - else if ((nanos = time - System.nanoTime()) > 0L) - LockSupport.parkNanos(this, nanos); + long nanos = (timed) ? time - System.nanoTime(): 0L; + try { + if (!timed) + LockSupport.park(this); + else if (nanos > 0L) + LockSupport.parkNanos(this, nanos); + } catch (Error | RuntimeException ex) { + cancelCowaiter(node, leader); + throw ex; + } interrupted |= Thread.interrupted(); if ((interrupted && interruptible) || - (timed && nanos <= 0L)) - return cancelCowaiter(node, leader, interrupted); + (timed && nanos <= 0L)) { + cancelCowaiter(node, leader); + return (interrupted) ? INTERRUPTED : 0L; + } } } if (node != null) @@ -1341,6 +1381,7 @@ else if ((nanos = time - System.nanoTime()) > 0L) byte spins = 0, postSpins = 0; // retries upon unpark of first thread boolean first = false; Node pred = null; + long nanos = 0L; for (long nextState;;) { if (!first && (pred = node.prev) != null && !(first = (head == pred))) { @@ -1371,21 +1412,25 @@ else if ((nanos = time - System.nanoTime()) > 0L) if (node.waiter == null) node.waiter = Thread.currentThread(); node.status = WAITING; - } else { - long nanos; - spins = postSpins = (byte)((postSpins << 1) | 1); - if (!timed) - LockSupport.park(this); - else if ((nanos = time - System.nanoTime()) > 0L) - LockSupport.parkNanos(this, nanos); - else - break; + } else if (!timed || (nanos = time - System.nanoTime()) > 0) { + try { + if (!timed) + LockSupport.park(this); + else + LockSupport.parkNanos(this, nanos); + } catch (Error | RuntimeException ex) { + cancelAcquire(node); + throw ex; + } node.clearStatus(); if ((interrupted |= Thread.interrupted()) && interruptible) break; - } + spins = postSpins = (byte)((postSpins << 1) | 1); + } else + break; } - return cancelAcquire(node, interrupted); + cancelAcquire(node); + return (interrupted || Thread.interrupted()) ? INTERRUPTED : 0L; } // Cancellation support @@ -1450,10 +1495,8 @@ private void unlinkCowaiter(ReaderNode node, ReaderNode leader) { * to recheck status. * * @param node the waiter (may be null if not yet enqueued) - * @param interrupted if already interrupted - * @return INTERRUPTED if interrupted or Thread.interrupted, else zero */ - private long cancelAcquire(Node node, boolean interrupted) { + private void cancelAcquire(Node node) { if (node != null) { node.waiter = null; node.status = CANCELLED; @@ -1461,7 +1504,6 @@ private long cancelAcquire(Node node, boolean interrupted) { if (node instanceof ReaderNode) signalCowaiters((ReaderNode)node); } - return (interrupted || Thread.interrupted()) ? INTERRUPTED : 0L; } /** @@ -1470,17 +1512,33 @@ private long cancelAcquire(Node node, boolean interrupted) { * * @param node if non-null, the waiter * @param leader if non-null, the node heading cowaiters list - * @param interrupted if already interrupted - * @return INTERRUPTED if interrupted or Thread.interrupted, else zero */ - private long cancelCowaiter(ReaderNode node, ReaderNode leader, - boolean interrupted) { + private void cancelCowaiter(ReaderNode node, ReaderNode leader) { if (node != null) { node.waiter = null; node.status = CANCELLED; unlinkCowaiter(node, leader); } - return (interrupted || Thread.interrupted()) ? INTERRUPTED : 0L; + } + + /** + * Fallback upon encountering OutOfMemoryErrors + */ + private long spinLockOnOOME(boolean write, boolean interruptible, + boolean timed, long time) { + long startTime = (timed) ? System.nanoTime() : 0L; + for (int spins = 0;;) { + long s = (write) ? tryAcquireWrite() : tryAcquireRead(); + if (s != 0L) + return s; + Thread.onSpinWait(); + if ((++spins & (1 << 8)) == 0) { // occasionally check + if (interruptible && Thread.interrupted()) + return INTERRUPTED; + if (timed && System.nanoTime() - startTime > time) + return 0L; + } + } } // Unsafe diff --git a/test/jdk/java/util/concurrent/locks/StampedLock/OOMEInStampedLock.java b/test/jdk/java/util/concurrent/locks/StampedLock/OOMEInStampedLock.java new file mode 100644 index 0000000000000..2346ad39fc465 --- /dev/null +++ b/test/jdk/java/util/concurrent/locks/StampedLock/OOMEInStampedLock.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.Phaser; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.StampedLock; +import java.util.function.Consumer; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +/** + * @test + * @bug 8066859 + * @summary An adaptation of OOMEInAQS test for StampedLocks + * @requires vm.gc.G1 + * @run main/othervm -XX:+UseG1GC -XX:-UseGCOverheadLimit -Xmx48M -XX:-UseTLAB OOMEInStampedLock + */ + +public class OOMEInStampedLock extends Thread { + static final int NTHREADS = 3; + static final int NREPS = 100; + // statically allocate + static final StampedLock stampedLock = new StampedLock(); + static final Lock wLock = stampedLock.asWriteLock(); + static final Lock rLock = stampedLock.asReadLock(); + static final CountDownLatch started = new CountDownLatch(1); + static final CountDownLatch filled = new CountDownLatch(1); + static final CountDownLatch canFill = new CountDownLatch(NTHREADS); + static volatile Object data; + static volatile Throwable exception; + static int turn; + + /** + * For each of NTHREADS threads, REPS times: Take turns + * executing. Introduce OOM using fillHeap during runs. In + * addition to testing AQS, the CountDownLatches ensure that + * methods execute at least once before OutOfMemory occurs, to + * avoid uncontrollable impact of OOME during class-loading. + */ + public static void main(String[] args) throws Throwable { + OOMEInStampedLock[] threads = new OOMEInStampedLock[NTHREADS]; + for (int i = 0; i < NTHREADS; ++i) + (threads[i] = new OOMEInStampedLock(i)).start(); + started.countDown(); + canFill.await(); + long t0 = System.nanoTime(); + data = fillHeap(); + filled.countDown(); + long t1 = System.nanoTime(); + for (int i = 0; i < NTHREADS; ++i) + threads[i].join(); + data = null; // free heap before reporting and terminating + System.gc(); + Throwable ex = exception; + if (ex != null) + throw ex; + System.out.println( + "fillHeap time: " + (t1 - t0) / 1000_000 + + " millis, whole test time: " + (System.nanoTime() - t0) / 1000_000 + + " millis" + ); + } + + final int tid; + OOMEInStampedLock(int tid) { + this.tid = tid; + } + + @Override + public void run() { + int id = tid, nextId = (id + 1) % NTHREADS; + final Lock wl = wLock, rl = rLock; + try { + started.await(); + for (int i = 0; i < NREPS; i++) { + int t; + rl.lock(); t = turn; rl.unlock(); + wl.lock(); + try { + if (turn == t && turn == id) + turn = nextId; + } finally { + wl.unlock(); + } + if (i == 2) { // Subsequent AQS methods encounter OOME + canFill.countDown(); + filled.await(); + } + } + data = null; + System.gc(); // avoid getting stuck while exiting + } catch (Throwable ex) { + data = null; + System.gc(); // avoid nested OOME + exception = ex; + } + } + + static Object[] fillHeap() { + Object[] first = null, last = null; + int size = 1 << 20; + while (size > 0) { + try { + Object[] array = new Object[size]; + if (first == null) { + first = array; + } else { + last[0] = array; + } + last = array; + } catch (OutOfMemoryError oome) { + size = size >>> 1; + } + } + return first; + } +}