diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index 6a24b6a23a..e25d514ebe 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -446,6 +446,8 @@ public class kotlinx/coroutines/JobSupport : kotlinx/coroutines/ChildJob, kotlin public abstract class kotlinx/coroutines/MainCoroutineDispatcher : kotlinx/coroutines/CoroutineDispatcher { public fun ()V public abstract fun getImmediate ()Lkotlinx/coroutines/MainCoroutineDispatcher; + public fun toString ()Ljava/lang/String; + protected final fun toStringInternalImpl ()Ljava/lang/String; } public final class kotlinx/coroutines/NonCancellable : kotlin/coroutines/AbstractCoroutineContextElement, kotlinx/coroutines/Job { diff --git a/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt b/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt index 3f2ddcd69f..daba38f0fd 100644 --- a/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt +++ b/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt @@ -43,4 +43,27 @@ public abstract class MainCoroutineDispatcher : CoroutineDispatcher() { * [Dispatchers.Main] supports immediate execution for Android, JavaFx and Swing platforms. */ public abstract val immediate: MainCoroutineDispatcher + + /** + * Returns a name of this main dispatcher for debugging purposes. This implementation returns + * `Dispatchers.Main` or `Dispatchers.Main.immediate` if it is the same as the corresponding + * reference in [Dispatchers] or a short class-name representation with address otherwise. + */ + override fun toString(): String = toStringInternalImpl() ?: "$classSimpleName@$hexAddress" + + /** + * Internal method for more specific [toString] implementations. It returns non-null + * string if this dispatcher is set in the platform as the main one. + * @suppress + */ + @InternalCoroutinesApi + protected fun toStringInternalImpl(): String? { + val main = Dispatchers.Main + if (this === main) return "Dispatchers.Main" + val immediate = + try { main.immediate } + catch (e: UnsupportedOperationException) { null } + if (this === immediate) return "Dispatchers.Main.immediate" + return null + } } diff --git a/kotlinx-coroutines-core/js/src/Dispatchers.kt b/kotlinx-coroutines-core/js/src/Dispatchers.kt index 995801ea0d..033b39c7e0 100644 --- a/kotlinx-coroutines-core/js/src/Dispatchers.kt +++ b/kotlinx-coroutines-core/js/src/Dispatchers.kt @@ -26,5 +26,5 @@ private class JsMainDispatcher(val delegate: CoroutineDispatcher) : MainCoroutin override fun dispatchYield(context: CoroutineContext, block: Runnable) = delegate.dispatchYield(context, block) - override fun toString(): String = delegate.toString() + override fun toString(): String = toStringInternalImpl() ?: delegate.toString() } diff --git a/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt b/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt index b3e49752e0..ddfcdbb142 100644 --- a/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt +++ b/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt @@ -114,7 +114,7 @@ private class MissingMainCoroutineDispatcher( } } - override fun toString(): String = "Main[missing${if (cause != null) ", cause=$cause" else ""}]" + override fun toString(): String = "Dispatchers.Main[missing${if (cause != null) ", cause=$cause" else ""}]" } /** diff --git a/kotlinx-coroutines-core/jvm/test/DispatchersToStringTest.kt b/kotlinx-coroutines-core/jvm/test/DispatchersToStringTest.kt new file mode 100644 index 0000000000..7adaa2a4b6 --- /dev/null +++ b/kotlinx-coroutines-core/jvm/test/DispatchersToStringTest.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +import kotlin.test.* + +class DispatchersToStringTest { + @Test + fun testStrings() { + assertEquals("Dispatchers.Unconfined", Dispatchers.Unconfined.toString()) + assertEquals("Dispatchers.Default", Dispatchers.Default.toString()) + assertEquals("Dispatchers.IO", Dispatchers.IO.toString()) + assertEquals("Dispatchers.Main[missing]", Dispatchers.Main.toString()) + assertEquals("Dispatchers.Main[missing]", Dispatchers.Main.immediate.toString()) + } +} \ No newline at end of file diff --git a/kotlinx-coroutines-core/native/src/Dispatchers.kt b/kotlinx-coroutines-core/native/src/Dispatchers.kt index 6c650046a0..aca1cc0693 100644 --- a/kotlinx-coroutines-core/native/src/Dispatchers.kt +++ b/kotlinx-coroutines-core/native/src/Dispatchers.kt @@ -26,5 +26,5 @@ private class NativeMainDispatcher(val delegate: CoroutineDispatcher) : MainCoro override fun dispatchYield(context: CoroutineContext, block: Runnable) = delegate.dispatchYield(context, block) - override fun toString(): String = delegate.toString() + override fun toString(): String = toStringInternalImpl() ?: delegate.toString() } diff --git a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt index af1ab4331b..1693409875 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), "Main") + HandlerContext(Looper.getMainLooper().asHandler(async = true)) override fun hintOnError(): String? = "For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used" @@ -97,7 +97,7 @@ internal fun Looper.asHandler(async: Boolean): Handler { @JvmField @Deprecated("Use Dispatchers.Main instead", level = DeprecationLevel.HIDDEN) -internal val Main: HandlerDispatcher? = runCatching { HandlerContext(Looper.getMainLooper().asHandler(async = true), "Main") }.getOrNull() +internal val Main: HandlerDispatcher? = runCatching { HandlerContext(Looper.getMainLooper().asHandler(async = true)) }.getOrNull() /** * Implements [CoroutineDispatcher] on top of an arbitrary Android [Handler]. @@ -149,12 +149,10 @@ internal class HandlerContext private constructor( } } - override fun toString(): String = - if (name != null) { - if (invokeImmediately) "$name [immediate]" else name - } else { - handler.toString() - } + override fun toString(): String = toStringInternalImpl() ?: run { + val str = name ?: handler.toString() + if (invokeImmediately) "$str.immediate" else str + } override fun equals(other: Any?): Boolean = other is HandlerContext && other.handler === handler override fun hashCode(): Int = System.identityHashCode(handler) diff --git a/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt b/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt index 1501639e5d..55decde61b 100644 --- a/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt +++ b/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt @@ -123,8 +123,8 @@ class HandlerDispatcherTest : TestBase() { ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 28) val main = Looper.getMainLooper().asHandler(async = true).asCoroutineDispatcher("testName") assertEquals("testName", main.toString()) - assertEquals("testName [immediate]", main.immediate.toString()) - assertEquals("testName [immediate]", main.immediate.immediate.toString()) + assertEquals("testName.immediate", main.immediate.toString()) + assertEquals("testName.immediate", main.immediate.immediate.toString()) } private suspend fun Job.join(mainLooper: ShadowLooper) { @@ -155,4 +155,10 @@ class HandlerDispatcherTest : TestBase() { yield() // yield back finish(5) } + + @Test + fun testMainDispatcherToString() { + assertEquals("Dispatchers.Main", Dispatchers.Main.toString()) + assertEquals("Dispatchers.Main.immediate", Dispatchers.Main.immediate.toString()) + } } diff --git a/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt b/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt index 5f39bf758c..a13a68368e 100644 --- a/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt +++ b/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt @@ -70,7 +70,7 @@ private object ImmediateJavaFxDispatcher : JavaFxDispatcher() { override fun isDispatchNeeded(context: CoroutineContext): Boolean = !Platform.isFxApplicationThread() - override fun toString() = "JavaFx [immediate]" + override fun toString() = toStringInternalImpl() ?: "JavaFx.immediate" } /** @@ -85,7 +85,7 @@ internal object JavaFx : JavaFxDispatcher() { override val immediate: MainCoroutineDispatcher get() = ImmediateJavaFxDispatcher - override fun toString() = "JavaFx" + override fun toString() = toStringInternalImpl() ?: "JavaFx" } private val pulseTimer by lazy { diff --git a/ui/kotlinx-coroutines-javafx/test/JavaFxDispatcherTest.kt b/ui/kotlinx-coroutines-javafx/test/JavaFxDispatcherTest.kt index 724be6d77b..24c5c132fd 100644 --- a/ui/kotlinx-coroutines-javafx/test/JavaFxDispatcherTest.kt +++ b/ui/kotlinx-coroutines-javafx/test/JavaFxDispatcherTest.kt @@ -7,6 +7,8 @@ package kotlinx.coroutines.javafx import javafx.application.* import kotlinx.coroutines.* import org.junit.* +import org.junit.Test +import kotlin.test.* class JavaFxDispatcherTest : TestBase() { @Before @@ -56,4 +58,10 @@ class JavaFxDispatcherTest : TestBase() { finish(5) } } + + @Test + fun testMainDispatcherToString() { + assertEquals("Dispatchers.Main", Dispatchers.Main.toString()) + assertEquals("Dispatchers.Main.immediate", Dispatchers.Main.immediate.toString()) + } } \ No newline at end of file diff --git a/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt b/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt index 50efe47126..77f109df91 100644 --- a/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt +++ b/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt @@ -67,7 +67,7 @@ private object ImmediateSwingDispatcher : SwingDispatcher() { override fun isDispatchNeeded(context: CoroutineContext): Boolean = !SwingUtilities.isEventDispatchThread() - override fun toString() = "Swing [immediate]" + override fun toString() = toStringInternalImpl() ?: "Swing.immediate" } /** @@ -77,5 +77,5 @@ internal object Swing : SwingDispatcher() { override val immediate: MainCoroutineDispatcher get() = ImmediateSwingDispatcher - override fun toString() = "Swing" + override fun toString() = toStringInternalImpl() ?: "Swing" } diff --git a/ui/kotlinx-coroutines-swing/test/SwingTest.kt b/ui/kotlinx-coroutines-swing/test/SwingTest.kt index cbed5bf1e9..7e53e57b17 100644 --- a/ui/kotlinx-coroutines-swing/test/SwingTest.kt +++ b/ui/kotlinx-coroutines-swing/test/SwingTest.kt @@ -97,4 +97,10 @@ class SwingTest : TestBase() { yield() // yield back finish(5) } + + @Test + fun testMainDispatcherToString() { + assertEquals("Dispatchers.Main", Dispatchers.Main.toString()) + assertEquals("Dispatchers.Main.immediate", Dispatchers.Main.immediate.toString()) + } } \ No newline at end of file