Skip to content

Commit 5a62781

Browse files
authored
Implement setMain in common code (Kotlin#2967)
Fixes Kotlin#1720
1 parent 2706a76 commit 5a62781

14 files changed

+142
-141
lines changed

kotlinx-coroutines-core/js/src/Dispatchers.kt

+15-1
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,22 @@ import kotlin.coroutines.*
88

99
public actual object Dispatchers {
1010
public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
11-
public actual val Main: MainCoroutineDispatcher = JsMainDispatcher(Default, false)
11+
public actual val Main: MainCoroutineDispatcher
12+
get() = injectedMainDispatcher ?: mainDispatcher
1213
public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
14+
15+
private val mainDispatcher = JsMainDispatcher(Default, false)
16+
private var injectedMainDispatcher: MainCoroutineDispatcher? = null
17+
18+
@PublishedApi
19+
internal fun injectMain(dispatcher: MainCoroutineDispatcher) {
20+
injectedMainDispatcher = dispatcher
21+
}
22+
23+
@PublishedApi
24+
internal fun resetInjectedMain() {
25+
injectedMainDispatcher = null
26+
}
1327
}
1428

1529
private class JsMainDispatcher(

kotlinx-coroutines-core/native/src/Dispatchers.kt

+17-1
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,26 @@ package kotlinx.coroutines
66

77
import kotlin.coroutines.*
88

9+
/** Not inside [Dispatchers], as otherwise mutating this throws an `InvalidMutabilityException`. */
10+
private var injectedMainDispatcher: MainCoroutineDispatcher? = null
11+
912
public actual object Dispatchers {
1013
public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
11-
public actual val Main: MainCoroutineDispatcher = NativeMainDispatcher(Default)
14+
public actual val Main: MainCoroutineDispatcher
15+
get() = injectedMainDispatcher ?: mainDispatcher
1216
public actual val Unconfined: CoroutineDispatcher get() = kotlinx.coroutines.Unconfined // Avoid freezing
17+
18+
private val mainDispatcher = NativeMainDispatcher(Default)
19+
20+
@PublishedApi
21+
internal fun injectMain(dispatcher: MainCoroutineDispatcher) {
22+
injectedMainDispatcher = dispatcher
23+
}
24+
25+
@PublishedApi
26+
internal fun resetInjectedMain() {
27+
injectedMainDispatcher = null
28+
}
1329
}
1430

1531
private class NativeMainDispatcher(val delegate: CoroutineDispatcher) : MainCoroutineDispatcher() {

kotlinx-coroutines-test/common/src/TestDispatchers.kt

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
/*
22
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
4+
@file:JvmName("TestDispatchers")
45

56
package kotlinx.coroutines.test
67

78
import kotlinx.coroutines.*
9+
import kotlinx.coroutines.test.internal.*
10+
import kotlin.jvm.*
811

912
/**
1013
* Sets the given [dispatcher] as an underlying dispatcher of [Dispatchers.Main].
@@ -13,7 +16,10 @@ import kotlinx.coroutines.*
1316
* It is unsafe to call this method if alive coroutines launched in [Dispatchers.Main] exist.
1417
*/
1518
@ExperimentalCoroutinesApi
16-
public expect fun Dispatchers.setMain(dispatcher: CoroutineDispatcher)
19+
public fun Dispatchers.setMain(dispatcher: CoroutineDispatcher) {
20+
require(dispatcher !is TestMainDispatcher) { "Dispatchers.setMain(Dispatchers.Main) is prohibited, probably Dispatchers.resetMain() should be used instead" }
21+
getTestMainDispatcher().setDispatcher(dispatcher)
22+
}
1723

1824
/**
1925
* Resets state of the [Dispatchers.Main] to the original main dispatcher.
@@ -23,4 +29,6 @@ public expect fun Dispatchers.setMain(dispatcher: CoroutineDispatcher)
2329
* It is unsafe to call this method if alive coroutines launched in [Dispatchers.Main] exist.
2430
*/
2531
@ExperimentalCoroutinesApi
26-
public expect fun Dispatchers.resetMain()
32+
public fun Dispatchers.resetMain() {
33+
getTestMainDispatcher().resetDispatcher()
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.coroutines.test.internal
6+
import kotlinx.coroutines.*
7+
import kotlin.coroutines.*
8+
9+
/**
10+
* The testable main dispatcher used by kotlinx-coroutines-test.
11+
* It is a [MainCoroutineDispatcher] that delegates all actions to a settable delegate.
12+
*/
13+
internal class TestMainDispatcher(private var delegate: CoroutineDispatcher):
14+
MainCoroutineDispatcher(),
15+
Delay by (delegate as? Delay ?: defaultDelay)
16+
{
17+
private val mainDispatcher = delegate // the initial value passed to the constructor
18+
19+
override val immediate: MainCoroutineDispatcher
20+
get() = (delegate as? MainCoroutineDispatcher)?.immediate ?: this
21+
22+
override fun dispatch(context: CoroutineContext, block: Runnable) = delegate.dispatch(context, block)
23+
24+
override fun isDispatchNeeded(context: CoroutineContext): Boolean = delegate.isDispatchNeeded(context)
25+
26+
override fun dispatchYield(context: CoroutineContext, block: Runnable) = delegate.dispatchYield(context, block)
27+
28+
fun setDispatcher(dispatcher: CoroutineDispatcher) {
29+
delegate = dispatcher
30+
}
31+
32+
fun resetDispatcher() {
33+
delegate = mainDispatcher
34+
}
35+
}
36+
37+
@Suppress("INVISIBLE_MEMBER")
38+
private val defaultDelay
39+
inline get() = DefaultDelay
40+
41+
@Suppress("INVISIBLE_MEMBER")
42+
internal expect fun Dispatchers.getTestMainDispatcher(): TestMainDispatcher

kotlinx-coroutines-test/common/test/TestCoroutineDispatcherOrderTest.kt

-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ class TestCoroutineDispatcherOrderTest {
1515

1616
private fun expect(index: Int) {
1717
val wasIndex = actionIndex.incrementAndGet()
18-
// println("expect($index), wasIndex=$wasIndex")
1918
check(index == wasIndex) { "Expecting action index $index but it is actually $wasIndex" }
2019
}
2120

kotlinx-coroutines-test/jvm/test/TestDispatchersTest.kt renamed to kotlinx-coroutines-test/common/test/TestDispatchersTest.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ class TestDispatchersTest {
1414

1515
private fun expect(index: Int) {
1616
val wasIndex = actionIndex.incrementAndGet()
17-
println("expect($index), wasIndex=$wasIndex")
1817
check(index == wasIndex) { "Expecting action index $index but it is actually $wasIndex" }
1918
}
2019

@@ -69,4 +68,4 @@ class TestDispatchersTest {
6968
block.run()
7069
}
7170
}
72-
}
71+
}

kotlinx-coroutines-test/common/test/TestRunBlockingOrderTest.kt

-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ class TestRunBlockingOrderTest {
1515

1616
private fun expect(index: Int) {
1717
val wasIndex = actionIndex.incrementAndGet()
18-
// println("expect($index), wasIndex=$wasIndex")
1918
check(index == wasIndex) { "Expecting action index $index but it is actually $wasIndex" }
2019
}
2120

kotlinx-coroutines-test/js/src/TestDispatchers.kt

-16
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.coroutines.test.internal
6+
import kotlinx.coroutines.*
7+
8+
@Suppress("INVISIBLE_MEMBER")
9+
internal actual fun Dispatchers.getTestMainDispatcher(): TestMainDispatcher =
10+
when (val mainDispatcher = Main) {
11+
is TestMainDispatcher -> mainDispatcher
12+
else -> TestMainDispatcher(mainDispatcher).also { injectMain(it) }
13+
}

kotlinx-coroutines-test/jvm/src/TestDispatchers.kt

-24
This file was deleted.

kotlinx-coroutines-test/jvm/src/internal/TestMainDispatcher.kt

-76
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.coroutines.test.internal
6+
7+
import kotlinx.coroutines.*
8+
import kotlinx.coroutines.internal.*
9+
10+
internal class TestMainDispatcherFactory : MainDispatcherFactory {
11+
12+
override fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher {
13+
val otherFactories = allFactories.filter { it !== this }
14+
val secondBestFactory = otherFactories.maxByOrNull { it.loadPriority } ?: MissingMainCoroutineDispatcherFactory
15+
val dispatcher = secondBestFactory.tryCreateDispatcher(otherFactories)
16+
return TestMainDispatcher(dispatcher)
17+
}
18+
19+
/**
20+
* [Int.MAX_VALUE] -- test dispatcher always wins no matter what factories are present in the classpath.
21+
* By default, all actions are delegated to the second-priority dispatcher, so that it won't be the issue.
22+
*/
23+
override val loadPriority: Int
24+
get() = Int.MAX_VALUE
25+
}
26+
27+
internal actual fun Dispatchers.getTestMainDispatcher(): TestMainDispatcher {
28+
val mainDispatcher = Main
29+
require(mainDispatcher is TestMainDispatcher) { "TestMainDispatcher is not set as main dispatcher, have $mainDispatcher instead." }
30+
return mainDispatcher
31+
}

kotlinx-coroutines-test/native/src/TestDispatchers.kt

-17
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.coroutines.test.internal
6+
import kotlinx.coroutines.*
7+
8+
@Suppress("INVISIBLE_MEMBER")
9+
internal actual fun Dispatchers.getTestMainDispatcher(): TestMainDispatcher =
10+
when (val mainDispatcher = Main) {
11+
is TestMainDispatcher -> mainDispatcher
12+
else -> TestMainDispatcher(mainDispatcher).also { injectMain(it) }
13+
}

0 commit comments

Comments
 (0)