1 Star 0 Fork 81

Alex Gao/openjdk-1.8.0

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
0003-8187408-AbstractQueuedSynchronizer-wait-queue-corrup.patch 14.21 KB
一键复制 编辑 原始数据 按行查看 历史
Date: Tue, 30 May 2023 15:42:59 +0800
Subject: [PATCH 03/59] 8187408: AbstractQueuedSynchronizer wait queue corrupted when thread awaits without holding the lock
Bug url: https://bugs.openjdk.org/browse/JDK-8187408
---
.../locks/AbstractQueuedLongSynchronizer.java | 20 +--
.../locks/AbstractQueuedSynchronizer.java | 52 +++---
jdk/test/java/util/Bug8187408/Bug8187408.java | 165 ++++++++++++++++++
3 files changed, 207 insertions(+), 30 deletions(-)
create mode 100644 jdk/test/java/util/Bug8187408/Bug8187408.java
diff --git a/jdk/src/share/classes/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java b/jdk/src/share/classes/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java
index 8699fc9b8..5adc39e17 100644
--- a/jdk/src/share/classes/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java
+++ b/jdk/src/share/classes/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java
@@ -64,11 +64,11 @@ public abstract class AbstractQueuedLongSynchronizer
private static final long serialVersionUID = 7373984972572414692L;
/*
- To keep sources in sync, the remainder of this source file is
- exactly cloned from AbstractQueuedSynchronizer, replacing class
- name and changing ints related with sync state to longs. Please
- keep it that way.
- */
+ * To keep sources in sync, the remainder of this source file is
+ * exactly cloned from AbstractQueuedSynchronizer, replacing class
+ * name and changing ints related with sync state to longs. Please
+ * keep it that way.
+ */
/**
* Creates a new {@code AbstractQueuedLongSynchronizer} instance
@@ -946,8 +946,7 @@ public abstract class AbstractQueuedLongSynchronizer
/**
* Returns {@code true} if synchronization is held exclusively with
* respect to the current (calling) thread. This method is invoked
- * upon each call to a non-waiting {@link ConditionObject} method.
- * (Waiting methods instead invoke {@link #release}.)
+ * upon each call to a {@link ConditionObject} method.
*
* <p>The default implementation throws {@link
* UnsupportedOperationException}. This method is invoked
@@ -1597,9 +1596,8 @@ public abstract class AbstractQueuedLongSynchronizer
}
/**
- * Condition implementation for a {@link
- * AbstractQueuedLongSynchronizer} serving as the basis of a {@link
- * Lock} implementation.
+ * Condition implementation for a {@link AbstractQueuedLongSynchronizer}
+ * serving as the basis of a {@link Lock} implementation.
*
* <p>Method documentation for this class describes mechanics,
* not behavioral specifications from the point of view of Lock
@@ -1632,6 +1630,8 @@ public abstract class AbstractQueuedLongSynchronizer
* @return its new wait node
*/
private Node addConditionWaiter() {
+ if (!isHeldExclusively())
+ throw new IllegalMonitorStateException();
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
diff --git a/jdk/src/share/classes/java/util/concurrent/locks/AbstractQueuedSynchronizer.java b/jdk/src/share/classes/java/util/concurrent/locks/AbstractQueuedSynchronizer.java
index 9088e5894..0e2bcdfef 100644
--- a/jdk/src/share/classes/java/util/concurrent/locks/AbstractQueuedSynchronizer.java
+++ b/jdk/src/share/classes/java/util/concurrent/locks/AbstractQueuedSynchronizer.java
@@ -192,19 +192,13 @@ import sun.misc.Unsafe;
* represent the locked state. While a non-reentrant lock
* does not strictly require recording of the current owner
* thread, this class does so anyway to make usage easier to monitor.
- * It also supports conditions and exposes
- * one of the instrumentation methods:
+ * It also supports conditions and exposes some instrumentation methods:
*
* <pre> {@code
* class Mutex implements Lock, java.io.Serializable {
*
* // Our internal helper class
* private static class Sync extends AbstractQueuedSynchronizer {
- * // Reports whether in locked state
- * protected boolean isHeldExclusively() {
- * return getState() == 1;
- * }
- *
* // Acquires the lock if state is zero
* public boolean tryAcquire(int acquires) {
* assert acquires == 1; // Otherwise unused
@@ -218,14 +212,27 @@ import sun.misc.Unsafe;
* // Releases the lock by setting state to zero
* protected boolean tryRelease(int releases) {
* assert releases == 1; // Otherwise unused
- * if (getState() == 0) throw new IllegalMonitorStateException();
+ * if (!isHeldExclusively())
+ * throw new IllegalMonitorStateException();
* setExclusiveOwnerThread(null);
* setState(0);
* return true;
* }
*
+ * // Reports whether in locked state
+ * public boolean isLocked() {
+ * return getState() != 0;
+ * }
+ *
+ * public boolean isHeldExclusively() {
+ * // a data race, but safe due to out-of-thin-air guarantees
+ * return getExclusiveOwnerThread() == Thread.currentThread();
+ * }
+ *
* // Provides a Condition
- * Condition newCondition() { return new ConditionObject(); }
+ * public Condition newCondition() {
+ * return new ConditionObject();
+ * }
*
* // Deserializes properly
* private void readObject(ObjectInputStream s)
@@ -238,12 +245,17 @@ import sun.misc.Unsafe;
* // The sync object does all the hard work. We just forward to it.
* private final Sync sync = new Sync();
*
- * public void lock() { sync.acquire(1); }
- * public boolean tryLock() { return sync.tryAcquire(1); }
- * public void unlock() { sync.release(1); }
- * public Condition newCondition() { return sync.newCondition(); }
- * public boolean isLocked() { return sync.isHeldExclusively(); }
- * public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
+ * public void lock() { sync.acquire(1); }
+ * public boolean tryLock() { return sync.tryAcquire(1); }
+ * public void unlock() { sync.release(1); }
+ * public Condition newCondition() { return sync.newCondition(); }
+ * public boolean isLocked() { return sync.isLocked(); }
+ * public boolean isHeldByCurrentThread() {
+ * return sync.isHeldExclusively();
+ * }
+ * public boolean hasQueuedThreads() {
+ * return sync.hasQueuedThreads();
+ * }
* public void lockInterruptibly() throws InterruptedException {
* sync.acquireInterruptibly(1);
* }
@@ -1168,8 +1180,7 @@ public abstract class AbstractQueuedSynchronizer
/**
* Returns {@code true} if synchronization is held exclusively with
* respect to the current (calling) thread. This method is invoked
- * upon each call to a non-waiting {@link ConditionObject} method.
- * (Waiting methods instead invoke {@link #release}.)
+ * upon each call to a {@link ConditionObject} method.
*
* <p>The default implementation throws {@link
* UnsupportedOperationException}. This method is invoked
@@ -1819,9 +1830,8 @@ public abstract class AbstractQueuedSynchronizer
}
/**
- * Condition implementation for a {@link
- * AbstractQueuedSynchronizer} serving as the basis of a {@link
- * Lock} implementation.
+ * Condition implementation for a {@link AbstractQueuedSynchronizer}
+ * serving as the basis of a {@link Lock} implementation.
*
* <p>Method documentation for this class describes mechanics,
* not behavioral specifications from the point of view of Lock
@@ -1852,6 +1862,8 @@ public abstract class AbstractQueuedSynchronizer
* @return its new wait node
*/
private Node addConditionWaiter() {
+ if (!isHeldExclusively())
+ throw new IllegalMonitorStateException();
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
diff --git a/jdk/test/java/util/Bug8187408/Bug8187408.java b/jdk/test/java/util/Bug8187408/Bug8187408.java
new file mode 100644
index 000000000..fee6c730d
--- /dev/null
+++ b/jdk/test/java/util/Bug8187408/Bug8187408.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2023, 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.
+ */
+
+ /*
+ * @test
+ * @bug 8187408
+ * @summary AbstractQueuedSynchronizer wait queue corrupted when thread awaits without holding the lock
+ */
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class Bug8187408 {
+ static final long LONG_DELAY_MS = 10000L;
+
+ public static void main(String[] args) throws InterruptedException {
+ final ThreadLocalRandom rnd = ThreadLocalRandom.current();
+ final AwaitMethod awaitMethod = randomAwaitMethod();
+ final int nThreads = rnd.nextInt(2, 10);
+ final ReentrantLock lock = new ReentrantLock();
+ final Condition cond = lock.newCondition();
+ final CountDownLatch done = new CountDownLatch(nThreads);
+ final ArrayList<Thread> threads = new ArrayList<>();
+
+ Runnable rogue = () -> {
+ while (done.getCount() > 0) {
+ try {
+ // call await without holding lock?!
+ await(cond, awaitMethod);
+ throw new AssertionError("should throw");
+ }
+ catch (IllegalMonitorStateException success) {}
+ catch (Throwable fail) { threadUnexpectedException(fail); }}};
+ Thread rogueThread = new Thread(rogue, "rogue");
+ threads.add(rogueThread);
+ rogueThread.start();
+
+ Runnable waiter = () -> {
+ lock.lock();
+ try {
+ done.countDown();
+ cond.await();
+ } catch (Throwable fail) {
+ threadUnexpectedException(fail);
+ } finally {
+ lock.unlock();
+ }};
+ for (int i = 0; i < nThreads; i++) {
+ Thread thread = new Thread(waiter, "waiter");
+ threads.add(thread);
+ thread.start();
+ }
+
+ assertTrue(done.await(LONG_DELAY_MS, MILLISECONDS));
+ lock.lock();
+ try {
+ assertEquals(nThreads, lock.getWaitQueueLength(cond));
+ } finally {
+ cond.signalAll();
+ lock.unlock();
+ }
+ for (Thread thread : threads) {
+ thread.join(LONG_DELAY_MS);
+ assertFalse(thread.isAlive());
+ }
+ }
+
+ private static void assertTrue(boolean expr) {
+ if (!expr)
+ throw new RuntimeException("assertion failed");
+ }
+
+ private static void assertFalse(boolean expr) {
+ if (expr)
+ throw new RuntimeException("assertion failed");
+ }
+
+ private static void assertEquals(int i, int j) {
+ if (i != j)
+ throw new AssertionError(i + " != " + j);
+ }
+
+ /**
+ * Records the given exception using {@link #threadRecordFailure},
+ * then rethrows the exception, wrapping it in an AssertionError
+ * if necessary.
+ */
+ private static void threadUnexpectedException(Throwable t) {
+ t.printStackTrace();
+ if (t instanceof RuntimeException)
+ throw (RuntimeException) t;
+ else if (t instanceof Error)
+ throw (Error) t;
+ else
+ throw new AssertionError("unexpected exception: " + t, t);
+ }
+
+ enum AwaitMethod { await, awaitTimed, awaitNanos, awaitUntil }
+ private static AwaitMethod randomAwaitMethod() {
+ AwaitMethod[] awaitMethods = AwaitMethod.values();
+ return awaitMethods[ThreadLocalRandom.current().nextInt(awaitMethods.length)];
+ }
+
+ /**
+ * Returns a new Date instance representing a time at least
+ * delayMillis milliseconds in the future.
+ */
+ private static Date delayedDate(long delayMillis) {
+ // Add 1 because currentTimeMillis is known to round into the past.
+ return new Date(System.currentTimeMillis() + delayMillis + 1);
+ }
+
+ /**
+ * Awaits condition "indefinitely" using the specified AwaitMethod.
+ */
+ private static void await(Condition c, AwaitMethod awaitMethod)
+ throws InterruptedException {
+ long timeoutMillis = 2 * LONG_DELAY_MS;
+ switch (awaitMethod) {
+ case await:
+ c.await();
+ break;
+ case awaitTimed:
+ assertTrue(c.await(timeoutMillis, MILLISECONDS));
+ break;
+ case awaitNanos:
+ long timeoutNanos = MILLISECONDS.toNanos(timeoutMillis);
+ long nanosRemaining = c.awaitNanos(timeoutNanos);
+ assertTrue(nanosRemaining > timeoutNanos / 2);
+ assertTrue(nanosRemaining <= timeoutNanos);
+ break;
+ case awaitUntil:
+ assertTrue(c.awaitUntil(delayedDate(timeoutMillis)));
+ break;
+ default:
+ throw new AssertionError();
+ }
+ }
+}
\ No newline at end of file
--
2.22.0
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/gaoxiang90/openjdk-1.8.0.git
git@gitee.com:gaoxiang90/openjdk-1.8.0.git
gaoxiang90
openjdk-1.8.0
openjdk-1.8.0
master

搜索帮助