Skip to content

Commit 407e0b6

Browse files
mvicsokolovaqwwdfsad
authored andcommitted
Support of new K/N memory model
* Dispatchers.Default backed by pool of workers on Linux and by global_queue on iOS-like * Implementation of Dispatchers.Main that uses main queue on iOS and default dispatcher on other platforms (#2858) * Introduced newSingleThreadDispatcher and newFixedThreadPoolDispatcher * Use proper reentrant locking and CoW arrays on new memory model, make TestBase _almost_ race-free * More thread-safety in Native counterpart and one more test from native-mt * Source-set sharing for tests shared between JVM and K/N * Wrap Obj-C interop into autorelease pool to avoid memory leaks
1 parent d3ead6f commit 407e0b6

File tree

66 files changed

+1156
-551
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1156
-551
lines changed

build.gradle

+5
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ allprojects {
139139
*/
140140
google()
141141
mavenCentral()
142+
maven { url "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" }
142143
}
143144
}
144145

@@ -169,6 +170,10 @@ configure(subprojects.findAll { !sourceless.contains(it.name) }) {
169170
// Remove null assertions to get smaller bytecode on Android
170171
kotlinOptions.freeCompilerArgs += ["-Xno-param-assertions", "-Xno-receiver-assertions", "-Xno-call-assertions"]
171172
}
173+
174+
tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinNativeCompile) {
175+
kotlinOptions.freeCompilerArgs += ["-memory-model", "experimental"]
176+
}
172177
}
173178

