Skip to content

Commit b4f2efa

Browse files
elizarovqwwdfsad
authored andcommitted
Kotlin/Native multithreading
* Provides newSingleThreadedContext. * Provides Dispatchers.Main on iOS, Dispatchers.Default everywhere. * Coroutine references (Job), all kinds of channels and StateFlow are shareable across workers. * Each individual coroutine is confined to a single worker. * Update Dispatchers docs to account for native-mt changes. * Multithreaded support in select expression. * Fix ObjC autorelease object leaks with native-mt dispatchers (#2477) Additional fixes: * Fixed broadcast builder with different thread * Fixed adding a child to a frozen parent job Fixes #462 Fixes #470 Fixes #765 Fixes #1645 Fixes #1751 Fixes #1828 Fixes #1831 Fixes #1764 Fixes #2064 Fixes #2025 Fixes #2226 Fixes #2138 Fixes #2263 Fixes #2322 Fixes #2283
1 parent 1efc9f1 commit b4f2efa

File tree

115 files changed

+3358
-1014
lines changed

Some content is hidden

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

115 files changed

+3358
-1014
lines changed

README.md

+18-9
Original file line numberDiff line numberDiff line change
@@ -201,17 +201,25 @@ Kotlin/JS version of `kotlinx.coroutines` is published as
201201

202202
#### Native
203203

204-
Kotlin/Native version of `kotlinx.coroutines` is published as
205-
[`kotlinx-coroutines-core-$platform`](https://mvnrepository.com/search?q=kotlinx-coroutines-core-) where `$platform` is
206-
the target Kotlin/Native platform. [List of currently supported targets](https://github.com/Kotlin/kotlinx.coroutines/blob/master/gradle/compile-native-multiplatform.gradle#L16).
204+
[Kotlin/Native](https://kotlinlang.org/docs/reference/native-overview.html) version of `kotlinx.coroutines` is published as
205+
[`kotlinx-coroutines-core`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.4.2/jar)
206+
(follow the link to get the dependency declaration snippet). **Kotlin/Native requires Gradle version 6.0 or later**
207+
to resolve that dependency properly into the corresponding platform-specific artifacts.
207208

209+
Kotlin/Native does not generally provide binary compatibility between versions.
210+
You should use the same version of Kotlin/Native compiler as was used to build `kotlinx.coroutines`.
208211

209-
Only single-threaded code (JS-style) on Kotlin/Native is supported in stable versions.
210-
Additionally, special `-native-mt` version is released on a regular basis, for the state of multi-threaded coroutines support
211-
please follow the [corresponding issue](https://github.com/Kotlin/kotlinx.coroutines/issues/462) for the additional details.
212+
Kotlin/Native does not support free sharing of mutable objects between threads as on JVM, so several
213+
limitations apply to using coroutines on Kotlin/Native.
214+
See [Sharing and background threads on Kotlin/Native](kotlin-native-sharing.md) for details.
212215

213-
Since Kotlin/Native does not generally provide binary compatibility between versions,
214-
you should use the same version of Kotlin/Native compiler as was used to build `kotlinx.coroutines`.
216+
Some functions like [newSingleThreadContext] and [runBlocking] are available only for Kotlin/JVM and Kotlin/Native
217+
and are not available on Kotlin/JS. In order to access them from the code that is shared between JVM and Native
218+
you need to enable granular metadata (aka HMPP) in your `gradle.properties` file:
219+
220+
```properties
221+
kotlin.mpp.enableGranularSourceSetsMetadata=true
222+
```
215223

216224
## Building and Contributing
217225

@@ -241,7 +249,8 @@ See [Contributing Guidelines](CONTRIBUTING.md).
241249
[Promise.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/kotlin.js.-promise/await.html
242250
[promise]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/promise.html
243251
[Window.asCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/org.w3c.dom.-window/as-coroutine-dispatcher.html
244-
252+
[newSingleThreadContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/new-single-thread-context.html
253+
[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
245254
<!--- INDEX kotlinx.coroutines.flow -->
246255

247256
[Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html

gradle.properties

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#
44

55
# Kotlin
6-
version=1.4.3-SNAPSHOT
6+
version=1.4.3-native-mt-SNAPSHOT
77
group=org.jetbrains.kotlinx
88
kotlin_version=1.4.30
99

@@ -55,7 +55,7 @@ jekyll_version=4.0
5555
org.gradle.jvmargs=-Xmx2g
5656

5757
# Workaround for Bintray treating .sha512 files as artifacts
58-
# https://github.com/gradle/gradle/issues/1.4.3
58+
# https://github.com/gradle/gradle/issues/11412
5959
systemProp.org.gradle.internal.publish.checksums.insecure=true
6060

6161
# todo:KLUDGE: This is commented out, and the property is set conditionally in build.gradle, because IDEA doesn't work with it.

kotlin-native-sharing.md

+184
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# Sharing and background threads on Kotlin/Native
2+
3+
## Preview disclaimer
4+
5+
This is a preview release of sharing and backgrounds threads for coroutines on Kotlin/Native.
6+
Details of this implementation will change in the future. See also [Known Problems](#known-problems)
7+
at the end of this document.
8+
9+
## Introduction
10+
11+
Kotlin/Native provides an automated memory management that works with mutable data objects separately
12+
and independently in each thread that uses Kotlin/Native runtime. Sharing data between threads is limited:
13+
14+
* Objects to be shared between threads can be [frozen](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/freeze.html).
15+
This makes the whole object graph deeply immutable and allows to share it between threads.
16+
* Mutable objects can be wrapped into [DetachedObjectGraph](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/-detached-object-graph/index.html)
17+
on one thread and later reattached onto the different thread.
18+
19+
This introduces several differences between Kotlin/JVM and Kotlin/Native in terms of coroutines that must
20+
be accounted for when writing cross-platform applications.
21+
22+
## Threads and dispatchers
23+
24+
An active coroutine has a mutable state. It cannot migrate from thread to thread. A coroutine in Kotlin/Native
25+
is always bound to a specific thread. Coroutines that are detached from a thread are currently not supported.
26+
27+
`kotlinx.coroutines` provides ability to create single-threaded dispatchers for background work
28+
via [newSingleThreadContext] function that is available for both Kotlin/JVM and Kotlin/Native. It is not
29+
recommended shutting down such a dispatcher on Kotlin/Native via [SingleThreadDispatcher.close] function
30+
while the application still working unless you are absolutely sure all coroutines running in this
31+
dispatcher have completed. Unlike Kotlin/JVM, there is no backup default thread that might
32+
execute cleanup code for coroutines that might have been still working in this dispatcher.
33+
34+
For interoperability with code that is using Kotlin/Native
35+
[Worker](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/-worker/index.html)
36+
API you can get a reference to single-threaded dispacher's worker using its [SingleThreadDispatcher.worker] property.
37+
38+
A [Default][Dispatchers.Default] dispatcher on Kotlin/Native contains a single background thread.
39+
This is the dispatcher that is used by default in [GlobalScope].
40+
41+
> This limitation may be lifted in the future with the default dispatcher becoming multi-threaded and/or
42+
> its coroutines becoming isolated from each other, so please do not assume that different coroutines running
43+
> in the default dispatcher can share mutable data between themselves.
44+
45+
A [Main][Dispatchers.Main] dispatcher is
46+
properly defined for all Darwin (Apple) targets, refers to the main thread, and integrates
47+
with Core Foundation main event loop.
48+
On Linux and Windows there is no platform-defined main thread, so [Main][Dispatchers.Main] simply refers
49+
to the current thread that must have been either created with `newSingleThreadContext` or be running
50+
inside [runBlocking] function.
51+
52+
The main thread of application has two options on using coroutines.
53+
A backend application's main thread shall use [runBlocking].
54+
A UI application running on one Apple's Darwin OSes shall run
55+
its main queue event loop using `NSRunLoopRun`, `UIApplicationMain`, or ` NSApplicationMain`.
56+
For example, that is how you can have main dispatcher in your own `main` function:
57+
58+
```kotlin
59+
fun main() {
60+
val mainScope = MainScope()
61+
mainScope.launch { /* coroutine in the main thread */ }
62+
CFRunLoopRun() // run event loop
63+
}
64+
```
65+
66+
## Switching threads
67+
68+
You switch from one dispatcher to another using a regular [withContext] function. For example, a code running
69+
on the main thread might do:
70+
71+
```kotlin
72+
// in the main thead
73+
val result = withContext(Dispatcher.Default) {
74+
// now executing in background thread
75+
}
76+
// now back to the main thread
77+
result // use result here
78+
```
79+
80+
If you capture a reference to any object that is defined in the main thread outside of `withContext` into the
81+
block inside `withContext` then it gets automatically frozen for transfer from the main thread to the
82+
background thread. Freezing is recursive, so you might accidentally freeze unrelated objects that are part of
83+
main thread's mutable state and get
84+
[InvalidMutabilityException](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/-invalid-mutability-exception/index.html)
85+
later in unrelated parts of your code.
86+
The easiest way to trouble-shoot it is to mark the objects that should not have been frozen using
87+
[ensureNeverFrozen](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/ensure-never-frozen.html)
88+
function so that you get exception in the very place they were frozen that would pinpoint the corresponding
89+
`withContext` call in your code.
90+
91+
The `result` of `withContext` call can be used after `withContext` call. It gets automatically frozen
92+
for transfer from background to the main thread, too.
93+
94+
A disciplined use of threads in Kotlin/Native is to transfer only immutable data between the threads.
95+
Such code works equally well both on Kotlin/JVM and Kotlin/Native.
96+
97+
> Note: freezing only happens when `withContext` changes from one thread to another. If you call
98+
> `withContext` and execution stays in the same thread, then there is not freezing and mutable data
99+
> can be captured and operated on as usual.
100+
101+
The same rule on freezing applies to coroutines launched with any builder like [launch], [async], [produce], etc.
102+
103+
## Communication objects
104+
105+
All core communication and synchronization objects in `kotlin.coroutines` such as
106+
[Job], [Deferred], [Channel], [BroadcastChannel], [Mutex], and [Semaphore] are _shareable_.
107+
It means that they can be frozen for sharing with another thread and still continue to operate normally.
108+
Any object that is transferred via a frozen (shared) [Deferred] or any [Channel] is also automatically frozen.
109+
110+
Similar rules apply to [Flow]. When an instance of a [Flow] itself is shared (frozen), then all the references that
111+
are captured in to the lambdas in this flow operators are frozen. Regardless of whether the flow instance itself
112+
was frozen, by default, the whole flow operates in a single thread, so mutable data can freely travel down the
113+
flow from emitter to collector. However, when [flowOn] operator is used to change the thread, then
114+
objects crossing the thread boundary get frozen.
115+
116+
Note, that if you protect any piece of mutable data with a [Mutex] or a [Semaphore] then it does not
117+
automatically become shareable. In order to share mutable data you have to either
118+
wrap it into [DetachedObjectGraph](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/-detached-object-graph/index.html)
119+
or use atomic classes ([AtomicInt](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/-atomic-int/index.html), etc).
120+
121+
## Cyclic garbage
122+
123+
Code working in a single thread on Kotlin/Native enjoys fully automatic memory management. Any object graph that
124+
is not referenced anymore is automatically reclaimed even if it contains cyclic chains of references. This does
125+
not extend to shared objects, though. Frozen immutable objects can be freely shared, even if then can contain
126+
reference cycles, but shareable [communication objects](#communication-objects) leak if a reference cycle
127+
to them appears. The easiest way to demonstrate it is to return a reference to a [async] coroutine as its result,
128+
so that the resulting [Deferred] contains a reference to itself:
129+
130+
```kotlin
131+
// from the main thread call coroutine in a background thread or otherwise share it
132+
val result = GlobalScope.async {
133+
coroutineContext // return its coroutine context that contains a self-reference
134+
}
135+
// now result will not be reclaimed -- memory leak
136+
```
137+
138+
A disciplined use of communication objects to transfer immutable data between coroutines does not
139+
result in any memory reclamation problems.
140+
141+
## Shared channels are resources
142+
143+
All kinds of [Channel] and [BroadcastChannel] implementations become _resources_ on Kotlin/Native when shared.
144+
They must be closed and fully consumed in order for their memory to be reclaimed. When they are not shared, they
145+
can be dropped in any state and will be reclaimed by memory manager, but a shared channel generally will not be reclaimed
146+
unless closed and consumed.
147+
148+
This does not affect [Flow], because it is a cold abstraction. Even though [Flow] internally uses channels to transfer
149+
data between threads, it always properly closes these channels when completing collection of data.
150+
151+
## Known problems
152+
153+
The current implementation is tested and works for all kinds of single-threaded cases and simple scenarios that
154+
transfer data between two thread like shown in [Switching Threads](#switching-threads) section. However, it is known
155+
to leak memory in scenarios involving concurrency under load, for example when multiple children coroutines running
156+
in different threads are simultaneously cancelled.
157+
158+
<!--- MODULE kotlinx-coroutines-core -->
159+
<!--- INDEX kotlinx.coroutines -->
160+
[newSingleThreadContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/new-single-thread-context.html
161+
[SingleThreadDispatcher.close]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-single-thread-dispatcher/-single-thread-dispatcher/close.html
162+
[SingleThreadDispatcher.worker]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-single-thread-dispatcher/-single-thread-dispatcher/worker.html
163+
[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
164+
[GlobalScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html
165+
[Dispatchers.Main]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html
166+
[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
167+
[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
168+
[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
169+
[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
170+
[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
171+
[Deferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
172+
<!--- INDEX kotlinx.coroutines.flow -->
173+
[Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
174+
[flowOn]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow-on.html
175+
<!--- INDEX kotlinx.coroutines.channels -->
176+
[produce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html
177+
[Channel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/index.html
178+
[BroadcastChannel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-broadcast-channel/index.html
179+
<!--- INDEX kotlinx.coroutines.selects -->
180+
<!--- INDEX kotlinx.coroutines.sync -->
181+
[Mutex]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/index.html
182+
[Semaphore]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-semaphore/index.html
183+
<!--- END -->
184+

kotlinx-coroutines-core/api/kotlinx-coroutines-core.api

+2-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ public abstract class kotlinx/coroutines/AbstractCoroutine : kotlinx/coroutines/
1313
protected fun onStart ()V
1414
public final fun resumeWith (Ljava/lang/Object;)V
1515
public final fun start (Lkotlinx/coroutines/CoroutineStart;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V
16-
public final fun start (Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function1;)V
1716
}
1817

1918
public final class kotlinx/coroutines/AwaitKt {
@@ -64,6 +63,7 @@ public class kotlinx/coroutines/CancellableContinuationImpl : kotlin/coroutines/
6463
public fun getCallerFrame ()Lkotlin/coroutines/jvm/internal/CoroutineStackFrame;
6564
public fun getContext ()Lkotlin/coroutines/CoroutineContext;
6665
public fun getContinuationCancellationCause (Lkotlinx/coroutines/Job;)Ljava/lang/Throwable;
66+
public synthetic fun getDelegate$kotlinx_coroutines_core ()Lkotlin/coroutines/Continuation;
6767
public final fun getResult ()Ljava/lang/Object;
6868
public fun getStackTraceElement ()Ljava/lang/StackTraceElement;
6969
public fun initCancellability ()V
@@ -157,7 +157,7 @@ public abstract class kotlinx/coroutines/CoroutineDispatcher : kotlin/coroutines
157157
public abstract fun dispatch (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Runnable;)V
158158
public fun dispatchYield (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Runnable;)V
159159
public fun get (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
160-
public final fun interceptContinuation (Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
160+
public fun interceptContinuation (Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
161161
public fun isDispatchNeeded (Lkotlin/coroutines/CoroutineContext;)Z
162162
public fun minusKey (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
163163
public final fun plus (Lkotlinx/coroutines/CoroutineDispatcher;)Lkotlinx/coroutines/CoroutineDispatcher;
@@ -226,8 +226,6 @@ public final class kotlinx/coroutines/CoroutineStart : java/lang/Enum {
226226
public static final field DEFAULT Lkotlinx/coroutines/CoroutineStart;
227227
public static final field LAZY Lkotlinx/coroutines/CoroutineStart;
228228
public static final field UNDISPATCHED Lkotlinx/coroutines/CoroutineStart;
229-
public final fun invoke (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)V
230-
public final fun invoke (Lkotlin/jvm/functions/Function2;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)V
231229
public final fun isLazy ()Z
232230
public static fun valueOf (Ljava/lang/String;)Lkotlinx/coroutines/CoroutineStart;
233231
public static fun values ()[Lkotlinx/coroutines/CoroutineStart;

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

+1-19
Original file line numberDiff line numberDiff line change
@@ -124,23 +124,6 @@ public abstract class AbstractCoroutine<in T>(
124124
return "\"$coroutineName\":${super.nameString()}"
125125
}
126126

127-
/**
128-
* Starts this coroutine with the given code [block] and [start] strategy.
129-
* This function shall be invoked at most once on this coroutine.
130-
*
131-
* First, this function initializes parent job from the `parentContext` of this coroutine that was passed to it
132-
* during construction. Second, it starts the coroutine based on [start] parameter:
133-
*
134-
* * [DEFAULT] uses [startCoroutineCancellable].
135-
* * [ATOMIC] uses [startCoroutine].
136-
* * [UNDISPATCHED] uses [startCoroutineUndispatched].
137-
* * [LAZY] does nothing.
138-
*/
139-
public fun start(start: CoroutineStart, block: suspend () -> T) {
140-
initParentJob()
141-
start(block, this)
142-
}
143-
144127
/**
145128
* Starts this coroutine with the given code [block] and [start] strategy.
146129
* This function shall be invoked at most once on this coroutine.
@@ -154,7 +137,6 @@ public abstract class AbstractCoroutine<in T>(
154137
* * [LAZY] does nothing.
155138
*/
156139
public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
157-
initParentJob()
158-
start(block, receiver, this)
140+
startAbstractCoroutine(start, receiver, this, block)
159141
}
160142
}

0 commit comments

Comments
 (0)