Skip to content

Commit d84b856

Browse files
dkhalanskyjbpablobaxter
authored andcommitted
Fix Dispatchers.Main not being fully initialized on Android and Swing (Kotlin#3101)
* Fix Dispatchers.Main not being fully initialized on Android If `unitTests.returnDefaultValues=true` is set, then `Looper.getMainLooper()` may return `null`. The type system of Kotlin is tricked to believe that the method can't ever return `null`, so doesn't check for it anywhere. As a result, despite not being fully initialized, `Dispatchers.Main` is considered correct. This was not an issue before, as it only surfaced when `Dispatchers.Main` was used. However, now, `Main` is the source of time for delays, so any delay will throw something incomprehensible if this happens.
1 parent 77efb0e commit d84b856

File tree

3 files changed

+19
-2
lines changed

3 files changed

+19
-2
lines changed

kotlinx-coroutines-core/common/src/internal/MainDispatcherFactory.kt

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ public interface MainDispatcherFactory {
1414
/**
1515
* Creates the main dispatcher. [allFactories] parameter contains all factories found by service loader.
1616
* This method is not guaranteed to be idempotent.
17+
*
18+
* It is required that this method fails with an exception instead of returning an instance that doesn't work
19+
* correctly as a [Delay].
20+
* The reason for this is that, on the JVM, [DefaultDelay] will use [Dispatchers.Main] for most delays by default
21+
* if this method returns an instance without throwing.
1722
*/
1823
public fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher
1924

ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt

+4-2
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,10 @@ public sealed class HandlerDispatcher : MainCoroutineDispatcher(), Delay {
5151

5252
internal class AndroidDispatcherFactory : MainDispatcherFactory {
5353

54-
override fun createDispatcher(allFactories: List<MainDispatcherFactory>) =
55-
HandlerContext(Looper.getMainLooper().asHandler(async = true))
54+
override fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher {
55+
val mainLooper = Looper.getMainLooper() ?: throw IllegalStateException("The main looper is not available")
56+
return HandlerContext(mainLooper.asHandler(async = true))
57+
}
5658

5759
override fun hintOnError(): String = "For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used"
5860

ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt

+10
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ private object ImmediateSwingDispatcher : SwingDispatcher() {
7474
* Dispatches execution onto Swing event dispatching thread and provides native [delay] support.
7575
*/
7676
internal object Swing : SwingDispatcher() {
77+
78+
/* A workaround so that the dispatcher's initialization crashes with an exception if running in a headless
79+
environment. This is needed so that this broken dispatcher is not used as the source of delays. */
80+
init {
81+
Timer(1) { }.apply {
82+
isRepeats = false
83+
start()
84+
}
85+
}
86+
7787
override val immediate: MainCoroutineDispatcher
7888
get() = ImmediateSwingDispatcher
7989

0 commit comments

Comments
 (0)