174179
if (build_snapshot_train) {

gradle.properties

+1
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,4 @@ org.gradle.jvmargs=-Xmx4g
5757

5858
kotlin.mpp.enableCompatibilityMetadataVariant=true
5959
kotlin.mpp.stability.nowarn=true
60+
kotlin.native.cacheKind=none

kotlinx-coroutines-core/build.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,11 @@ kotlin {
9191
binaries {
9292
// Test for memory leaks using a special entry point that does not exit but returns from main
9393
binaries.getTest("DEBUG").freeCompilerArgs += ["-e", "kotlinx.coroutines.mainNoExit"]
94+
binaries.getTest("DEBUG").optimized = true
9495
// Configure a separate test where code runs in background
9596
test("background", [org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType.DEBUG]) {
9697
freeCompilerArgs += ["-e", "kotlinx.coroutines.mainBackground"]
98+
optimized = true
9799
}
98100
}
99101
testRuns {

kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public abstract class CoroutineDispatcher :
5959
* This method should generally be exception-safe. An exception thrown from this method
6060
* may leave the coroutines that use this dispatcher in the inconsistent and hard to debug state.
6161
*/
62+
@ExperimentalCoroutinesApi
6263
public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true
6364

6465
/**

kotlinx-coroutines-core/common/src/EventLoop.common.kt

+11-1
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay {
271271
// then process one event from queue
272272
val task = dequeue()
273273
if (task != null) {
274-
task.run()
274+
platformAutoreleasePool { task.run() }
275275
return 0
276276
}
277277
return nextTime
@@ -526,3 +526,13 @@ internal expect object DefaultExecutor {
526526
public fun enqueue(task: Runnable)
527527
}
528528

529+
/**
530+
* Used by Darwin targets to wrap a [Runnable.run] call in an Objective-C Autorelease Pool. It is a no-op on JVM, JS and
531+
* non-Darwin native targets.
532+
*
533+
* Coroutines on Darwin targets can call into the Objective-C world, where a callee may push a to-be-returned object to
534+
* the Autorelease Pool, so as to avoid a premature ARC release before it reaches the caller. This means the pool must
535+
* be eventually drained to avoid leaks. Since Kotlin Coroutines does not use [NSRunLoop], which provides automatic
536+
* pool management, it must manage the pool creation and pool drainage manually.
537+
*/
538+
internal expect inline fun platformAutoreleasePool(crossinline block: () -> Unit)

kotlinx-coroutines-core/common/test/EmptyContext.kt

+2-6
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@ package kotlinx.coroutines
77
import kotlinx.coroutines.intrinsics.*
88
import kotlin.coroutines.*
99

10-
suspend fun <T> withEmptyContext(block: suspend () -> T): T {
11-
val baseline = Result.failure<T>(IllegalStateException("Block was suspended"))
12-
var result: Result<T> = baseline
13-
block.startCoroutineUnintercepted(Continuation(EmptyCoroutineContext) { result = it })
14-
while (result == baseline) yield()
15-
return result.getOrThrow()
10+
suspend fun <T> withEmptyContext(block: suspend () -> T): T = suspendCoroutine { cont ->
11+
block.startCoroutineUnintercepted(Continuation(EmptyCoroutineContext) { cont.resumeWith(it) })
1612
}

kotlinx-coroutines-core/common/test/TestBase.common.kt

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import kotlin.test.*
1212

1313
public expect val isStressTest: Boolean
1414
public expect val stressTestMultiplier: Int
15+
public expect val stressTestMultiplierSqrt: Int
1516

1617
/**
1718
* The result of a multiplatform asynchronous test.
@@ -20,6 +21,10 @@ public expect val stressTestMultiplier: Int
2021
@Suppress("NO_ACTUAL_FOR_EXPECT")
2122
public expect class TestResult
2223

24+
public expect val isNative: Boolean
25+
// "Speedup" native stress tests
26+
public val stressTestNativeDivisor = if (isNative) 10 else 1
27+
2328
public expect open class TestBase constructor() {
2429
/*
2530
* In common tests we emulate parameterized tests

kotlinx-coroutines-core/common/test/flow/FlowInvariantsTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ class FlowInvariantsTest : TestBase() {
235235
}
236236
expectUnreached()
237237
} catch (e: IllegalStateException) {
238-
assertTrue(e.message!!.contains("Flow invariant is violated"))
238+
assertTrue(e.message!!.contains("Flow invariant is violated"), "But had: ${e.message}")
239239
finish(2)
240240
}
241241
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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
6+
7+
import kotlin.coroutines.*
8+
9+
/**
10+
* Runs a new coroutine and **blocks** the current thread until its completion.
11+
* This function should not be used from a coroutine. It is designed to bridge regular blocking code
12+
* to libraries that are written in suspending style, to be used in `main` functions and in tests.
13+
*/
14+
public expect fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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
6+
7+
@ExperimentalCoroutinesApi
8+
public expect fun newSingleThreadContext(name: String): MultithreadedDispatcher
9+
10+
@ExperimentalCoroutinesApi
11+
public expect fun newFixedThreadPoolContext(nThreads: Int, name: String): MultithreadedDispatcher
12+
13+
/**
14+
* A coroutine dispatcher that is confined to a single thread.
15+
*/
16+
@ExperimentalCoroutinesApi
17+
public expect abstract class MultithreadedDispatcher : CoroutineDispatcher {
18+
19+
/**
20+
* Closes this coroutine dispatcher and shuts down its thread.
21+
*/
22+
public abstract fun close()
23+
}

kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt renamed to kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package kotlinx.coroutines.internal
77

88
import kotlinx.atomicfu.*
99
import kotlinx.coroutines.*
10+
import kotlin.jvm.*
1011

1112
private typealias Node = LockFreeLinkedListNode
1213

@@ -616,7 +617,7 @@ public actual open class LockFreeLinkedListNode {
616617
assert { next === this._next.value }
617618
}
618619

619-
override fun toString(): String = "${this::class.java.simpleName}@${Integer.toHexString(System.identityHashCode(this))}"
620+
override fun toString(): String = "${this::classSimpleName}@${this.hexAddress}"
620621
}
621622

622623
private class Removed(@JvmField val ref: Node) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package kotlinx.coroutines
2+
3+
import kotlinx.coroutines.channels.*
4+
import kotlin.test.*
5+
6+
/*
7+
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
8+
*/
9+
10+
abstract class AbstractDispatcherConcurrencyTest : TestBase() {
11+
12+
public abstract val dispatcher: CoroutineDispatcher
13+
14+
@Test
15+
fun testLaunchAndJoin() {
16+
expect(1)
17+
var capturedMutableState = 0
18+
val job = GlobalScope.launch(dispatcher) {
19+
++capturedMutableState
20+
expect(2)
21+
}
22+
runBlocking { job.join() }
23+
assertEquals(1, capturedMutableState)
24+
finish(3)
25+
}
26+
27+
@Test
28+
fun testDispatcherIsActuallyMultithreaded() {
29+
val channel = Channel<Int>()
30+
GlobalScope.launch(dispatcher) {
31+
channel.send(42)
32+
}
33+
34+
var result = ChannelResult.failure<Int>()
35+
while (!result.isSuccess) {
36+
result = channel.tryReceive()
37+
// Block the thread, wait
38+
}
39+
// Delivery was successful, let's check it
40+
assertEquals(42, result.getOrThrow())
41+
}
42+
43+
@Test
44+
fun testDelayInDefaultDispatcher() {
45+
expect(1)
46+
val job = GlobalScope.launch(dispatcher) {
47+
expect(2)
48+
delay(100)
49+
expect(3)
50+
}
51+
runBlocking { job.join() }
52+
finish(4)
53+
}
54+
}

kotlinx-coroutines-core/jvm/test/AtomicCancellationTest.kt renamed to kotlinx-coroutines-core/concurrent/test/AtomicCancellationTest.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package kotlinx.coroutines
@@ -142,4 +142,4 @@ class AtomicCancellationTest : TestBase() {
142142
yield() // to jobToJoin & canceller
143143
expect(6)
144144
}
145-
}
145+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.coroutines
6+
7+
import kotlinx.coroutines.exceptions.*
8+
import kotlin.test.*
9+
10+
class ConcurrentExceptionsStressTest : TestBase() {
11+
private val nWorkers = 4
12+
private val nRepeat = 1000 * stressTestMultiplier
13+
14+
private val workers = Array(nWorkers) { index ->
15+
newSingleThreadContext("JobExceptionsStressTest-$index")
16+
}
17+
18+
@AfterTest
19+
fun tearDown() {
20+
workers.forEach {
21+
it.close()
22+
}
23+
}
24+
25+
@Test
26+
fun testStress() = runTest {
27+
repeat(nRepeat) {
28+
testOnce()
29+
}
30+
}
31+
32+
@Suppress("SuspendFunctionOnCoroutineScope") // workaround native inline fun stacktraces
33+
private suspend fun CoroutineScope.testOnce() {
34+
val deferred = async(NonCancellable) {
35+
repeat(nWorkers) { index ->
36+
// Always launch a coroutine even if parent job was already cancelled (atomic start)
37+
launch(workers[index], start = CoroutineStart.ATOMIC) {
38+
randomWait()
39+
throw StressException(index)
40+
}
41+
}
42+
}
43+
deferred.join()
44+
assertTrue(deferred.isCancelled)
45+
val completionException = deferred.getCompletionExceptionOrNull()
46+
val cause = completionException as? StressException
47+
?: unexpectedException("completion", completionException)
48+
val suppressed = cause.suppressed
49+
val indices = listOf(cause.index) + suppressed.mapIndexed { index, e ->
50+
(e as? StressException)?.index ?: unexpectedException("suppressed $index", e)
51+
}
52+
repeat(nWorkers) { index ->
53+
assertTrue(index in indices, "Exception $index is missing: $indices")
54+
}
55+
assertEquals(nWorkers, indices.size, "Duplicated exceptions in list: $indices")
56+
}
57+
58+
private fun unexpectedException(msg: String, e: Throwable?): Nothing {
59+
e?.printStackTrace()
60+
throw IllegalStateException("Unexpected $msg exception", e)
61+
}
62+
63+
private class StressException(val index: Int) : SuppressSupportingThrowable()
64+
}
65+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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.exceptions
6+
7+
import kotlinx.coroutines.*
8+
9+
internal expect open class SuppressSupportingThrowable() : Throwable
10+
expect val Throwable.suppressed: Array<Throwable>
11+
expect fun Throwable.printStackTrace()
12+
13+
expect fun randomWait()
14+
15+
expect fun currentThreadName(): String
16+
17+
inline fun MultithreadedDispatcher.use(block: (MultithreadedDispatcher) -> Unit) {
18+
try {
19+
block(this)
20+
} finally {
21+
close()
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
package kotlinx.coroutines
5+
6+
class DefaultDispatcherConcurrencyTest : AbstractDispatcherConcurrencyTest() {
7+
override val dispatcher: CoroutineDispatcher = Dispatchers.Default
8+
}

kotlinx-coroutines-core/jvm/test/JobStructuredJoinStressTest.kt renamed to kotlinx-coroutines-core/concurrent/test/JobStructuredJoinStressTest.kt

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
/*
2-
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

5-
package kotlinx.coroutines
6-
7-
import org.junit.*
5+
import kotlinx.coroutines.*
6+
import kotlin.test.*
87
import kotlin.coroutines.*
98

109
/**
@@ -61,4 +60,4 @@ class JobStructuredJoinStressTest : TestBase() {
6160
}
6261
finish(2 + nRepeats)
6362
}
64-
}
63+
}

0 commit comments

Comments
 (0)