androidx / androidx/media

Wrong thread in cancelation when removeListener() in AndroidTest

Open
#3,005 8 comments 0 reactions 1 assignee View on GitHub

Nobody has claimed this yet.

bug needs triage
Dominant language
Java
Stars
3k
Forks
955
Avg merge
12d 14h
Merged PRs (30d)
2

Description

Version

Media3 1.9.0 and 1.8.0

More version details

We have the following problem:
Every time a test fails and the player has been initialised, we get the following exception:

java.lang.IllegalStateException: Player is accessed on the wrong thread.
Current thread: “Instr: eu.xxx.app.testutils.helper.XxxAndroidTestRunner”
Expected thread: 'main'

We are running an Android test. The app is partly in Compose and partly in XML Views.

In Compose, we have integrated ExoPlayer with:

val exoPlayer = remember {
        ExoPlayer.Builder(context)
            .setLooper(android.os.Looper.getMainLooper())
            .build().apply {
                prepare()
            }
    }

The problem seems to be this code.
That code is part of the media3 PlayerExtensionsKt.class.

private suspend fun Player.listenImpl(
  events: Player.Events?,
  onEvents: Player.(Player.Events) -> Unit,
): Nothing {
  lateinit var listener: PlayerListener
  try {
    suspendCancellableCoroutine { continuation ->
      listener = PlayerListener(events, onEvents, continuation)
      continuation.invokeOnCancellation { listener.isCancelled.set(true) }
      addListener(listener)
    }
  } finally {
    removeListener(listener)
  }
}

Normally, the code is only accessible via a check on the main thread.

However, if there is a cancellation, then finally is called with the AndroidTestRunner thread because the cancellation comes from the thread. This causes the check in removeListener() to fail and throws the wrong thread exception.

Im an not sure, if it is the code or if i do something wrong. Any ideas how to fix that?

Devices that reproduce the issue

Emulator Android 15

But others aswell

Devices that do not reproduce the issue

No response

Reproducible in the demo app?

Not tested

Reproduction steps

I'm not sure how to set this up with minimal effort.

Expected result

It should not crash in failed ui tests

