mirror of
https://github.com/androidx/media.git
synced 2025-04-29 22:36:54 +08:00
Remove remaining synchronized keywords from EPII
These are needed for the `waitUninterruptibly` handling, which is really just waiting for a condition to become true on another thread with a timeout, as well as Clock and interrupt handling. We already have ConditionVariable that serves this purpose, which has methods with a timeout and with interrupt handling. Adding another version of the call with both timeout and interrupt handling allows to replace the EPII manual code. The ConditionVariable methods were also missing the clock calls to signal a wait operation. PiperOrigin-RevId: 743214709
This commit is contained in:
parent
d133300627
commit
989e9f9e84
@ -82,6 +82,7 @@ public class ConditionVariable {
|
||||
*/
|
||||
public synchronized void block() throws InterruptedException {
|
||||
while (!isOpen) {
|
||||
clock.onThreadBlocked();
|
||||
wait();
|
||||
}
|
||||
}
|
||||
@ -105,6 +106,7 @@ public class ConditionVariable {
|
||||
block();
|
||||
} else {
|
||||
while (!isOpen && nowMs < endMs) {
|
||||
clock.onThreadBlocked();
|
||||
wait(endMs - nowMs);
|
||||
nowMs = clock.elapsedRealtime();
|
||||
}
|
||||
@ -113,14 +115,17 @@ public class ConditionVariable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Blocks until the condition is open.
|
||||
*
|
||||
* <p>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 {
|
||||
clock.onThreadBlocked();
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
wasInterrupted = true;
|
||||
@ -132,6 +137,45 @@ public class ConditionVariable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks until the condition is open or until {@code timeoutMs} have passed.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* @param timeoutMs The maximum time to wait in milliseconds. If {@code timeoutMs <= 0} then the
|
||||
* call will return immediately without blocking.
|
||||
* @return True if the condition was opened, false if the call returns because of the timeout.
|
||||
*/
|
||||
public synchronized boolean blockUninterruptible(long timeoutMs) {
|
||||
if (timeoutMs <= 0) {
|
||||
return isOpen;
|
||||
}
|
||||
long nowMs = clock.elapsedRealtime();
|
||||
long endMs = nowMs + timeoutMs;
|
||||
if (endMs < nowMs) {
|
||||
// timeoutMs is large enough for (nowMs + timeoutMs) to rollover. Block indefinitely.
|
||||
blockUninterruptible();
|
||||
} else {
|
||||
boolean wasInterrupted = false;
|
||||
while (!isOpen && nowMs < endMs) {
|
||||
try {
|
||||
clock.onThreadBlocked();
|
||||
wait(endMs - nowMs);
|
||||
} catch (InterruptedException e) {
|
||||
wasInterrupted = true;
|
||||
}
|
||||
nowMs = clock.elapsedRealtime();
|
||||
}
|
||||
if (wasInterrupted) {
|
||||
// Restore the interrupted status.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
return isOpen;
|
||||
}
|
||||
|
||||
/** Returns whether the condition is opened. */
|
||||
public synchronized boolean isOpen() {
|
||||
return isOpen;
|
||||
|
@ -33,14 +33,14 @@ public class ConditionVariableTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void blockWithTimeout_timesOut() throws InterruptedException {
|
||||
public void block_withTimeoutUnopened_timesOut() throws InterruptedException {
|
||||
ConditionVariable conditionVariable = buildTestConditionVariable();
|
||||
assertThat(conditionVariable.block(1)).isFalse();
|
||||
assertThat(conditionVariable.isOpen()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void blockWithTimeout_blocksForAtLeastTimeout() throws InterruptedException {
|
||||
public void block_withTimeoutUnopened_blocksForAtLeastTimeout() throws InterruptedException {
|
||||
ConditionVariable conditionVariable = buildTestConditionVariable();
|
||||
long startTimeMs = System.currentTimeMillis();
|
||||
assertThat(conditionVariable.block(/* timeoutMs= */ 500)).isFalse();
|
||||
@ -49,7 +49,8 @@ public class ConditionVariableTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void blockWithMaxTimeout_blocks_thenThrowsWhenInterrupted() throws InterruptedException {
|
||||
public void block_withMaxTimeoutUnopened_blocksThenThrowsWhenInterrupted()
|
||||
throws InterruptedException {
|
||||
ConditionVariable conditionVariable = buildTestConditionVariable();
|
||||
|
||||
AtomicBoolean blockReturned = new AtomicBoolean();
|
||||
@ -76,7 +77,7 @@ public class ConditionVariableTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void block_blocks_thenThrowsWhenInterrupted() throws InterruptedException {
|
||||
public void block_unopened_blocksThenThrowsWhenInterrupted() throws InterruptedException {
|
||||
ConditionVariable conditionVariable = buildTestConditionVariable();
|
||||
|
||||
AtomicBoolean blockReturned = new AtomicBoolean();
|
||||
@ -103,7 +104,7 @@ public class ConditionVariableTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void block_blocks_thenReturnsWhenOpened() throws InterruptedException {
|
||||
public void block_opened_blocksThenReturnsWhenOpened() throws InterruptedException {
|
||||
ConditionVariable conditionVariable = buildTestConditionVariable();
|
||||
|
||||
AtomicBoolean blockReturned = new AtomicBoolean();
|
||||
@ -130,7 +131,7 @@ public class ConditionVariableTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void blockUnterruptible_blocksIfInterrupted_thenUnblocksWhenOpened()
|
||||
public void blockUninterruptible_blocksIfInterruptedThenUnblocksWhenOpened()
|
||||
throws InterruptedException {
|
||||
ConditionVariable conditionVariable = buildTestConditionVariable();
|
||||
|
||||
@ -140,8 +141,8 @@ public class ConditionVariableTest {
|
||||
new Thread(
|
||||
() -> {
|
||||
conditionVariable.blockUninterruptible();
|
||||
blockReturned.set(true);
|
||||
interruptedStatusSet.set(Thread.currentThread().isInterrupted());
|
||||
blockReturned.set(true);
|
||||
});
|
||||
|
||||
blockingThread.start();
|
||||
@ -160,6 +161,58 @@ public class ConditionVariableTest {
|
||||
assertThat(conditionVariable.isOpen()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void blockUninterruptible_withTimeoutUnopened_timesOut() throws InterruptedException {
|
||||
ConditionVariable conditionVariable = buildTestConditionVariable();
|
||||
|
||||
assertThat(conditionVariable.blockUninterruptible(1)).isFalse();
|
||||
assertThat(conditionVariable.isOpen()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void blockUninterruptible_withTimeoutUnopened_blocksForAtLeastTimeout()
|
||||
throws InterruptedException {
|
||||
ConditionVariable conditionVariable = buildTestConditionVariable();
|
||||
|
||||
long startTimeMs = System.currentTimeMillis();
|
||||
assertThat(conditionVariable.blockUninterruptible(/* timeoutMs= */ 500)).isFalse();
|
||||
long endTimeMs = System.currentTimeMillis();
|
||||
|
||||
assertThat(endTimeMs - startTimeMs).isAtLeast(500);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void blockUninterruptible_withMaxTimeout_blocksUntilOpened() throws InterruptedException {
|
||||
ConditionVariable conditionVariable = buildTestConditionVariable();
|
||||
AtomicBoolean blockReturned = new AtomicBoolean();
|
||||
AtomicBoolean interruptedStatusSet = new AtomicBoolean();
|
||||
Thread blockingThread =
|
||||
new Thread(
|
||||
() -> {
|
||||
conditionVariable.blockUninterruptible(/* timeoutMs= */ Long.MAX_VALUE);
|
||||
interruptedStatusSet.set(Thread.currentThread().isInterrupted());
|
||||
blockReturned.set(true);
|
||||
});
|
||||
|
||||
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() {
|
||||
|
@ -54,6 +54,7 @@ import androidx.media3.common.Player.RepeatMode;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.Clock;
|
||||
import androidx.media3.common.util.ConditionVariable;
|
||||
import androidx.media3.common.util.HandlerWrapper;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.TraceUtil;
|
||||
@ -80,7 +81,6 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/** Implements the internal behavior of {@link ExoPlayerImpl}. */
|
||||
/* package */ final class ExoPlayerImplInternal
|
||||
@ -536,12 +536,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
handler.obtainMessage(MSG_SET_FOREGROUND_MODE, /* foregroundMode */ 1, 0).sendToTarget();
|
||||
return true;
|
||||
} else {
|
||||
AtomicBoolean processedFlag = new AtomicBoolean();
|
||||
ConditionVariable processedCondition = new ConditionVariable(clock);
|
||||
handler
|
||||
.obtainMessage(MSG_SET_FOREGROUND_MODE, /* foregroundMode */ 0, 0, processedFlag)
|
||||
.obtainMessage(MSG_SET_FOREGROUND_MODE, /* foregroundMode */ 0, 0, processedCondition)
|
||||
.sendToTarget();
|
||||
waitUninterruptibly(processedFlag, setForegroundModeTimeoutMs);
|
||||
return processedFlag.get();
|
||||
return processedCondition.blockUninterruptible(setForegroundModeTimeoutMs);
|
||||
}
|
||||
}
|
||||
|
||||
@ -560,13 +559,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
if (releasedOnApplicationThread || !playbackLooper.getThread().isAlive()) {
|
||||
return true;
|
||||
}
|
||||
AtomicBoolean processedFlag = new AtomicBoolean();
|
||||
ConditionVariable processedCondition = new ConditionVariable(clock);
|
||||
handler
|
||||
.obtainMessage(MSG_SET_VIDEO_OUTPUT, new Pair<>(videoOutput, processedFlag))
|
||||
.obtainMessage(MSG_SET_VIDEO_OUTPUT, new Pair<>(videoOutput, processedCondition))
|
||||
.sendToTarget();
|
||||
if (timeoutMs != C.TIME_UNSET) {
|
||||
waitUninterruptibly(processedFlag, timeoutMs);
|
||||
return processedFlag.get();
|
||||
return processedCondition.blockUninterruptible(timeoutMs);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -581,10 +579,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
return true;
|
||||
}
|
||||
releasedOnApplicationThread = true;
|
||||
AtomicBoolean processedFlag = new AtomicBoolean();
|
||||
handler.obtainMessage(MSG_RELEASE, processedFlag).sendToTarget();
|
||||
waitUninterruptibly(processedFlag, releaseTimeoutMs);
|
||||
return processedFlag.get();
|
||||
ConditionVariable processedCondition = new ConditionVariable(clock);
|
||||
handler.obtainMessage(MSG_RELEASE, processedCondition).sendToTarget();
|
||||
return processedCondition.blockUninterruptible(releaseTimeoutMs);
|
||||
}
|
||||
|
||||
public Looper getPlaybackLooper() {
|
||||
@ -707,13 +704,15 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
break;
|
||||
case MSG_SET_FOREGROUND_MODE:
|
||||
setForegroundModeInternal(
|
||||
/* foregroundMode= */ msg.arg1 != 0, /* processedFlag= */ (AtomicBoolean) msg.obj);
|
||||
/* foregroundMode= */ msg.arg1 != 0,
|
||||
/* processedCondition= */ (ConditionVariable) msg.obj);
|
||||
break;
|
||||
case MSG_SET_VIDEO_OUTPUT:
|
||||
Pair<Object, AtomicBoolean> setVideoOutputPayload = (Pair<Object, AtomicBoolean>) msg.obj;
|
||||
Pair<Object, ConditionVariable> setVideoOutputPayload =
|
||||
(Pair<Object, ConditionVariable>) msg.obj;
|
||||
setVideoOutputInternal(
|
||||
/* videoOutput= */ setVideoOutputPayload.first,
|
||||
/* processedFlag= */ setVideoOutputPayload.second);
|
||||
/* processedCondition= */ setVideoOutputPayload.second);
|
||||
break;
|
||||
case MSG_STOP:
|
||||
stopInternal(/* forceResetRenderers= */ false, /* acknowledgeStop= */ true);
|
||||
@ -783,7 +782,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
setVideoFrameMetadataListenerInternal((VideoFrameMetadataListener) msg.obj);
|
||||
break;
|
||||
case MSG_RELEASE:
|
||||
releaseInternal(/* processedFlag= */ (AtomicBoolean) msg.obj);
|
||||
releaseInternal(/* processedCondition= */ (ConditionVariable) msg.obj);
|
||||
// Return immediately to not send playback info updates after release.
|
||||
return true;
|
||||
default:
|
||||
@ -916,36 +915,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
playbackInfo = playbackInfo.copyWithPlaybackError(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks the current thread until a condition becomes true or the specified amount of time has
|
||||
* elapsed.
|
||||
*
|
||||
* <p>If the current thread is interrupted while waiting for the condition to become true, this
|
||||
* method will restore the interrupt <b>after</b> the condition became true or the operation times
|
||||
* out.
|
||||
*
|
||||
* @param condition The condition.
|
||||
* @param timeoutMs The time in milliseconds to wait for the condition to become true.
|
||||
*/
|
||||
private synchronized void waitUninterruptibly(AtomicBoolean condition, long timeoutMs) {
|
||||
long deadlineMs = clock.elapsedRealtime() + timeoutMs;
|
||||
long remainingMs = timeoutMs;
|
||||
boolean wasInterrupted = false;
|
||||
while (!condition.get() && remainingMs > 0) {
|
||||
try {
|
||||
clock.onThreadBlocked();
|
||||
wait(remainingMs);
|
||||
} catch (InterruptedException e) {
|
||||
wasInterrupted = true;
|
||||
}
|
||||
remainingMs = deadlineMs - clock.elapsedRealtime();
|
||||
}
|
||||
if (wasInterrupted) {
|
||||
// Restore the interrupted status.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private void setState(int state) {
|
||||
if (playbackInfo.playbackState != state) {
|
||||
if (state != Player.STATE_BUFFERING) {
|
||||
@ -1776,7 +1745,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
}
|
||||
|
||||
private void setForegroundModeInternal(
|
||||
boolean foregroundMode, @Nullable AtomicBoolean processedFlag) {
|
||||
boolean foregroundMode, @Nullable ConditionVariable processedCondition) {
|
||||
if (this.foregroundMode != foregroundMode) {
|
||||
this.foregroundMode = foregroundMode;
|
||||
if (!foregroundMode) {
|
||||
@ -1785,16 +1754,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (processedFlag != null) {
|
||||
synchronized (this) {
|
||||
processedFlag.set(true);
|
||||
notifyAll();
|
||||
}
|
||||
if (processedCondition != null) {
|
||||
processedCondition.open();
|
||||
}
|
||||
}
|
||||
|
||||
private void setVideoOutputInternal(
|
||||
@Nullable Object videoOutput, @Nullable AtomicBoolean processedFlag)
|
||||
@Nullable Object videoOutput, @Nullable ConditionVariable processedCondition)
|
||||
throws ExoPlaybackException {
|
||||
for (RendererHolder renderer : renderers) {
|
||||
renderer.setVideoOutput(videoOutput);
|
||||
@ -1803,11 +1769,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|| playbackInfo.playbackState == Player.STATE_BUFFERING) {
|
||||
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
||||
}
|
||||
if (processedFlag != null) {
|
||||
synchronized (this) {
|
||||
processedFlag.set(true);
|
||||
notifyAll();
|
||||
}
|
||||
if (processedCondition != null) {
|
||||
processedCondition.open();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1823,7 +1786,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
setState(Player.STATE_IDLE);
|
||||
}
|
||||
|
||||
private void releaseInternal(AtomicBoolean processedFlag) {
|
||||
private void releaseInternal(ConditionVariable processedCondition) {
|
||||
try {
|
||||
resetInternal(
|
||||
/* resetRenderers= */ true,
|
||||
@ -1837,10 +1800,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
setState(Player.STATE_IDLE);
|
||||
} finally {
|
||||
playbackLooperProvider.releaseLooper();
|
||||
synchronized (this) {
|
||||
processedFlag.set(true);
|
||||
notifyAll();
|
||||
}
|
||||
processedCondition.open();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,7 +161,8 @@ public final class ExternallyLoadedImagePlaybackTest {
|
||||
MediaSource.Factory mediaSourceFactory =
|
||||
new DefaultMediaSourceFactory(applicationContext)
|
||||
.setExternalImageLoader(
|
||||
unused -> listeningExecutorService.submit(loadingComplete::blockUninterruptible));
|
||||
unused ->
|
||||
listeningExecutorService.submit(() -> loadingComplete.blockUninterruptible()));
|
||||
ExoPlayer player =
|
||||
new ExoPlayer.Builder(applicationContext, renderersFactory)
|
||||
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
||||
|
Loading…
x
Reference in New Issue
Block a user