Skip to content

Commit f4b5fb9

Browse files
cpovirkGoogle Java Core Libraries
authored andcommitted
Fix interrupt handling in the Condition overloads of Uninterruptibles.awaitUninterruptibly.
(extremely belated follow-up to #1212 / #2870 / #3010) (Plus, I addressed one http://errorprone.info/bugpattern/LockNotBeforeTry warning in the test. I don't want to think about the http://errorprone.info/bugpattern/WaitNotInLoop warnings right now, but I did leave a comment about them.) This bug felt worth explaining in the comments in the code itself, so I'll leave the explanation to them. RELNOTES=n/a PiperOrigin-RevId: 862924189
1 parent 8138afb commit f4b5fb9

File tree

4 files changed

+176
-76
lines changed

4 files changed

+176
-76
lines changed

android/guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,19 @@ public void testNull() throws Exception {
117117
// CountDownLatch.await() tests
118118

119119
// Condition.await() tests
120+
121+
/*
122+
* Our tests for awaitUninterruptibly are written under the assumption that no spurious wakeups
123+
* occur except for those produced by awaitUninterruptibly itself in response to interrupts.
124+
*/
125+
120126
public void testConditionAwaitTimeoutExceeded() {
121127
Stopwatch stopwatch = Stopwatch.createStarted();
122128
Condition condition = TestCondition.create();
123129

124-
boolean signaledBeforeTimeout = awaitUninterruptibly(condition, 500, MILLISECONDS);
130+
boolean returnedBeforeTimeout = awaitUninterruptibly(condition, 500, MILLISECONDS);
125131

126-
assertFalse(signaledBeforeTimeout);
132+
assertFalse(returnedBeforeTimeout);
127133
assertAtLeastTimePassed(stopwatch, 500);
128134
assertNotInterrupted();
129135
}
@@ -132,9 +138,9 @@ public void testConditionAwaitTimeoutNotExceeded() {
132138
Stopwatch stopwatch = Stopwatch.createStarted();
133139
Condition condition = TestCondition.createAndSignalAfter(500, MILLISECONDS);
134140

135-
boolean signaledBeforeTimeout = awaitUninterruptibly(condition, 1500, MILLISECONDS);
141+
boolean returnedBeforeTimeout = awaitUninterruptibly(condition, 1500, MILLISECONDS);
136142

137-
assertTrue(signaledBeforeTimeout);
143+
assertTrue(returnedBeforeTimeout);
138144
assertTimeNotPassed(stopwatch, LONG_DELAY_MS);
139145
assertNotInterrupted();
140146
}
@@ -144,10 +150,10 @@ public void testConditionAwaitInterruptedTimeoutExceeded() {
144150
Condition condition = TestCondition.create();
145151
requestInterruptIn(500);
146152

147-
boolean signaledBeforeTimeout = awaitUninterruptibly(condition, 1000, MILLISECONDS);
153+
boolean returnedBeforeTimeout = awaitUninterruptibly(condition, 1000, MILLISECONDS);
148154

149-
assertFalse(signaledBeforeTimeout);
150-
assertAtLeastTimePassed(stopwatch, 1000);
155+
assertTrue(returnedBeforeTimeout);
156+
assertAtLeastTimePassed(stopwatch, 500);
151157
assertInterrupted();
152158
}
153159

@@ -156,9 +162,21 @@ public void testConditionAwaitInterruptedTimeoutNotExceeded() {
156162
Condition condition = TestCondition.createAndSignalAfter(1000, MILLISECONDS);
157163
requestInterruptIn(500);
158164

159-
boolean signaledBeforeTimeout = awaitUninterruptibly(condition, 1500, MILLISECONDS);
165+
boolean returnedBeforeTimeout = awaitUninterruptibly(condition, 1500, MILLISECONDS);
160166

161-
assertTrue(signaledBeforeTimeout);
167+
assertTrue(returnedBeforeTimeout);
168+
assertTimeNotPassed(stopwatch, LONG_DELAY_MS);
169+
assertInterrupted();
170+
}
171+
172+
public void testConditionAwaitMultiInterrupt() {
173+
Stopwatch stopwatch = Stopwatch.createStarted();
174+
Condition condition = TestCondition.createAndSignalAfter(1000, MILLISECONDS);
175+
repeatedlyInterruptTestThread(tearDownStack);
176+
177+
boolean returnedBeforeTimeout = awaitUninterruptibly(condition, Duration.ofHours(1));
178+
179+
assertTrue(returnedBeforeTimeout);
162180
assertTimeNotPassed(stopwatch, LONG_DELAY_MS);
163181
assertInterrupted();
164182
}
@@ -184,9 +202,9 @@ public void testTryLockTimeoutNotExceeded() {
184202
Lock lock = new ReentrantLock();
185203
acquireFor(lock, 500, MILLISECONDS);
186204

187-
boolean signaledBeforeTimeout = tryLockUninterruptibly(lock, 1500, MILLISECONDS);
205+
boolean acquired = tryLockUninterruptibly(lock, 1500, MILLISECONDS);
188206

189-
assertTrue(signaledBeforeTimeout);
207+
assertTrue(acquired);
190208
assertTimeNotPassed(stopwatch, LONG_DELAY_MS);
191209
assertNotInterrupted();
192210
}
@@ -197,9 +215,9 @@ public void testTryLockInterruptedTimeoutExceeded() {
197215
Thread lockThread = acquireFor(lock, 5, SECONDS);
198216
requestInterruptIn(500);
199217

200-
boolean signaledBeforeTimeout = tryLockUninterruptibly(lock, 1000, MILLISECONDS);
218+
boolean acquired = tryLockUninterruptibly(lock, 1000, MILLISECONDS);
201219

202-
assertFalse(signaledBeforeTimeout);
220+
assertFalse(acquired);
203221
assertAtLeastTimePassed(stopwatch, 1000);
204222
assertInterrupted();
205223

@@ -213,9 +231,9 @@ public void testTryLockInterruptedTimeoutNotExceeded() {
213231
acquireFor(lock, 1000, MILLISECONDS);
214232
requestInterruptIn(500);
215233

216-
boolean signaledBeforeTimeout = tryLockUninterruptibly(lock, 1500, MILLISECONDS);
234+
boolean acquired = tryLockUninterruptibly(lock, 1500, MILLISECONDS);
217235

218-
assertTrue(signaledBeforeTimeout);
236+
assertTrue(acquired);
219237
assertTimeNotPassed(stopwatch, LONG_DELAY_MS);
220238
assertInterrupted();
221239
}
@@ -876,8 +894,9 @@ private static Thread acquireFor(Lock lock, long duration, TimeUnit unit) {
876894
@Override
877895
public void run() {
878896
lock.lock();
879-
latch.countDown();
880897
try {
898+
899+
latch.countDown();
881900
Thread.sleep(unit.toMillis(duration));
882901
} catch (InterruptedException e) {
883902
// simply finish execution

android/guava/src/com/google/common/util/concurrent/Uninterruptibles.java

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,6 @@
4747
*/
4848
@GwtCompatible
4949
public final class Uninterruptibles {
50-
51-
// Implementation Note: As of 3-7-11, the logic for each blocking/timeout
52-
// methods is identical, save for method being invoked.
53-
5450
/** Invokes {@code latch.}{@link CountDownLatch#await() await()} uninterruptibly. */
5551
@J2ktIncompatible
5652
@GwtIncompatible // concurrency
@@ -115,9 +111,18 @@ public static boolean awaitUninterruptibly(CountDownLatch latch, long timeout, T
115111
}
116112

117113
/**
118-
* Invokes {@code condition.}{@link Condition#await(long, TimeUnit) await(timeout, unit)}
119-
* uninterruptibly.
114+
* Invokes {@code condition.}{@link Condition#await(long, TimeUnit) await(timeout, unit)} in a way
115+
* that more conveniently supports uninterruptible waits.
120116
*
117+
* <p>If the underlying {@code await} call is interrupted, then {@code awaitUninterruptibly}
118+
* converts that into a <a
119+
* href="https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/locks/Condition.html#implementation-considerations-heading">spurious
120+
* wakeup</a>. This means that resulting wait is not "uninterruptible" in the normal sense of
121+
* {@link Uninterruptibles}. Still, this method allows callers to write <a
122+
* href="https://errorprone.info/bugpattern/WaitNotInLoop">the standard, required loop for waiting on a {@code
123+
* Condition}</a> but without the need to handle interruption.
124+
*
125+
* @return {@code false} if the waiting time detectably elapsed before return from the method
121126
* @since 33.4.0 (but since 28.0 in the JRE flavor)
122127
*/
123128
@J2ktIncompatible
@@ -128,32 +133,58 @@ public static boolean awaitUninterruptibly(Condition condition, Duration timeout
128133
}
129134

130135
/**
131-
* Invokes {@code condition.}{@link Condition#await(long, TimeUnit) await(timeout, unit)}
132-
* uninterruptibly.
136+
* Invokes {@code condition.}{@link Condition#await(long, TimeUnit) await(timeout, unit)} in a way
137+
* that more conveniently supports uninterruptible waits.
133138
*
139+
* <p>If the underlying {@code await} call is interrupted, then {@code awaitUninterruptibly}
140+
* converts that into a <a
141+
* href="https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/locks/Condition.html#implementation-considerations-heading">spurious
142+
* wakeup</a>. This means that resulting wait is not "uninterruptible" in the normal sense of
143+
* {@link Uninterruptibles}. Still, this method allows callers to write <a
144+
* href="https://errorprone.info/bugpattern/WaitNotInLoop">the standard, required loop for waiting on a {@code
145+
* Condition}</a> but without the need to handle interruption.
146+
*
147+
* @return {@code false} if the waiting time detectably elapsed before return from the method
134148
* @since 23.6
135149
*/
136150
@J2ktIncompatible
137151
@GwtIncompatible // concurrency
138152
@SuppressWarnings("GoodTime") // should accept a java.time.Duration
139153
public static boolean awaitUninterruptibly(Condition condition, long timeout, TimeUnit unit) {
140-
boolean interrupted = false;
141-
try {
142-
long remainingNanos = unit.toNanos(timeout);
143-
long end = System.nanoTime() + remainingNanos;
154+
/*
155+
* An uninterruptible wait on a Condition requires different logic than an uninterruptible wait
156+
* on most other types: In cases in which we "should" receive both an interrupt and a
157+
* notification nearly simultaneously, we sometimes receive only an interrupt. Thus, when we're
158+
* interrupted, we can't just poll whether it's time to end the wait because our "end the wait"
159+
* notification has been lost. (This is in contrast to how we can poll with, say, a
160+
* CountDownLatch.) In order to avoid hiding the requested notification from the caller, we need
161+
* to return. Fortunately, a wait on a Condition is allowed to return early on account of a
162+
* "spurious wakeup," so we're allowed to convert interruptions into such wakeups.
163+
*/
164+
165+
/*
166+
* Since we can't loop inside awaitUninterruptibly(Condition, ...), the user is responsible for
167+
* calling us again in case of interrupt. Then, if we were to call await(...) immediately, as we
168+
* do in the other Uninterruptibles methods, it would throw immediately. Then we'd restore the
169+
* interrupt and return again, and the user would call us again, creating a busy wait.
170+
*
171+
* Thus, we need to clear the interrupt eagerly in case it's an interrupt from a previous call
172+
* to awaitUninterruptibly in the user code's Condition loop.
173+
*/
174+
boolean wasAlreadyInterrupted = Thread.interrupted();
175+
long remainingNanos = unit.toNanos(timeout);
176+
long end = System.nanoTime() + remainingNanos;
144177

145-
while (true) {
146-
try {
147-
return condition.await(remainingNanos, NANOSECONDS);
148-
} catch (InterruptedException e) {
149-
interrupted = true;
150-
remainingNanos = end - System.nanoTime();
151-
}
152-
}
153-
} finally {
154-
if (interrupted) {
178+
try {
179+
boolean result = condition.await(remainingNanos, NANOSECONDS);
180+
if (wasAlreadyInterrupted) {
155181
Thread.currentThread().interrupt();
156182
}
183+
return result;
184+
} catch (InterruptedException e) {
185+
Thread.currentThread().interrupt();
186+
// better than `end > System.nanoTime()` because `System.nanoTime()` could wrap around
187+
return end - System.nanoTime() > 0;
157188
}
158189
}
159190

guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,19 @@ public void testNull() throws Exception {
117117
// CountDownLatch.await() tests
118118

119119
// Condition.await() tests
120+
121+
/*
122+
* Our tests for awaitUninterruptibly are written under the assumption that no spurious wakeups
123+
* occur except for those produced by awaitUninterruptibly itself in response to interrupts.
124+
*/
125+
120126
public void testConditionAwaitTimeoutExceeded() {
121127
Stopwatch stopwatch = Stopwatch.createStarted();
122128
Condition condition = TestCondition.create();
123129

124-
boolean signaledBeforeTimeout = awaitUninterruptibly(condition, 500, MILLISECONDS);
130+
boolean returnedBeforeTimeout = awaitUninterruptibly(condition, 500, MILLISECONDS);
125131

126-
assertFalse(signaledBeforeTimeout);
132+
assertFalse(returnedBeforeTimeout);
127133
assertAtLeastTimePassed(stopwatch, 500);
128134
assertNotInterrupted();
129135
}
@@ -132,9 +138,9 @@ public void testConditionAwaitTimeoutNotExceeded() {
132138
Stopwatch stopwatch = Stopwatch.createStarted();
133139
Condition condition = TestCondition.createAndSignalAfter(500, MILLISECONDS);
134140

135-
boolean signaledBeforeTimeout = awaitUninterruptibly(condition, 1500, MILLISECONDS);
141+
boolean returnedBeforeTimeout = awaitUninterruptibly(condition, 1500, MILLISECONDS);
136142

137-
assertTrue(signaledBeforeTimeout);
143+
assertTrue(returnedBeforeTimeout);
138144
assertTimeNotPassed(stopwatch, LONG_DELAY_MS);
139145
assertNotInterrupted();
140146
}
@@ -144,10 +150,10 @@ public void testConditionAwaitInterruptedTimeoutExceeded() {
144150
Condition condition = TestCondition.create();
145151
requestInterruptIn(500);
146152

147-
boolean signaledBeforeTimeout = awaitUninterruptibly(condition, 1000, MILLISECONDS);
153+
boolean returnedBeforeTimeout = awaitUninterruptibly(condition, 1000, MILLISECONDS);
148154

149-
assertFalse(signaledBeforeTimeout);
150-
assertAtLeastTimePassed(stopwatch, 1000);
155+
assertTrue(returnedBeforeTimeout);
156+
assertAtLeastTimePassed(stopwatch, 500);
151157
assertInterrupted();
152158
}
153159

@@ -156,9 +162,21 @@ public void testConditionAwaitInterruptedTimeoutNotExceeded() {
156162
Condition condition = TestCondition.createAndSignalAfter(1000, MILLISECONDS);
157163
requestInterruptIn(500);
158164

159-
boolean signaledBeforeTimeout = awaitUninterruptibly(condition, 1500, MILLISECONDS);
165+
boolean returnedBeforeTimeout = awaitUninterruptibly(condition, 1500, MILLISECONDS);
160166

161-
assertTrue(signaledBeforeTimeout);
167+
assertTrue(returnedBeforeTimeout);
168+
assertTimeNotPassed(stopwatch, LONG_DELAY_MS);
169+
assertInterrupted();
170+
}
171+
172+
public void testConditionAwaitMultiInterrupt() {
173+
Stopwatch stopwatch = Stopwatch.createStarted();
174+
Condition condition = TestCondition.createAndSignalAfter(1000, MILLISECONDS);
175+
repeatedlyInterruptTestThread(tearDownStack);
176+
177+
boolean returnedBeforeTimeout = awaitUninterruptibly(condition, Duration.ofHours(1));
178+
179+
assertTrue(returnedBeforeTimeout);
162180
assertTimeNotPassed(stopwatch, LONG_DELAY_MS);
163181
assertInterrupted();
164182
}
@@ -184,9 +202,9 @@ public void testTryLockTimeoutNotExceeded() {
184202
Lock lock = new ReentrantLock();
185203
acquireFor(lock, 500, MILLISECONDS);
186204

187-
boolean signaledBeforeTimeout = tryLockUninterruptibly(lock, 1500, MILLISECONDS);
205+
boolean acquired = tryLockUninterruptibly(lock, 1500, MILLISECONDS);
188206

189-
assertTrue(signaledBeforeTimeout);
207+
assertTrue(acquired);
190208
assertTimeNotPassed(stopwatch, LONG_DELAY_MS);
191209
assertNotInterrupted();
192210
}
@@ -197,9 +215,9 @@ public void testTryLockInterruptedTimeoutExceeded() {
197215
Thread lockThread = acquireFor(lock, 5, SECONDS);
198216
requestInterruptIn(500);
199217

200-
boolean signaledBeforeTimeout = tryLockUninterruptibly(lock, 1000, MILLISECONDS);
218+
boolean acquired = tryLockUninterruptibly(lock, 1000, MILLISECONDS);
201219

202-
assertFalse(signaledBeforeTimeout);
220+
assertFalse(acquired);
203221
assertAtLeastTimePassed(stopwatch, 1000);
204222
assertInterrupted();
205223

@@ -213,9 +231,9 @@ public void testTryLockInterruptedTimeoutNotExceeded() {
213231
acquireFor(lock, 1000, MILLISECONDS);
214232
requestInterruptIn(500);
215233

216-
boolean signaledBeforeTimeout = tryLockUninterruptibly(lock, 1500, MILLISECONDS);
234+
boolean acquired = tryLockUninterruptibly(lock, 1500, MILLISECONDS);
217235

218-
assertTrue(signaledBeforeTimeout);
236+
assertTrue(acquired);
219237
assertTimeNotPassed(stopwatch, LONG_DELAY_MS);
220238
assertInterrupted();
221239
}
@@ -876,8 +894,9 @@ private static Thread acquireFor(Lock lock, long duration, TimeUnit unit) {
876894
@Override
877895
public void run() {
878896
lock.lock();
879-
latch.countDown();
880897
try {
898+
899+
latch.countDown();
881900
Thread.sleep(unit.toMillis(duration));
882901
} catch (InterruptedException e) {
883902
// simply finish execution

0 commit comments

Comments
 (0)