Skip to content

Commit 83f17cc

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. 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
1 parent de0eea9 commit 83f17cc

File tree

107 files changed

+3042
-868
lines changed

Some content is hidden

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

107 files changed

+3042
-868
lines changed

README.md

+17-10
Original file line numberDiff line numberDiff line change
@@ -198,19 +198,24 @@ You can also use [`kotlinx-coroutines-core`](https://www.npmjs.com/package/kotli
198198
### Native
199199

200200
[Kotlin/Native](https://kotlinlang.org/docs/reference/native-overview.html) version of `kotlinx.coroutines` is published as
201-
[`kotlinx-coroutines-core-native`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-native/1.4.0/jar)
202-
(follow the link to get the dependency declaration snippet).
201+
[`kotlinx-coroutines-core`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.4.0/jar)
202+
(follow the link to get the dependency declaration snippet). **Kotlin/Native requires Gradle version 6.0 or later**
203+
to resolve that dependency properly into the corresponding platform-specific artifacts.
203204

204-
Only single-threaded code (JS-style) on Kotlin/Native is currently supported.
205-
Kotlin/Native supports only Gradle version 4.10 and you need to enable Gradle metadata in your
206-
`settings.gradle` file:
205+
Kotlin/Native does not generally provide binary compatibility between versions.
206+
You should use the same version of Kotlin/Native compiler as was used to build `kotlinx.coroutines`.
207207

208-
```groovy
209-
enableFeaturePreview('GRADLE_METADATA')
210-
```
208+
Kotlin/Native does not support free sharing of mutable objects between threads as on JVM, so several
209+
limitations apply to using coroutines on Kotlin/Native.
210+
See [Sharing and background threads on Kotlin/Native](kotlin-native-sharing.md) for details.
211+
212+
Some functions like [newSingleThreadContext] and [runBlocking] are available only for Kotlin/JVM and Kotlin/Native
213+
and are not available on Kotlin/JS. In order to access them from the code that is shared between JVM and Native
214+
you need to enable granular metadata (aka HMPP) in your `gradle.properties` file:
211215

212-
Since Kotlin/Native does not generally provide binary compatibility between versions,
213-
you should use the same version of Kotlin/Native compiler as was used to build `kotlinx.coroutines`.
216+
```properties
217+
kotlin.mpp.enableGranularSourceSetsMetadata=true
218+
```
214219

215220
## Building and Contributing
216221

@@ -239,6 +244,8 @@ See [Contributing Guidelines](CONTRIBUTING.md).
239244
[Promise.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/kotlin.js.-promise/await.html
240245
[promise]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/promise.html
241246
[Window.asCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/org.w3c.dom.-window/as-coroutine-dispatcher.html
247+
[newSingleThreadContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/new-single-thread-context.html
248+
[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
242249
<!--- INDEX kotlinx.coroutines.flow -->
243250
[Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
244251
[_flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow.html

gradle.properties

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

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

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)