Actual result
java.lang.IllegalStateException: Player is accessed on the wrong thread.
Current thread: 'Instr: eu.xxx.app.testutils.helper.XxxAndroidTestRunner'
Expected thread: 'main'
See https://developer.android.com/guide/topics/media/issues/player-accessed-on-wrong-thread
at androidx.media3.exoplayer.ExoPlayerImpl.verifyApplicationThread(ExoPlayerImpl.java:3102)
at androidx.media3.exoplayer.ExoPlayerImpl.removeListener(ExoPlayerImpl.java:1918)
at androidx.media3.common.PlayerExtensionsKt.listenImpl(PlayerExtensions.kt:131)
at androidx.media3.common.PlayerExtensionsKt.access$listenImpl(PlayerExtensions.kt:1)
at androidx.media3.common.PlayerExtensionsKt$listenImpl$1.invokeSuspend(Unknown Source:13)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
at androidx.compose.ui.test.ApplyingContinuationInterceptor$SendApplyContinuation.resumeWith(ApplyingContinuationInterceptor.kt:52)
at androidx.compose.ui.test.FrameDeferringContinuationInterceptor$FrameDeferredContinuation.resumeWith(FrameDeferringContinuationInterceptor.jvm.kt:187)
at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:163)
at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:152)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:470)
at kotlinx.coroutines.CancellableContinuationImpl.cancel(CancellableContinuationImpl.kt:214)
at kotlinx.coroutines.CancellableContinuationImpl.parentCancelled$kotlinx_coroutines_core(CancellableContinuationImpl.kt:221)
at kotlinx.coroutines.ChildContinuation.invoke(CancellableContinuationImpl.kt:698)
at kotlinx.coroutines.JobSupport.notifyCancelling(JobSupport.kt:1604)
at kotlinx.coroutines.JobSupport.tryMakeCancelling(JobSupport.kt:826)
at kotlinx.coroutines.JobSupport.makeCancelling(JobSupport.kt:786)
at kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core(JobSupport.kt:702)
at kotlinx.coroutines.JobSupport.parentCancelled(JobSupport.kt:668)
at kotlinx.coroutines.ChildHandleNode.invoke(JobSupport.kt:1580)
at kotlinx.coroutines.JobSupport.notifyCancelling(JobSupport.kt:1604)
at kotlinx.coroutines.JobSupport.tryMakeCompletingSlowPath(JobSupport.kt:931)
at kotlinx.coroutines.JobSupport.tryMakeCompleting(JobSupport.kt:894)
at kotlinx.coroutines.JobSupport.cancelMakeCompleting(JobSupport.kt:727)
at kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core(JobSupport.kt:698)
at kotlinx.coroutines.JobSupport.cancelInternal(JobSupport.kt:663)
at kotlinx.coroutines.JobSupport.cancel(JobSupport.kt:648)
at kotlinx.coroutines.Job$DefaultImpls.cancel$default(Job.kt:207)
at androidx.compose.runtime.Recomposer.cancel(Recomposer.kt:1208)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1$1$1$1$1.invokeSuspend(ComposeUiTest.android.kt:1001)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1$1$1$1$1.invoke(Unknown Source:8)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1$1$1$1$1.invoke(Unknown Source:2)
at androidx.compose.ui.test.EspressoLink.withStrategy(EspressoLink.android.kt:69)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1$1.invokeSuspend(ComposeUiTest.android.kt:602)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1$1.invoke(Unknown Source:8)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1$1.invoke(Unknown Source:4)
at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTest$2$1$1.invokeSuspend(TestBuilders.kt:317)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
at kotlinx.coroutines.test.TestDispatcher.processEvent$kotlinx_coroutines_test(TestDispatcher.kt:24)
at kotlinx.coroutines.test.TestCoroutineScheduler.tryRunNextTaskUnless$kotlinx_coroutines_test(TestCoroutineScheduler.kt:99)
at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTest$2$1$workRunner$1.invokeSuspend(TestBuilders.kt:326)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:263)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:94)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:70)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source:1)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:48)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source:1)
at kotlinx.coroutines.test.TestBuildersJvmKt.createTestResult(TestBuildersJvm.kt:10)
at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt.runTest-8Mi8wO0(TestBuilders.kt:309)
at kotlinx.coroutines.test.TestBuildersKt.runTest-8Mi8wO0(Unknown Source:1)
at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt.runTest-8Mi8wO0(TestBuilders.kt:167)
at kotlinx.coroutines.test.TestBuildersKt.runTest-8Mi8wO0(Unknown Source:1)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.runTest(ComposeUiTest.android.kt:585)
at androidx.compose.ui.test.junit4.AndroidComposeTestRule$apply$1.evaluate(AndroidComposeTestRule.android.kt:334)
at eu.xxx.app.testutils.rules.PrepareAppRule$apply$1.evaluate(PrepareAppRule.kt:34)
at eu.xxx.app.testutils.rules.AllureInformationRule$apply$1.evaluate(AllureInformationRule.kt:18)
at androidx.test.rule.GrantPermissionRule$RequestPermissionStatement.evaluate(GrantPermissionRule.java:150)
at eu.xxx.app.testutils.rules.DisableAnimationsRule$apply$1.evaluate(DisableAnimationsRule.kt:20)
at com.adevinta.android.barista.rule.cleardata.ClearDatabaseRule$apply$1.evaluate(ClearDatabaseRule.kt:31)
at com.adevinta.android.barista.rule.cleardata.ClearPreferencesRule$1.evaluate(ClearPreferencesRule.java:26)
at io.qameta.allure.android.rules.LogcatRule$apply$1.evaluate(LogcatRule.kt:23)
at io.qameta.allure.android.rules.ScreenshotRule$apply$1.evaluate(ScreenshotRule.kt:22)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:68)
at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:59)
at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:467)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2594)
Media

No Media

Bug Report
  • You will email the zip file produced by adb bugreport to android-media-github@google.com after filing this issue.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.