diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ConditionVariable.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ConditionVariable.java index b7f0d04e23..e5665c76ff 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/ConditionVariable.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/ConditionVariable.java @@ -111,6 +111,26 @@ public class ConditionVariable { return isOpen; } + /** + * Blocks until the condition is open. Unlike {@link #block}, this method will continue to block + * if the calling thread is interrupted. If the calling thread was interrupted then its {@link + * Thread#isInterrupted() interrupted status} will be set when the method returns. + */ + public synchronized void blockUninterruptible() { + boolean wasInterrupted = false; + while (!isOpen) { + try { + wait(); + } catch (InterruptedException e) { + wasInterrupted = true; + } + } + if (wasInterrupted) { + // Restore the interrupted status. + Thread.currentThread().interrupt(); + } + } + /** Returns whether the condition is opened. */ public synchronized boolean isOpen() { return isOpen; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/ConditionVariableTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/ConditionVariableTest.java index 8f2fb2ed14..e7e0d8911a 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/util/ConditionVariableTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/ConditionVariableTest.java @@ -49,34 +49,7 @@ public class ConditionVariableTest { } @Test - public void blockWithoutTimeout_blocks() throws InterruptedException { - ConditionVariable conditionVariable = buildTestConditionVariable(); - - AtomicBoolean blockReturned = new AtomicBoolean(); - AtomicBoolean blockWasInterrupted = new AtomicBoolean(); - Thread blockingThread = - new Thread( - () -> { - try { - conditionVariable.block(); - blockReturned.set(true); - } catch (InterruptedException e) { - blockWasInterrupted.set(true); - } - }); - - blockingThread.start(); - Thread.sleep(500); - assertThat(blockReturned.get()).isFalse(); - - blockingThread.interrupt(); - blockingThread.join(); - assertThat(blockWasInterrupted.get()).isTrue(); - assertThat(conditionVariable.isOpen()).isFalse(); - } - - @Test - public void blockWithMaxTimeout_blocks() throws InterruptedException { + public void blockWithMaxTimeout_blocks_thenThrowsWhenInterrupted() throws InterruptedException { ConditionVariable conditionVariable = buildTestConditionVariable(); AtomicBoolean blockReturned = new AtomicBoolean(); @@ -103,7 +76,34 @@ public class ConditionVariableTest { } @Test - public void open_unblocksBlock() throws InterruptedException { + public void block_blocks_thenThrowsWhenInterrupted() throws InterruptedException { + ConditionVariable conditionVariable = buildTestConditionVariable(); + + AtomicBoolean blockReturned = new AtomicBoolean(); + AtomicBoolean blockWasInterrupted = new AtomicBoolean(); + Thread blockingThread = + new Thread( + () -> { + try { + conditionVariable.block(); + blockReturned.set(true); + } catch (InterruptedException e) { + blockWasInterrupted.set(true); + } + }); + + blockingThread.start(); + Thread.sleep(500); + assertThat(blockReturned.get()).isFalse(); + + blockingThread.interrupt(); + blockingThread.join(); + assertThat(blockWasInterrupted.get()).isTrue(); + assertThat(conditionVariable.isOpen()).isFalse(); + } + + @Test + public void block_blocks_thenReturnsWhenOpened() throws InterruptedException { ConditionVariable conditionVariable = buildTestConditionVariable(); AtomicBoolean blockReturned = new AtomicBoolean(); @@ -129,6 +129,37 @@ public class ConditionVariableTest { assertThat(conditionVariable.isOpen()).isTrue(); } + @Test + public void blockUnterruptible_blocksIfInterrupted_thenUnblocksWhenOpened() + throws InterruptedException { + ConditionVariable conditionVariable = buildTestConditionVariable(); + + AtomicBoolean blockReturned = new AtomicBoolean(); + AtomicBoolean interruptedStatusSet = new AtomicBoolean(); + Thread blockingThread = + new Thread( + () -> { + conditionVariable.blockUninterruptible(); + blockReturned.set(true); + interruptedStatusSet.set(Thread.currentThread().isInterrupted()); + }); + + blockingThread.start(); + Thread.sleep(500); + assertThat(blockReturned.get()).isFalse(); + + blockingThread.interrupt(); + Thread.sleep(500); + // blockUninterruptible should still be blocked. + assertThat(blockReturned.get()).isFalse(); + + conditionVariable.open(); + blockingThread.join(); + // blockUninterruptible should have set the thread's interrupted status on exit. + assertThat(interruptedStatusSet.get()).isTrue(); + assertThat(conditionVariable.isOpen()).isTrue(); + } + private static ConditionVariable buildTestConditionVariable() { return new ConditionVariable( new SystemClock() {