Skip to content

Commit 8df9c27

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 Fixes #2688
1 parent b231887 commit 8df9c27

File tree

120 files changed

+3440
-1021
lines changed

Some content is hidden

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

120 files changed

+3440
-1021
lines changed

README.md

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

198198
#### Native
199199

200-
Kotlin/Native version of `kotlinx.coroutines` is published as
201-
[`kotlinx-coroutines-core-$platform`](https://mvnrepository.com/search?q=kotlinx-coroutines-core-) where `$platform` is
202-
the target Kotlin/Native platform. [List of currently supported targets](https://github.com/Kotlin/kotlinx.coroutines/blob/master/gradle/compile-native-multiplatform.gradle#L16).
200+
[Kotlin/Native](https://kotlinlang.org/docs/reference/native-overview.html) version of `kotlinx.coroutines` is published as
201+
[`kotlinx-coroutines-core`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.4.2/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

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`.
204207

205-
Only single-threaded code (JS-style) on Kotlin/Native is supported in stable versions.
206-
Additionally, a special `-native-mt` version is released on a regular basis, for the state of multi-threaded coroutines support
207-
please follow the [corresponding issue](https://github.com/Kotlin/kotlinx.coroutines/issues/462) for the additional details.
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.
208211

209-
Since Kotlin/Native does not generally provide binary compatibility between versions,
210-
you should use the same version of the Kotlin/Native compiler as was used to build `kotlinx.coroutines`.
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:
215+
216+
```properties
217+
kotlin.mpp.enableGranularSourceSetsMetadata=true
218+
```
211219

212220
## Building and Contributing
213221

@@ -237,7 +245,8 @@ See [Contributing Guidelines](CONTRIBUTING.md).
237245
[Promise.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/await.html
238246
[promise]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/promise.html
239247
[Window.asCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/as-coroutine-dispatcher.html
240-
248+
[newSingleThreadContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/new-single-thread-context.html
249+
[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
241250
<!--- INDEX kotlinx.coroutines.flow -->
242251

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

gradle.properties

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

55
# Kotlin
6-
version=1.5.1-SNAPSHOT
6+
version=1.5.1-native-mt-SNAPSHOT
77
group=org.jetbrains.kotlinx
88
kotlin_version=1.5.20
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

+3-5
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public class kotlinx/coroutines/CancellableContinuationImpl : kotlin/coroutines/
6060
public fun getCallerFrame ()Lkotlin/coroutines/jvm/internal/CoroutineStackFrame;
6161
public fun getContext ()Lkotlin/coroutines/CoroutineContext;
6262
public fun getContinuationCancellationCause (Lkotlinx/coroutines/Job;)Ljava/lang/Throwable;
63+
public synthetic fun getDelegate$kotlinx_coroutines_core ()Lkotlin/coroutines/Continuation;
6364
public final fun getResult ()Ljava/lang/Object;
6465
public fun getStackTraceElement ()Ljava/lang/StackTraceElement;
6566
public fun initCancellability ()V
@@ -154,11 +155,11 @@ public abstract class kotlinx/coroutines/CoroutineDispatcher : kotlin/coroutines
154155
public abstract fun dispatch (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Runnable;)V
155156
public fun dispatchYield (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Runnable;)V
156157
public fun get (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
157-
public final fun interceptContinuation (Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
158+
public fun interceptContinuation (Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
158159
public fun isDispatchNeeded (Lkotlin/coroutines/CoroutineContext;)Z
159160
public fun minusKey (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
160161
public final fun plus (Lkotlinx/coroutines/CoroutineDispatcher;)Lkotlinx/coroutines/CoroutineDispatcher;
161-
public final fun releaseInterceptedContinuation (Lkotlin/coroutines/Continuation;)V
162+
public fun releaseInterceptedContinuation (Lkotlin/coroutines/Continuation;)V
162163
public fun toString ()Ljava/lang/String;
163164
}
164165

@@ -223,8 +224,6 @@ public final class kotlinx/coroutines/CoroutineStart : java/lang/Enum {
223224
public static final field DEFAULT Lkotlinx/coroutines/CoroutineStart;
224225
public static final field LAZY Lkotlinx/coroutines/CoroutineStart;
225226
public static final field UNDISPATCHED Lkotlinx/coroutines/CoroutineStart;
226-
public final fun invoke (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)V
227-
public final fun invoke (Lkotlin/jvm/functions/Function2;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)V
228227
public final fun isLazy ()Z
229228
public static fun valueOf (Ljava/lang/String;)Lkotlinx/coroutines/CoroutineStart;
230229
public static fun values ()[Lkotlinx/coroutines/CoroutineStart;
@@ -420,7 +419,6 @@ public class kotlinx/coroutines/JobSupport : kotlinx/coroutines/ChildJob, kotlin
420419
public final fun getKey ()Lkotlin/coroutines/CoroutineContext$Key;
421420
public final fun getOnJoin ()Lkotlinx/coroutines/selects/SelectClause0;
422421
protected fun handleJobException (Ljava/lang/Throwable;)Z
423-
protected final fun initParentJob (Lkotlinx/coroutines/Job;)V
424422
public final fun invokeOnCompletion (Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle;
425423
public final fun invokeOnCompletion (ZZLkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle;
426424
public fun isActive ()Z

0 commit comments

Comments
 (0)