Description
The current threading model came up in multiple issues recently and we are considering options to improve the current situation. If someone feels strongly for/against a solution, please let us know.
In particular, if you are using ExoPlayer on a background thread we would be interested why you feel this is needed.
Background
The current player threading model requires that the player is accessed from a single thread. The player also calls all listener callbacks on this thread. This thread is the thread the player is created on or the main thread if that thread doesn't have a Looper.
This model may cause problems in the following situations:
- When executing code that must be called on the main thread in one of the listener callbacks (e.g. the UI components and ImaAdsLoader do that). If the listener callbacks happen on a background thread, the code will throw (see ExoPlayerSimpleView updating not on the ui thread and crushing the app #4439).
- When code calls the player from another thread (see e.g. Allow specifying a Looper when instantiating ExoPlayer #4278 and Crash reported pointing internal to player. #4408):
This may happen if:- the developer is unsure which thread certain methods run on
- or just because calling the player directly is more convenient than posting to another thread
The first example causes problems, although the player is used in the documented way and only ExoPlayer's own UI components violate the contract. The second example violates the documented rules outright, but it's easy to get wrong and developers may feel that having to post onto the correct thread is unnecessarily complicated.
Options for improvement
There are four main options to improve the current model:
- Keep it as it is and only amend documentation for UI components and other components relying on being called on the main thread. May also add assertions to enforce correct usage.
- Pro: No code change needed. Keeps current behaviour.
- Con: This would prevent all people using the UI components from accessing the player on a background thread (=violation between what we advertise and what we actually support).
- Require all implementations to use the main thread to access the player unless they explicitly specify a Looper for player access when creating the player.
- Pro: Prevents the problems above by moving most users to the main thread automatically. Simplifies threading rules.
- Con: Still prevents the combination of background thread and using UI components, but at least makes it an explicit choice. May silently change the current behaviour of implementations (unless we enforce using the explicit Looper parameter with assertions).
- Require all implementations to use the main thread to access the player.
- Pro: Prevents the problems listed above. Also easy to reason about and simplifies bug search.
- Con: May be too restrictive in case there are legitimate reasons where the player needs to be accessed on a background thread. May also require many developers to change their code to adhere to these rules.
- Synchronize all access to the player + allow code registering a listener to specify the Looper thread on which the listener is called.
- Pro: Prevents the problems above by allowing a more flexible use of the player. Supports other use cases where the player is accessed in parallel. Prevents accidental developer bugs as it's by definition not possible to use the player in a wrong way.
- Con: Introduces complications with maintaining the player. Difficult to reason about. Listener callbacks may see unexpected state changes if the player changes while processing one action. May also need a way to support transactional player changes (or another external way to lock access to the player). May create deadlocks in existing code if external locking is used for player/callback access.
Option 4 is the least preferred option, as it involves many changes and may introduce new problems.
Options 1-3 are all feasible. We need to establish whether using the player on a background thread is (a) necessary for any case or (b) widely used. If neither is true, option 2 or 3 may be best.