Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MediaController.releaseFuture() cancellation race #345

Closed
1 task
bubenheimer opened this issue Apr 20, 2023 · 4 comments
Closed
1 task

MediaController.releaseFuture() cancellation race #345

bubenheimer opened this issue Apr 20, 2023 · 4 comments
Assignees
Labels

Comments

@bubenheimer
Copy link

Media3 Version

Media3 1.0.1

Devices that reproduce the issue

N/A

Devices that do not reproduce the issue

No response

Reproducible in the demo app?

Not tested

Reproduction steps

MediaController.releaseFuture() seems to have a cancellation race that could leak the controller when the future gets done between isDone() and cancel():

public static void releaseFuture(Future<? extends MediaController> controllerFuture) {
if (!controllerFuture.isDone()) {
controllerFuture.cancel(/* mayInterruptIfRunning= */ true);
return;
}
MediaController controller;
try {
controller = controllerFuture.get();
} catch (CancellationException | ExecutionException | InterruptedException e) {
return;
}
controller.release();
}

Expected result

N/A

Actual result

N/A

Media

N/A

Bug Report

@tonihei
Copy link
Collaborator

tonihei commented Apr 20, 2023

All of the methods of MediaController are supposed to be called from the specified application thread (see https://developer.android.com/reference/androidx/media3/session/MediaController#threading-model), including releaseFuture. The Future itself is also only marked as done on the same thread... so the case you describe shouldn't happen? Did you see this happen somehow and was it caused by using a different thread for calling releaseFuture? If so, I'd be curious to hear if the different thread was needed or more natural to use in your app.

@tonihei tonihei self-assigned this Apr 20, 2023
@bubenheimer
Copy link
Author

bubenheimer commented Apr 20, 2023

Ah, right, I think I looked at that earlier and decided to go with it for that reason (release() must be called on the right thread). The intended contract is not obvious, though, as releaseFuture() is static and not directly tied to the specified applicationLooper. If it does get a hold of the controller without cancelling first, it would check if it's on the right thread, otherwise it would not.

To validate correctness I'd need to find the internals of where the future is marked done.

Perhaps the javadoc could clarify the contract. Also, can't you skip the isDone() check and just test the return value of cancel()? This would seem to address the unclear correctness part. Or would that hang if everything runs on the same thread that is also a looper thread?

@bubenheimer
Copy link
Author

bubenheimer commented Apr 20, 2023

The reason I tripped over this was my MediaSessionService leaking, so I did a code inspection. I just use the main thread at the moment, on the Controller side.

@tonihei
Copy link
Collaborator

tonihei commented Apr 21, 2023

Thanks for the suggestion - we can mention it in the docs (and use future.cancel only without the additional isDone check)

rohitjoins pushed a commit that referenced this issue Apr 24, 2023
And remove unnecessary check for isDone.

Issue: #345
PiperOrigin-RevId: 525999615
@tonihei tonihei closed this as completed Apr 25, 2023
icbaker pushed a commit that referenced this issue May 17, 2023
And remove unnecessary check for isDone.

Issue: #345
PiperOrigin-RevId: 525999615
(cherry picked from commit 186f3d5)
@androidx androidx locked and limited conversation to collaborators Jun 25, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

3 participants