kotlinx.coroutines
kotlinx.coroutines copied to clipboard
Dispatchers.setMain does not implement a correct Dispatchers.Main.immediate
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-coroutine-dispatcher/immediate.html states that, if Dispatchers.Main.immediate is supported, it behaves like Dispatchers.Unconfined when already in the correct execution context; otherwise, it should throw UnsupportedOperationException. However, this is not the case for Dispatchers.setMain, which will just use the dispatcher itself for Dispatchers.Main.immediate, without preventing stack overflows or eager execution of coroutines.
This causes real problems in the test code. The main example is viewModelScope, a CoroutineScope that corresponds to a ViewScope, Android-specific storage of data for use in UI. Because of being UI-related, viewModelScope uses the UI thread by default, and to avoid unnecessary dispatches when working with a ViewModel from the main thread, it uses Dispatchers.Main.immediate.
Code that tests functions using viewModelScope is typically structured in a way that expects the dispatches to happen eagerly—corresponding to real-world behavior in the typical case—which necessitates the use of UnconfinedTestDispatcher in Dispatchers.setMain for such tests. However, this will lead to incorrect dispatching behavior for just Dispatchers.Main, which is supposed to behave like a StandardTestDispatcher.
The most straightforward idea is two-part:
1. Make StandardTestDispatcher a MainCoroutineDispatcher, confined to the thread and exposing a proper immediate.
Problems:
- What is the test thread? If we just check the thread at the time of creation of the test dispatcher, can this can cause problems with multithreaded test runners?
- Calls to
advance*/runCurrentfrom other threads will hang if we implement this naively. Maybe a better idea is not to confine the execution to one thread, but to consider the thread doing the innermostadvance*/runCurrentto be the "main" thread.
2. Throw UnsupportedOperationException for Dispatchers.Main.immediate for non-MainCoroutineDispatcher arguments to Dispatchers.setMain.
This is a breaking change.
- We could lessen the issue by also making
UnconfinedTestDispatcheraMainCoroutineDispatcher, but that would promote its incorrect use. - Maybe, instead of breaking
Dispatchers.setMain, we should deprecate it in favor of something else. For example, if we end up implementing https://github.com/Kotlin/kotlinx.coroutines/issues/982, we could just provide something likeDispatchers.mock(main, default)that would also fix the issue withMain.immediate.