From 0659bb8da69d119900770df5acaf2d5b0e6b67c2 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Mon, 20 Dec 2021 15:55:36 +0300 Subject: [PATCH 1/4] 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. --- ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt index ca8dd0d0ca..7400f12ff3 100644 --- a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt +++ b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt @@ -52,7 +52,7 @@ public sealed class HandlerDispatcher : MainCoroutineDispatcher(), Delay { internal class AndroidDispatcherFactory : MainDispatcherFactory { override fun createDispatcher(allFactories: List) = - HandlerContext(Looper.getMainLooper().asHandler(async = true)) + HandlerContext(Looper.getMainLooper()!!.asHandler(async = true)) override fun hintOnError(): String = "For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used" From bd2a317628cfefa1023cc7712b0ddbfdc617830d Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Tue, 21 Dec 2021 12:25:04 +0300 Subject: [PATCH 2/4] Fix the Swing module successfully creating a broken Main dispatcher This is relevant for headless environments. --- ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt b/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt index d2d9b78658..3b43483dbc 100644 --- a/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt +++ b/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt @@ -74,6 +74,16 @@ private object ImmediateSwingDispatcher : SwingDispatcher() { * Dispatches execution onto Swing event dispatching thread and provides native [delay] support. */ internal object Swing : SwingDispatcher() { + + /* A workaround so that the dispatcher's initialization crashes with an exception if running in a headless + environment. This is needed so that this broken dispatcher is not used as the source of delays. */ + init { + Timer(1) { }.apply { + isRepeats = false + start() + } + } + override val immediate: MainCoroutineDispatcher get() = ImmediateSwingDispatcher From 322849cdea2fb1fa50478467cb35ac91a12ab22e Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Tue, 21 Dec 2021 13:05:51 +0300 Subject: [PATCH 3/4] Document the failure mode more carefully --- .../common/src/internal/MainDispatcherFactory.kt | 5 +++++ ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/internal/MainDispatcherFactory.kt b/kotlinx-coroutines-core/common/src/internal/MainDispatcherFactory.kt index 0b8638687b..45872f179d 100644 --- a/kotlinx-coroutines-core/common/src/internal/MainDispatcherFactory.kt +++ b/kotlinx-coroutines-core/common/src/internal/MainDispatcherFactory.kt @@ -14,6 +14,11 @@ public interface MainDispatcherFactory { /** * Creates the main dispatcher. [allFactories] parameter contains all factories found by service loader. * This method is not guaranteed to be idempotent. + * + * It is required that this method fails with an exception instead of returning an instance that doesn't work + * correctly as a [Delay]. + * The reason for this is that, on the JVM, [DefaultDelay] will use [Dispatchers.Main] for most delays by default + * if this method returns an instance without throwing. */ public fun createDispatcher(allFactories: List): MainCoroutineDispatcher diff --git a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt index 7400f12ff3..77b9642127 100644 --- a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt +++ b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt @@ -51,8 +51,10 @@ public sealed class HandlerDispatcher : MainCoroutineDispatcher(), Delay { internal class AndroidDispatcherFactory : MainDispatcherFactory { - override fun createDispatcher(allFactories: List) = - HandlerContext(Looper.getMainLooper()!!.asHandler(async = true)) + override fun createDispatcher(allFactories: List) { + val mainLooper = Looper.getMainLooper() ?: throw IllegalStateException("The main looper is not available") + HandlerContext(mainLooper.asHandler(async = true)) + } override fun hintOnError(): String = "For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used" From f7f8b5d013b469fbda89d362bc88ab525927f777 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Tue, 21 Dec 2021 13:39:22 +0300 Subject: [PATCH 4/4] Fix --- ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt index 77b9642127..ffd5df060c 100644 --- a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt +++ b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt @@ -51,9 +51,9 @@ public sealed class HandlerDispatcher : MainCoroutineDispatcher(), Delay { internal class AndroidDispatcherFactory : MainDispatcherFactory { - override fun createDispatcher(allFactories: List) { + override fun createDispatcher(allFactories: List): MainCoroutineDispatcher { val mainLooper = Looper.getMainLooper() ?: throw IllegalStateException("The main looper is not available") - HandlerContext(mainLooper.asHandler(async = true)) + return HandlerContext(mainLooper.asHandler(async = true)) } override fun hintOnError(): String = "For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used"