diff --git a/docs/kr.tree b/docs/kr.tree index 38c0d8f5811..34f92543484 100644 --- a/docs/kr.tree +++ b/docs/kr.tree @@ -234,12 +234,6 @@ - - - - - - diff --git a/docs/topics/compiler-reference.md b/docs/topics/compiler-reference.md index f01bf7eef55..fe9d929251a 100644 --- a/docs/topics/compiler-reference.md +++ b/docs/topics/compiler-reference.md @@ -291,10 +291,6 @@ Enable emitting debug information. Produce an application for running unit tests from the project. -### -generate-worker-test-runner (-trw) - -Produce an application for running unit tests in a [worker thread](native-immutability.md#concurrency-in-kotlin-native). - ### -generate-no-exit-test-runner (-trn) Produce an application for running unit tests without an explicit process exit. diff --git a/docs/topics/kotlin-hands-on.md b/docs/topics/kotlin-hands-on.md index 4b1308eeae7..724a5ce7b9c 100644 --- a/docs/topics/kotlin-hands-on.md +++ b/docs/topics/kotlin-hands-on.md @@ -57,12 +57,6 @@ Create a simple HTTP client that can run natively on multiple platforms using Ko [**Start**](native-app-with-c-and-libcurl.md) -## Introduction to Kotlin/Native concurrency - -Learn about the concurrency model in Kotlin/Native, and how to work with state in a multithreaded environment. - -[**Start**](multiplatform-mobile-concurrency-overview.md) - ## Kotlin Multiplatform: networking and data storage Learn how to create a mobile application for Android and iOS using Kotlin Multiplatform with Ktor and SQLDelight. diff --git a/docs/topics/multiplatform-mobile/multiplatform-mobile-concurrency-and-coroutines.md b/docs/topics/multiplatform-mobile/multiplatform-mobile-concurrency-and-coroutines.md index e02d7f3c7d4..e69de29bb2d 100644 --- a/docs/topics/multiplatform-mobile/multiplatform-mobile-concurrency-and-coroutines.md +++ b/docs/topics/multiplatform-mobile/multiplatform-mobile-concurrency-and-coroutines.md @@ -1,284 +0,0 @@ -[//]: # (title: Concurrency and coroutines) - -> This page describes the features of the legacy memory manager. Check out [Kotlin/Native memory management](native-memory-manager.md) -> to learn about the new memory manager, which has been enabled by default since Kotlin 1.7.20. -> -> For more information on the concurrent programming in Kotlin, see the [Coroutines guide](coroutines-guide.md). -> -{type="warning"} - -When working with mobile platforms, you may need to write multithreaded code that runs in parallel. For this, -you can use the [standard](#coroutines) `kotlinx.coroutines` library or its [multithreaded version](#multithreaded-coroutines) -and [alternative solutions](#alternatives-to-kotlinx-coroutines). - -Review the pros and cons of each solution and choose the one that works best for your situation. - -Learn more about [concurrency, the current approach, and future improvements](multiplatform-mobile-concurrency-overview.md). - -## Coroutines - -Coroutines are light-weight threads that allow you to write asynchronous non-blocking code. Kotlin provides the -[`kotlinx.coroutines`](https://github.com/Kotlin/kotlinx.coroutines) library with a number of high-level coroutine-enabled primitives. - -The current version of `kotlinx.coroutines`, which can be used for iOS, supports usage only in a single thread. -You cannot send work to other threads by changing a [dispatcher](#dispatcher-for-changing-threads). - -For Kotlin %kotlinVersion%, the recommended coroutines version is `%coroutinesVersion%`. - -You can suspend execution and do work on other threads while using a different mechanism for scheduling -and managing that work. However, this version of `kotlinx.coroutines` cannot change threads on its own. - -There is also [another version of `kotlinx.coroutines`](#multithreaded-coroutines) that provides support for multiple threads. - -Get acquainted with the main concepts for using coroutines: - -* [Asynchronous vs. parallel processing](#asynchronous-vs-parallel-processing) -* [Dispatcher for changing threads](#dispatcher-for-changing-threads) -* [Frozen captured data](#frozen-captured-data) -* [Frozen returned data](#frozen-returned-data) - -### Asynchronous vs. parallel processing - -Asynchronous and parallel processing are different. - -Within a coroutine, the processing sequence may be suspended and resumed later. This allows for asynchronous, -non-blocking code, without using callbacks or promises. That is asynchronous processing, but everything related to that -coroutine can happen in a single thread. - -The following code makes a network call using [Ktor](https://ktor.io/). In the main thread, the call is initiated -and suspended, while another underlying process performs the actual networking. When completed, the code resumes -in the main thread. - -```kotlin -val client = HttpClient() -//Running in the main thread, start a `get` call -client.get("https://example.com/some/rest/call") -//The get call will suspend and let other work happen in the main thread, and resume when the get call completes -``` - -That is different from parallel code that needs to be run in another thread. Depending on your purpose -and the libraries you use, you may never need to use multiple threads. - -### Dispatcher for changing threads - -Coroutines are executed by a dispatcher that defines which thread the coroutine will be executed on. -There are a number of ways in which you can specify the dispatcher, or change the one for the coroutine. For example: - -```kotlin -suspend fun differentThread() = withContext(Dispatchers.Default){ - println("Different thread") -} -``` - -`withContext` takes both a dispatcher as an argument and a code block that will be executed by the thread defined by -the dispatcher. Learn more about [coroutine context and dispatchers](coroutine-context-and-dispatchers.md). - -To perform work on a different thread, specify a different dispatcher and a code block to execute. In general, -switching dispatchers and threads works similar to the JVM, but there are differences related to freezing -captured and returned data. - -### Frozen captured data - -To run code on a different thread, you pass a `functionBlock`, which gets frozen and then runs in another thread. - -```kotlin -fun runOnDifferentThread(functionBlock: () -> R) -``` - -You will call that function as follows: - -```kotlin -runOnDifferentThread { - //Code run in another thread -} -``` - -As described in the [concurrency overview](multiplatform-mobile-concurrency-overview.md), a state shared between threads in -Kotlin/Native must be frozen. A function argument is a state itself, which will be frozen along with anything it captures. - -Coroutine functions that cross threads use the same pattern. To allow function blocks to be executed on another thread, -they are frozen. - -In the following example, the data class instance `dc` will be captured by the function block and will be frozen when crossing -threads. The `println` statement will print `true`. - -```kotlin -val dc = DataClass("Hello") -withContext(Dispatchers.Default) { - println("${dc.isFrozen}") -} -``` - -When running parallel code, be careful with the captured state. -Sometimes it's obvious when the state will be captured, but not always. For example: - -```kotlin -class SomeModel(val id:IdRec){ - suspend fun saveData() = withContext(Dispatchers.Default){ - saveToDb(id) - } -} -``` - -The code inside `saveData` runs on another thread. That will freeze `id`, but because `id` is a property of the parent class, -it will also freeze the parent class. - -### Frozen returned data - -Data returned from a different thread is also frozen. Even though it's recommended that you return immutable data, you can -return a mutable state in a way that doesn't allow a returned value to be changed. - -```kotlin -val dc = withContext(Dispatchers.Default) { - DataClass("Hello Again") -} - -println("${dc.isFrozen}") -``` - -It may be a problem if a mutable state is isolated in a single thread and coroutine threading operations are used for -communication. If you attempt to return data that retains a reference to the mutable state, it will also freeze the data by -association. - -Learn more about the [thread-isolated state](multiplatform-mobile-concurrent-mutability.md#thread-isolated-state). - -## Multithreaded coroutines - -A [special branch](https://github.com/Kotlin/kotlinx.coroutines/tree/native-mt) of the `kotlinx.coroutines` library -provides support for using multiple threads. It is a separate branch for the reasons listed in the [future concurrency model blog post](https://blog.jetbrains.com/kotlin/2020/07/kotlin-native-memory-management-roadmap/). - -However, you can still use the multithreaded version of `kotlinx.coroutines` in production, taking its specifics into account. - -The current version for Kotlin %kotlinVersion% is `%coroutinesVersion%-native-mt`. - -To use the multithreaded version, add a dependency for the `commonMain` source set in `build.gradle(.kts)`: - - - - -```kotlin -val commonMain by getting { - dependencies { - implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core:%coroutinesVersion%-native-mt") - } -} -``` - - - - -```groovy -commonMain { - dependencies { - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:%coroutinesVersion%-native-mt' - } -} -``` - - - - -When using other libraries that also depend on `kotlinx.coroutines`, such as Ktor, make sure to specify the multithreaded version -of `kotlinx-coroutines`. You can do this with `strictly`: - - - - -```kotlin -implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core:%coroutinesVersion%-native-mt") { - version { - strictly("%coroutinesVersion%-native-mt") - } -} -``` - - - - -```groovy -implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:%coroutinesVersion%-native-mt' { - version { - strictly '%coroutinesVersion%-native-mt' - } -} -``` - - - - -Because the main version of `kotlinx.coroutines` is a single-threaded one, libraries will almost certainly rely on this version. -If you see `InvalidMutabilityException` related to a coroutine operation, it's very likely that you are using the wrong version. - -> Using multithreaded coroutines may result in _memory leaks_. This can be a problem for complex coroutine scenarios under load. -> We are working on a solution for this. -> -{type="note"} - -See a [complete example of using multithreaded coroutines in a Kotlin Multiplatform application](https://github.com/touchlab/KaMPKit). - -## Alternatives to `kotlinx-coroutines` - -There are a few alternative ways to run parallel code. - -### CoroutineWorker - -[`CoroutinesWorker`](https://github.com/Autodesk/coroutineworker) is a library published by AutoDesk that implements some -features of coroutines across threads using the single-threaded version of `kotlinx.coroutines`. - -For simple suspend functions this is a pretty good option, but it does not support Flow and other structures. - -### Reaktive - -[Reaktive](https://github.com/badoo/Reaktive) is an Rx-like library that implements Reactive extensions for Kotlin Multiplatform. -It has some coroutine extensions but is primarily designed around RX and threads. - -### Custom processor - -For simpler background tasks, you can create your own processor with wrappers around platform specifics. -See a [simple example](https://github.com/touchlab/KMMWorker). - -### Platform concurrency - -In production, you can also rely on the platform to handle concurrency. -This could be helpful if the shared Kotlin code will be used for business logic or data operations rather -than architecture. - -To share a state in iOS across threads, that state needs to be [frozen](multiplatform-mobile-concurrency-overview.md#immutable-and-frozen-state). The concurrency libraries mentioned here -will freeze your data automatically. You will rarely need to do so explicitly, if ever. - -If you return data to the iOS platform that should be shared across threads, ensure -that data is frozen before leaving the iOS boundary. - -Kotlin has the concept of frozen only for Kotlin/Native platforms including iOS. To make `freeze` available in common code, -you can create expect and actual implementations for `freeze`, or use [`stately-common`](https://github.com/touchlab/Stately#stately-common), which provides this functionality. -In Kotlin/Native, `freeze` will freeze your state, while on the JVM it'll do nothing. - -To use `stately-common`, add a dependency for the `commonMain` source set in `build.gradle(.kts)`: - - - - -```kotlin -val commonMain by getting { - dependencies { - implementation ("co.touchlab:stately-common:1.0.x") - } -} -``` - - - - -```groovy -commonMain { - dependencies { - implementation 'co.touchlab:stately-common:1.0.x' - } -} -``` - - - - -_This material was prepared by [Touchlab](https://touchlab.co/) for publication by JetBrains._ - diff --git a/docs/topics/multiplatform-mobile/multiplatform-mobile-concurrency-overview.md b/docs/topics/multiplatform-mobile/multiplatform-mobile-concurrency-overview.md deleted file mode 100644 index 458f0d0df27..00000000000 --- a/docs/topics/multiplatform-mobile/multiplatform-mobile-concurrency-overview.md +++ /dev/null @@ -1,197 +0,0 @@ -[//]: # (title: Concurrency overview) - -> This page describes the features of the legacy memory manager. Check out [Kotlin/Native memory management](native-memory-manager.md) -> to learn about the new memory manager, which has been enabled by default since Kotlin 1.7.20. -> -> For more information on the concurrent programming in Kotlin, see the [Coroutines guide](coroutines-guide.md). -> -{type="warning"} - -When you extend your development experience from Android to Kotlin Multiplatform for mobile, you will encounter a different state -and concurrency model for iOS. This is a Kotlin/Native model that compiles Kotlin code to native binaries that can run without a virtual machine, for example on iOS. - -Having mutable memory available to multiple threads at the same time, if unrestricted, is known to be risky and prone to error. -Languages like Java, C++, and Swift/Objective-C let multiple threads access the same state in an unrestricted way. Concurrency issues are unlike other programming issues in that they are -often very difficult to reproduce. You may not see them locally while developing, and they may happen sporadically. -And sometimes you can only see them in production under load. - -In short, just because your tests pass, you can't necessarily be sure that your code is OK. - -Not all languages are designed this way. JavaScript simply does not -allow you to access the same state concurrently. At the other end of the spectrum is Rust, with its -language-level management of concurrency and states, which makes it very popular. - -## Rules for state sharing - -Kotlin/Native introduces rules for sharing states between threads. These rules exist to prevent unsafe shared -access to mutable states. If you come from a JVM background and write concurrent code, you may need to change the way -you architect your data, but doing so will allow you to achieve the same results without risky side effects. - -It is also important to point out that there are [ways to work around these rules](multiplatform-mobile-concurrent-mutability.md). -The intent is to make working around these rules something that you rarely have to do, if ever. - -There are just two simple rules regarding state and concurrency. - -### Rule 1: Mutable state == 1 thread - -If your state is mutable, only one thread can _see_ it at a time. Any regular class state that -you would normally use in Kotlin is considered by the Kotlin/Native runtime as _mutable_. If you aren't using concurrency, -Kotlin/Native behaves the same as any other Kotlin code, with the exception of [global state](#global-state). - -```kotlin -data class SomeData(var count:Int) - -fun simpleState(){ - val sd = SomeData(42) - sd.count++ - println("My count is ${sd.count}") // It will be 43 -} -``` - -If there's only one thread, you won't have concurrency issues. Technically this is referred -to as _thread confinement_, which means that you cannot change the UI from a background thread. Kotlin/Native's state rules -formalize that concept for all threads. - -### Rule 2: Immutable state == many threads - -If a state can't be changed, multiple threads can safely access it. -In Kotlin/Native, _immutable_ doesn't mean everything is a `val`. It means _frozen state_. - -## Immutable and frozen state - -The example below is immutable by definition – it has 2 `val` elements, and both are of final immutable types. - -```kotlin -data class SomeData(val s:String, val i:Int) -``` - -This next example may be immutable or mutable. It is not clear what `SomeInterface` will do internally at compile time. -In Kotlin, it is not possible to determine deep immutability statically at compile time. - -```kotlin -data class SomeData(val s:String, val i:SomeInterface) -``` - -Kotlin/Native needs to verify that some part of a state really is immutable at runtime. The runtime could simply go -through the whole state and verify that each part is deeply immutable, but that would be inflexible. And if you needed -to do that every time the runtime wanted to check mutability, there would be significant consequences for performance. - -Kotlin/Native defines a new runtime state called _frozen_. Any instance of an object may be frozen. If an object is frozen: - -1. You cannot change any part of its state. Attempting to do so will result in a runtime exception: `InvalidMutabilityException`. -A frozen object instance is 100%, runtime-verified, immutable. -2. Everything it references is also frozen. All other objects it has a reference to are guaranteed to be frozen. This means that, -when the runtime needs to determine whether an object can be shared with another thread, it only needs to check whether that object -is frozen. If it is, the whole graph is also frozen and is safe to be shared. - -The Native runtime adds an extension function `freeze()` to all classes. Calling `freeze()` will freeze an object, and everything -referenced by the object, recursively. - -```kotlin -data class MoreData(val strData: String, var width: Float) -data class SomeData(val moreData: MoreData, var count: Int) -//... -val sd = SomeData(MoreData("abc", 10.0), 0) -sd.freeze() -``` - -![Freezing state](freezing-state.png){animated="true"} - -* `freeze()` is a one-way operation. You can't _unfreeze_ something. -* `freeze()` is not available in shared Kotlin code, but several libraries provide expect and actual declarations - for using it in shared code. However, if you're using a concurrency library, like [`kotlinx.coroutines`](https://github.com/Kotlin/kotlinx.coroutines), it will -likely freeze data that crosses thread boundaries automatically. - -`freeze` is not unique to Kotlin. You can also find it in [Ruby](https://www.honeybadger.io/blog/when-to-use-freeze-and-frozen-in-ruby/) and [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze). - -## Global state - -Kotlin allows you to define a state as globally available. If left simply mutable, the global state would violate [_Rule 1_](#rule-1-mutable-state-1-thread). -To conform to Kotlin/Native's state rules, the global state has some special conditions. -These conditions freeze the state or make it visible only to a single thread. - -### Global `object` - -Global `object` instances are frozen by default. This means that all threads can access them, but they are immutable. The following won't work. - -```kotlin -object SomeState{ - var count = 0 - fun add(){ - count++ //This will throw an exception - } -} -``` - -Trying to change `count` will throw an exception because `SomeState` is frozen (which means all of its data is frozen). - -You can make a global object thread _local_, which will allow it to be mutable and give each thread a copy of its state. -Annotate it with `@ThreadLocal`. - -```kotlin -@ThreadLocal -object SomeState{ - var count = 0 - fun add(){ - count++ //👍 - } -} -``` - -If different threads read `count`, they'll get different values, because each thread has its own copy. - -These global object rules also apply to companion objects. - -```kotlin -class SomeState{ - companion object{ - var count = 0 - fun add(){ - count++ //This will throw an exception - } - } -} -``` - -### Global properties - -Global properties are a special case. *They are only available to the main thread*, but they are mutable. Accessing them from -other threads will throw an exception. - -```kotlin -val hello = "Hello" //Only main thread can see this -``` - -You can annotate them with : - -* `@SharedImmutable`, which will make them globally available but frozen. -* `@ThreadLocal`, which will give each thread its own mutable copy. - -This rule applies to global properties with backing fields. Computed properties and global functions do not have the main -thread restriction. - -## Current and future models - -Kotlin/Native's concurrency rules will require some adjustment in architecture design, but with the help of libraries and -new best practices, day to day development is basically unaffected. In fact, adhering to Kotlin/Native's rules regarding -multiplatform code will result in safer concurrency across the cross-platform mobile application. - -In the Kotlin Multiplatform application, you have Android and iOS targets with different state rules. Some teams, generally ones working on -larger applications, share code for very specific functionality, and often manage concurrency in the host platform. -This will require explicit freezing of states returned from Kotlin, but otherwise, it is straightforward. - -A more extensive model, where concurrency is managed in Kotlin -and the host communicates on its main thread to shared code, is simpler from a state management perspective. -Concurrency libraries, like [`kotlinx.coroutines`](https://github.com/Kotlin/kotlinx.coroutines), -will help automate freezing. You'll also be able to leverage the power of [coroutines](coroutines-overview.md) -in your code and increase efficiency by sharing more code. - -However, the current Kotlin/Native concurrency model has a number of deficiencies. For example, mobile developers are used to freely -sharing their objects between threads, and they have already developed a number of approaches and architectural patterns to -avoid data races while doing so. It is possible to write efficient applications that do not block the main thread using -Kotlin/Native, but the ability to do so comes with a steep learning curve. - -That's why we are working on creating a new memory manager and concurrency model for Kotlin/Native that will help us remove these -drawbacks. Learn more about [where we are going with this](https://blog.jetbrains.com/kotlin/2020/07/kotlin-native-memory-management-roadmap/). - -_This material was prepared by [Touchlab](https://touchlab.co/) for publication by JetBrains._ diff --git a/docs/topics/multiplatform-mobile/multiplatform-mobile-concurrent-mutability.md b/docs/topics/multiplatform-mobile/multiplatform-mobile-concurrent-mutability.md deleted file mode 100644 index 83917d6289d..00000000000 --- a/docs/topics/multiplatform-mobile/multiplatform-mobile-concurrent-mutability.md +++ /dev/null @@ -1,212 +0,0 @@ -[//]: # (title: Concurrent mutability) - -> This page describes the features of the legacy memory manager. Check out [Kotlin/Native memory management](native-memory-manager.md) -> to learn about the new memory manager, which has been enabled by default since Kotlin 1.7.20. -> -> For more information on the concurrent programming in Kotlin, see the [Coroutines guide](coroutines-guide.md). -> -{type="warning"} - -When it comes to working with iOS, [Kotlin/Native's state and concurrency model](multiplatform-mobile-concurrency-overview.md) has [two simple rules](multiplatform-mobile-concurrency-overview.md#rules-for-state-sharing). - -1. A mutable, non-frozen state is visible to only one thread at a time. -2. An immutable, frozen state can be shared between threads. - -The result of following these rules is that you can't change [global states](multiplatform-mobile-concurrency-overview.md#global-state), -and you can't change the same shared state from multiple threads. In many cases, simply changing your approach to -how you design your code will work fine, and you don't need concurrent mutability. States were mutable from multiple threads in -JVM code, but they didn't *need* to be. - -However, in many other cases, you may need arbitrary thread access to a state, or you may have _service_ objects that should be - available to the entire application. Or maybe you simply don't want to go through the potentially costly exercise of -redesigning existing code. Whatever the reason, _it will not always be feasible to constrain a mutable state to a single thread_. - -There are various techniques that help you work around these restrictions, each with their own pros and cons: - -* [Atomics](#atomics) -* [Thread-isolated states](#thread-isolated-state) -* [Low-level capabilities](#low-level-capabilities) - -## Atomics - -Kotlin/Native provides a set of Atomic classes that can be frozen while still supporting changes to the value they contain. -These classes implement a special-case handling of states in the Kotlin/Native runtime. This means that you can change -values inside a frozen state. - -The Kotlin/Native runtime includes a few different variations of Atomics. You can use them directly or from a library. - -Kotlin provides an experimental low-level [`kotlinx.atomicfu`](https://github.com/Kotlin/kotlinx.atomicfu) library that is currently -used only for internal purposes and is not supported for general usage. You can also use [Stately](https://github.com/touchlab/Stately), -a utility library for multiplatform compatibility with Kotlin/Native-specific concurrency, developed by [Touchlab](https://touchlab.co). - -### `AtomicInt`/`AtomicLong` - -The first two are simple numerics: `AtomicInt` and `AtomicLong`. They allow you to have a shared `Int` or `Long` that can be -read and changed from multiple threads. - -```kotlin -object AtomicDataCounter { - val count = AtomicInt(3) - - fun addOne() { - count.increment() - } -} -``` - -The example above is a global `object`, which is frozen by default in Kotlin/Native. In this case, however, you can change the value of `count`. -It's important to note that you can change the value of `count` _from any thread_. - -### `AtomicReference` - -`AtomicReference` holds an object instance, and you can change that object instance. The object you put in `AtomicReference` -must be frozen, but you can change the value that `AtomicReference` holds. For example, the following won't work in Kotlin/Native: - -```kotlin -data class SomeData(val i: Int) - -object GlobalData { - var sd = SomeData(0) - - fun storeNewValue(i: Int) { - sd = SomeData(i) //Doesn't work - } -} -``` - -According to the [rules of global state](multiplatform-mobile-concurrency-overview.md#global-state), global `object` values are -frozen in Kotlin/Native, so trying to modify `sd` will fail. You could implement it instead with `AtomicReference`: - -```kotlin -data class SomeData(val i: Int) - -object GlobalData { - val sd = AtomicReference(SomeData(0).freeze()) - - fun storeNewValue(i: Int) { - sd.value = SomeData(i).freeze() - } -} -``` - -The `AtomicReference` itself is frozen, which lets it live inside something that is frozen. The data in the `AtomicReference` -instance is explicitly frozen in the code above. However, in the multiplatform libraries, the data -will be frozen automatically. If you use the Kotlin/Native runtime's `AtomicReference`, you *should* remember to call -`freeze()` explicitly. - -`AtomicReference` can be very useful when you need to share a state. There are some drawbacks to consider, however. - -Accessing and changing values in an `AtomicReference` is very costly performance-wise *relative to* a standard mutable state. -If performance is a concern, you may want to consider using another approach involving a [thread-isolated state](#thread-isolated-state). - -There is also a potential issue with memory leaks, which will be resolved in the future. In situations where the object -kept in the `AtomicReference` has cyclical references, it may leak memory if you don't explicitly clear it out: - -* If you have state that may have cyclic references and needs to be reclaimed, you should use a nullable type in the -`AtomicReference` and set it to null explicitly when you're done with it. -* If you're keeping `AtomicReference` in a global object that never leaves scope, this won't matter (because the memory -never needs to be reclaimed during the life of the process). - -```kotlin -class Container(a:A) { - val atom = AtomicReference(a.freeze()) - - /** - * Call when you're done with Container - */ - fun clear(){ - atom.value = null - } -} -``` - -Finally, there's also a consistency concern. Setting/getting values in `AtomicReference` is itself atomic, but if your -logic requires a longer chain of thread exclusion, you'll need to implement that yourself. For example, if you have a -list of values in an `AtomicReference` and you want to scan them first before adding a new one, you'll need to have some -form of concurrency management that `AtomicReference` alone does not provide. - -The following won't protect against duplicate values in the list if called from multiple threads: - -```kotlin -object MyListCache { - val atomicList = AtomicReference(listOf().freeze()) - fun addEntry(s:String){ - val l = atomicList.value - val newList = mutableListOf() - newList.addAll(l) - if(!newList.contains(s)){ - newList.add(s) - } - atomicList.value = newList.freeze() - } -} -``` - -You will need to implement some form of locking or check-and-set logic to ensure proper concurrency. - -## Thread-isolated state - -[Rule 1 of Kotlin/Native state](multiplatform-mobile-concurrency-overview.md#rule-1-mutable-state-1-thread) is that a mutable state is -visible to only one thread. Atomics allow mutability from any thread. -Isolating a mutable state to a single thread, and allowing other threads to communicate with that state, is an alternative -method for achieving concurrent mutability. - -To do this, create a work queue that has exclusive access to a thread, and create a mutable state that -lives in just that thread. Other threads communicate with the mutable thread by scheduling _work_ on the work queue. - -Data that goes in or comes out, if any, needs to be frozen, but the mutable state hidden in the worker thread remains -mutable. - -Conceptually it looks like the following: one thread pushes a frozen state into the state worker, which stores it in -the mutable state container. Another thread later schedules work that takes that state out. - -![Thread-isolated state](isolated-state.png){animated="true"} - -Implementing thread-isolated states is somewhat complex, but there are libraries that provide this functionality. - -### `AtomicReference` vs. thread-isolated state - -For simple values, `AtomicReference` will likely be an easier option. For cases with significant states, and potentially -significant state changes, using a thread-isolated state may be a better choice. The main performance penalty is actually crossing -over threads. But in performance tests with collections, for example, a thread-isolated state significantly outperforms a -mutable state implemented with `AtomicReference`. - -The thread-isolated state also avoids the consistency issues that `AtomicReference` has. Because all operations happen in the -state thread, and because you're scheduling work, you can perform operations with multiple steps and guarantee consistency -without managing thread exclusion. Thread isolation is a design feature of the Kotlin/Native state rules, and -isolating mutable states works with those rules. - -The thread-isolated state is also more flexible insofar as you can make mutable states concurrent. -You can use any type of mutable state, rather than needing to create complex concurrent implementations. - -## Low-level capabilities - -Kotlin/Native has some more advanced ways of sharing concurrent states. To achieve high performance, you may need to avoid -the concurrency rules altogether. - -> This is a more advanced topic. You should have a deep understanding of how concurrency in Kotlin/Native works under -> the hood, and you'll need to be very careful when using this approach. Learn more about [concurrency](native-immutability.md#concurrency-in-kotlin-native). -> -{type="note"} - -Kotlin/Native runs on top of C++ and provides interop with C and Objective-C. If you are running on iOS, you can also pass lambda -arguments into your shared code from Swift. All of this native code runs outside of the Kotlin/Native state restrictions. - -That means that you can implement a concurrent mutable state in a native language and have Kotlin/Native talk to it. - -You can use [Objective-C interop](native-c-interop.md) to access low-level code. -You can also use Swift to implement Kotlin interfaces or pass in lambdas that Kotlin code can call -from any thread. - -One of the benefits of a platform-native approach is performance. On the negative side, you'll need to manage concurrency on your own. -Objective-C does not know about `frozen`, but if you store states from Kotlin in Objective-C structures, and share them -between threads, the Kotlin states definitely need to be frozen. -Kotlin/Native's runtime will generally warn you about issues, but it's possible -to cause concurrency problems in native code that are very, very difficult to track down. It is also very easy to create -memory leaks. - -Since in the Kotlin Multiplatform application you are also targeting the JVM, you'll need alternate ways to implement anything you use -platform native code for. This will obviously take more work and may lead to platform inconsistencies. - -_This material was prepared by [Touchlab](https://touchlab.co/) for publication by JetBrains._ - diff --git a/docs/topics/multiplatform-mobile/multiplatform-mobile-ktor-sqldelight.md b/docs/topics/multiplatform-mobile/multiplatform-mobile-ktor-sqldelight.md index d62b258a900..3572a16a4bc 100644 --- a/docs/topics/multiplatform-mobile/multiplatform-mobile-ktor-sqldelight.md +++ b/docs/topics/multiplatform-mobile/multiplatform-mobile-ktor-sqldelight.md @@ -1020,7 +1020,7 @@ library. This tutorial features some potentially resource-heavy operations, like parsing JSON and making requests to the database in the main thread. To learn about how to write concurrent code and optimize your app, -see [How to work with concurrency](multiplatform-mobile-concurrency-overview.md). +see the [Coroutines guide](coroutines-guide.md). You can also check out these additional learning materials: diff --git a/docs/topics/native/native-binary-licenses.md b/docs/topics/native/native-binary-licenses.md index dd89c3ed507..21397ba74c4 100644 --- a/docs/topics/native/native-binary-licenses.md +++ b/docs/topics/native/native-binary-licenses.md @@ -60,12 +60,11 @@ Always include the following license files for the corresponding projects: -Specific targets require additional license files: +The `mingwX64` target requires additional license files: -| Project | Targets | Files to be included | -|-----------------------------------------------------------------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [MinGW-w64 headers and runtime libraries](https://www.mingw-w64.org/) | `mingw*` |
  • MinGW-w64 runtime license
  • Winpthreads license
  • | -| [Musl (math implementation)](https://musl.libc.org/) | `wasm32` | [Musl copyright notice](https://github.com/JetBrains/kotlin/blob/master/kotlin-native/runtime/src/main/cpp/math/COPYRIGHT) | +| Project | Files to be included | +|-----------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [MinGW-w64 headers and runtime libraries](https://www.mingw-w64.org/) |
  • MinGW-w64 runtime license
  • Winpthreads license
  • | > None of these libraries require the distributed Kotlin/Native binaries to be open-sourced. > diff --git a/docs/topics/native/native-immutability.md b/docs/topics/native/native-immutability.md deleted file mode 100644 index 60e351cc1e9..00000000000 --- a/docs/topics/native/native-immutability.md +++ /dev/null @@ -1,224 +0,0 @@ -[//]: # (title: Immutability and concurrency in Kotlin/Native) - -> This page describes the features of the legacy memory manager. Check out [Kotlin/Native memory management](native-memory-manager.md) -> to learn about the new memory manager, which has been enabled by default since Kotlin 1.7.20. -> -> For more information on the concurrent programming in Kotlin, see the [Coroutines guide](coroutines-guide.md). -> -{type="warning"} - -Kotlin/Native implements strict mutability checks, ensuring -the important invariant that the object is either immutable or -accessible from the single thread at that moment in time (`mutable XOR global`). - -Immutability is a runtime property in Kotlin/Native, and can be applied -to an arbitrary object subgraph using the `kotlin.native.concurrent.freeze` function. -It makes all the objects reachable from the given one immutable. Such a transition is a one-way operation. For example, objects cannot be unfrozen later. -Some naturally immutable objects such as `kotlin.String`, `kotlin.Int`, and -other primitive types, along with `AtomicInt` and `AtomicReference`, are frozen -by default. If a mutating operation is applied to a frozen object, -an `InvalidMutabilityException` is thrown. - -To achieve `mutable XOR global` invariant, all globally visible states (currently, -`object` singletons and enums) are automatically frozen. If object freezing -is not desired, a `kotlin.native.ThreadLocal` annotation can be used, which will make -the object state thread-local, and so, mutable (but the changed state is not visible to -other threads). - -Top-level/global variables of non-primitive types are by default accessible in the -main thread (i.e., the thread which initialized _Kotlin/Native_ runtime first) only. -Access from another thread will lead to an `IncorrectDereferenceException` being thrown. -To make such variables accessible in other threads, you can use either the `@ThreadLocal` annotation -and mark the value thread-local or `@SharedImmutable`, which will make the value frozen and accessible -from other threads. See [Global variables and singletons](#global-variables-and-singletons). - -Class `AtomicReference` can be used to publish the changed frozen state to -other threads and build patterns like shared caches. See [Atomic primitives and references](#atomic-primitives-and-references). - -## Concurrency in Kotlin/Native - -Kotlin/Native runtime doesn't encourage a classical thread-oriented concurrency -model with mutually exclusive code blocks and conditional variables, as this model is -known to be error-prone and unreliable. Instead, we suggest a collection of -alternative approaches, allowing you to use hardware concurrency and implement blocking IO. -Those approaches are as follows, and they will be elaborated on in further sections: - -### Workers - -Instead of threads, Kotlin/Native runtime offers the concept of workers: concurrently executed -control flow streams with an associated request queue. Workers are very similar to the actors -in the Actor Model. A worker can exchange Kotlin objects with another worker so that at any moment, -each mutable object is owned by a single worker, but ownership can be transferred. -See section [Object transfer and freezing](#object-transfer-and-freezing). - -Once a worker is started with the `Worker.start` function call, it can be addressed with its own unique integer -worker id. Other workers, or non-worker concurrency primitives, such as OS threads, can send a message -to the worker with the `execute` call. - -```kotlin -val future = execute(TransferMode.SAFE, { SomeDataForWorker() }) { - // data returned by the second function argument comes to the - // worker routine as 'input' parameter. - input -> - // Here we create an instance to be returned when someone consumes result future. - WorkerResult(input.stringParam + " result") -} - -future.consume { - // Here we see result returned from routine above. Note that future object or - // id could be transferred to another worker, so we don't have to consume future - // in same execution context it was obtained. - result -> println("result is $result") -} -``` - -The call to `execute` uses a function passed as its second parameter to produce an object subgraph -(for example, a set of mutually referring objects) which is then passed as a whole to that worker. It is then no longer -available to the thread that initiated the request. This property is checked if the first parameter -is `TransferMode.SAFE` by graph traversal and is just assumed to be true if it is `TransferMode.UNSAFE`. -The last parameter to `execute` is a special Kotlin lambda, which is not allowed to capture any state -and is actually invoked in the target worker's context. Once processed, the result is transferred to whatever consumes -it in the future, and it is attached to the object graph of that worker/thread. - -If an object is transferred in `UNSAFE` mode and is still accessible from multiple concurrent executors, -the program will likely crash unexpectedly, so consider that last resort in optimizing, not a general-purpose -mechanism. - -### Object transfer and freezing - -An important invariant that Kotlin/Native runtime maintains is that the object is either owned by a single -thread/worker, or it is immutable (_shared XOR mutable_). This ensures that the same data has a single mutator, -and so there is no need for locking to exist. To achieve such an invariant, we use the concept of not externally -referred object subgraphs. -This is a subgraph without external references from outside of the subgraph, which could be checked -algorithmically with O(N) complexity (in ARC systems), where N is the number of elements in such a subgraph. -Such subgraphs are usually produced as a result of a lambda expression, for example, some builder, and may not -contain objects referred to externally. - -Freezing is a runtime operation making a given object subgraph immutable by modifying the object header -so that future mutation attempts throw an `InvalidMutabilityException`. It is deep, so -if an object has a pointer to other objects, the transitive closure of such objects will be frozen. -Freezing is a one-way transformation; frozen objects cannot be unfrozen. Frozen objects have a nice -property that, due to their immutability, they can be freely shared between multiple workers/threads -without breaking the "mutable XOR shared" invariant. - -If an object is frozen, it can be checked with an extension property `isFrozen`, and if it is, object sharing -is allowed. Currently, Kotlin/Native runtime only freezes the enum objects after creation, although additional -auto-freezing of certain provably immutable objects could be implemented in the future. - -### Object subgraph detachment - -An object subgraph without external references can be disconnected using `DetachedObjectGraph` to -a `COpaquePointer` value, which could be stored in `void*` data, so the disconnected object subgraphs -can be stored in a C data structure, and later attached back with `DetachedObjectGraph.attach()` in an arbitrary thread -or a worker. Together with [raw memory sharing](#raw-shared-memory), it allows side-channel object transfer between -concurrent threads if the worker mechanisms are insufficient for a particular task. Note that object detachment -may require an explicit leaving function holding object references and then performing cyclic garbage collection. -For example, code like: - -```kotlin -val graph = DetachedObjectGraph { - val map = mutableMapOf() - for (entry in map.entries) { - // ... - } - map -} -``` - -will not work as expected and will throw runtime exception, as there are uncollected cycles in the detached graph, while: - -```kotlin -val graph = DetachedObjectGraph { - { - val map = mutableMapOf() - for (entry in map.entries) { - // ... - } - map - }().also { - kotlin.native.internal.GC.collect() - } - } -``` - -will work properly, as holding references will be released, and then cyclic garbage affecting the reference counter is -collected. - -### Raw shared memory - -Considering the strong ties between Kotlin/Native and C via interoperability, in conjunction with the other mechanisms -mentioned above, it is possible to build popular data structures, like concurrent hashmap or shared cache, with -Kotlin/Native. It is possible to rely upon shared C data and store references to detached object subgraphs in it. -Consider the following .def file: - -```c -package = global - ---- -typedef struct { - int version; - void* kotlinObject; -} SharedData; - -SharedData sharedData; -``` - -After running the cinterop tool, it can share Kotlin data in a versionized global structure, -and interact with it from Kotlin transparently via autogenerated Kotlin like this: - -```kotlin -class SharedData(rawPtr: NativePtr) : CStructVar(rawPtr) { - var version: Int - var kotlinObject: COpaquePointer? -} -``` - -So in combination with the top-level variable declared above, it can allow looking at the same memory from different -threads and building traditional concurrent structures with platform-specific synchronization primitives. - -### Global variables and singletons - -Frequently, global variables are a source of unintended concurrency issues, so _Kotlin/Native_ implements -the following mechanisms to prevent the unintended sharing of state via global objects: - -* global variables, unless specially marked, can be only accessed from the main thread (that is, the thread - _Kotlin/Native_ runtime was first initialized), if other thread access such a global, `IncorrectDereferenceException` is thrown -* for global variables marked with the `@kotlin.native.ThreadLocal` annotation, each thread keeps a thread-local copy, - so changes are not visible between threads -* for global variables marked with the `@kotlin.native.SharedImmutable` annotation value is shared, but frozen - before publishing, so each thread sees the same value -* singleton objects unless marked with `@kotlin.native.ThreadLocal` are frozen and shared, lazy values allowed, - unless cyclic frozen structures were attempted to be created -* enums are always frozen - -These mechanisms combined allow natural race-free programming with code reuse across platforms in Multiplatform projects. - -### Atomic primitives and references - -Kotlin/Native standard library provides primitives for safe working with concurrently mutable data, namely -`AtomicInt`, `AtomicLong`, `AtomicNativePtr`, `AtomicReference` and `FreezableAtomicReference` in the package -`kotlin.native.concurrent`. -Atomic primitives allow concurrency-safe update operations, such as increment, decrement, and compare-and-swap, -along with value setters and getters. Atomic primitives are always considered frozen by the runtime, and -while their fields can be updated with the regular `field.value += 1`, it is not concurrency safe. -The value must be changed using dedicated operations so it is possible to perform concurrent-safe -global counters and similar data structures. - -Some algorithms require shared mutable references across multiple workers. For example, the global mutable -configuration could be implemented as an immutable instance of properties list atomically replaced with the -new version on configuration update as the whole in a single transaction. This way, no inconsistent configuration -could be seen, and at the same time, the configuration could be updated as needed. -To achieve such functionality, Kotlin/Native runtime provides two related classes: -`kotlin.native.concurrent.AtomicReference` and `kotlin.native.concurrent.FreezableAtomicReference`. -Atomic reference holds a reference to a frozen or immutable object, and its value could be updated by set -or compare-and-swap operation. Thus, a dedicated set of objects could be used to create mutable shared object graphs -(of immutable objects). Cycles in the shared memory could be created using atomic references. -Kotlin/Native runtime doesn't support garbage collecting cyclic data when the reference cycle goes through -`AtomicReference` or frozen `FreezableAtomicReference`. So to avoid memory leaks, atomic references -that are potentially parts of shared cyclic data should be zeroed out once no longer needed. - -If atomic reference value is attempted to be set to a non-frozen value, a runtime exception is thrown. - -Freezable atomic reference is similar to the regular atomic reference until frozen behaves like a regular box -for a reference. After freezing, it behaves like an atomic reference and can only hold a reference to a frozen object. diff --git a/docs/topics/native/native-ios-integration.md b/docs/topics/native/native-ios-integration.md index 73d177e0b3b..3e879fe2267 100644 --- a/docs/topics/native/native-ios-integration.md +++ b/docs/topics/native/native-ios-integration.md @@ -1,10 +1,5 @@ [//]: # (title: iOS integration) -> This page describes the new memory manager enabled by default since Kotlin 1.7.20. See our [migration guide](native-migration-guide.md) -> to move your projects from the legacy memory manager that will be removed in Kotlin 1.9.20. -> -{type="note"} - Integration of Kotlin/Native garbage collector with Swift/Objective-C ARC is seamless and generally requires no additional work to be done. Learn more about [Swift/Objective-C interoperability](native-objc-interop.md). diff --git a/docs/topics/native/native-memory-manager.md b/docs/topics/native/native-memory-manager.md index 9fc400fab37..abfa5cd6388 100644 --- a/docs/topics/native/native-memory-manager.md +++ b/docs/topics/native/native-memory-manager.md @@ -1,18 +1,10 @@ [//]: # (title: Kotlin/Native memory management) -> This page describes features of the new memory manager enabled by default since Kotlin 1.7.20. See our [migration guide](native-migration-guide.md) -> to move your projects from the legacy memory that will be removed in Kotlin 1.9.20. -> -{type="note"} - Kotlin/Native uses a modern memory manager that is similar to JVM, Go, and other mainstream technologies: * Objects are stored in a shared heap and can be accessed from any thread. * Tracing garbage collector (GC) is executed periodically to collect objects that are not reachable from the "roots", like local and global variables. -The memory manager is the same across all the Kotlin/Native targets, except for wasm32, which is only supported in the -[legacy memory manager](#legacy-memory-manager). - ## Garbage collector The exact algorithm of GC is constantly evolving. As of 1.7.20, it is the Stop-the-World Mark and Concurrent Sweep @@ -154,23 +146,6 @@ fun mainBackground(args: Array) { Then, compile the test binary with the `-e testlauncher.mainBackground` compiler flag. -## Legacy memory manager - -If it's necessary, you can switch back to the legacy memory manager. Set the following option in your `gradle.properties`: - -```none -kotlin.native.binary.memoryModel=strict -``` - -> * Compiler cache support is not available for the legacy memory manager, so compilation times might - become worse. -> * This Gradle option for reverting to the legacy memory manager will be removed in future releases. -> -{type="note"} - -If you encounter issues with migrating from the legacy memory manager, or you want to temporarily support both the current -and legacy memory managers, see our recommendations in the [migration guide](native-migration-guide.md). - ## What's next * [Migrate from the legacy memory manager](native-migration-guide.md) diff --git a/docs/topics/native/native-target-support.md b/docs/topics/native/native-target-support.md index fce82f25e77..2fc6efb38bc 100644 --- a/docs/topics/native/native-target-support.md +++ b/docs/topics/native/native-target-support.md @@ -22,16 +22,15 @@ Mind the following terms used in tier tables: ## Tier 1 * The target is regularly tested on CI to be able to compile and run. -* We're doing our best to provide a source and [binary compatibility between compiler releases](https://youtrack.jetbrains.com/issue/KT-42293). +* We provide a source and [binary compatibility between compiler releases](https://youtrack.jetbrains.com/issue/KT-42293). -| Gradle target name | Target triple | Running tests | Description | -|------------------------|-------------------------------|---------------|------------------------------------------------| -| `linuxX64` | `x86_64-unknown-linux-gnu` | ✅ | Linux on x86_64 platforms | -| Apple macOS hosts only | | | | -| `macosX64` | `x86_64-apple-macos` | ✅ | Apple macOS on x86_64 platforms | -| `macosArm64` | `aarch64-apple-macos` | ✅ | Apple macOS on Apple Silicon platforms | -| `iosSimulatorArm64` | `aarch64-apple-ios-simulator` | ✅ | Apple iOS simulator on Apple Silicon platforms | -| `iosX64` | `x86_64-apple-ios-simulator` | ✅ | Apple iOS simulator on x86-64 platforms | +| Gradle target name | Target triple | Running tests | Description | +|-------------------------|-------------------------------|---------------|------------------------------------------------| +| Apple macOS hosts only: | | | | +| `macosX64` | `x86_64-apple-macos` | ✅ | Apple macOS on x86_64 platforms | +| `macosArm64` | `aarch64-apple-macos` | ✅ | Apple macOS on Apple Silicon platforms | +| `iosSimulatorArm64` | `aarch64-apple-ios-simulator` | ✅ | Apple iOS simulator on Apple Silicon platforms | +| `iosX64` | `x86_64-apple-ios-simulator` | ✅ | Apple iOS simulator on x86-64 platforms | ## Tier 2 @@ -40,8 +39,9 @@ Mind the following terms used in tier tables: | Gradle target name | Target triple | Running tests | Description | |-------------------------|-----------------------------------|---------------|----------------------------------------------------| +| `linuxX64` | `x86_64-unknown-linux-gnu` | ✅ | Linux on x86_64 platforms | | `linuxArm64` | `aarch64-unknown-linux-gnu` | | Linux on ARM64 platforms | -| Apple macOS hosts only | | | | +| Apple macOS hosts only: | | | | | `watchosSimulatorArm64` | `aarch64-apple-watchos-simulator` | ✅ | Apple watchOS simulator on Apple Silicon platforms | | `watchosX64` | `x86_64-apple-watchos-simulator` | ✅ | Apple watchOS 64-bit simulator on x86_64 platforms | | `watchosArm32` | `armv7k-apple-watchos` | | Apple watchOS on ARM32 platforms | @@ -66,27 +66,19 @@ Mind the following terms used in tier tables: * We can't promise a source and binary compatibility between different compiler releases, though such changes for these targets are quite rare. -| Gradle target name | Target triple | Running tests | Description | -|------------------------|---------------------------------|---------------|----------------------------------------------------------------------| -| `androidNativeArm32` | `arm-unknown-linux-androideabi` | | [Android NDK](https://developer.android.com/ndk) on ARM32 platforms | -| `androidNativeArm64` | `aarch64-unknown-linux-android` | | [Android NDK](https://developer.android.com/ndk) on ARM64 platforms | -| `androidNativeX86` | `i686-unknown-linux-android` | | [Android NDK](https://developer.android.com/ndk) on x86 platforms | -| `androidNativeX64` | `x86_64-unknown-linux-android` | | [Android NDK](https://developer.android.com/ndk) on x86_64 platforms | -| `mingwX64` | `x86_64-pc-windows-gnu` | ✅ | 64-bit [MinGW](https://www.mingw-w64.org) on Windows 7 and later | -| Apple macOS hosts only | | | | -| `watchosDeviceArm64` | `aarch64-apple-watchos` | | Apple watchOS on ARM64 platforms | - -## Deprecated targets - -The following targets are deprecated since Kotlin 1.8.20 and will be removed in 1.9.20: - -* `iosArm32` -* `watchosX86` -* `wasm32` -* `mingwX86` -* `linuxArm32Hfp` -* `linuxMips32` -* `linuxMipsel32` +| Gradle target name | Target triple | Running tests | Description | +|-------------------------|---------------------------------|---------------|----------------------------------------------------------------------| +| `androidNativeArm32` | `arm-unknown-linux-androideabi` | | [Android NDK](https://developer.android.com/ndk) on ARM32 platforms | +| `androidNativeArm64` | `aarch64-unknown-linux-android` | | [Android NDK](https://developer.android.com/ndk) on ARM64 platforms | +| `androidNativeX86` | `i686-unknown-linux-android` | | [Android NDK](https://developer.android.com/ndk) on x86 platforms | +| `androidNativeX64` | `x86_64-unknown-linux-android` | | [Android NDK](https://developer.android.com/ndk) on x86_64 platforms | +| `mingwX64` | `x86_64-pc-windows-gnu` | ✅ | 64-bit [MinGW](https://www.mingw-w64.org) on Windows 7 and later | +| Apple macOS hosts only: | | | | +| `watchosDeviceArm64` | `aarch64-apple-watchos` | | Apple watchOS on ARM64 platforms | + +> The `linuxArm32Hfp` target is deprecated and will be removed in future releases. +> +{type="note"} ## For library authors diff --git a/redirects/pages.yml b/redirects/pages.yml index 0b2a6a1fa10..986462286e3 100644 --- a/redirects/pages.yml +++ b/redirects/pages.yml @@ -681,3 +681,14 @@ - from: /user-groups/support.html to: /docs/kug-guidelines.html#support-for-kugs-from-jetbrains + +- from: + - /docs/native-immutability.html + - /docs/native-concurrency.html + - /docs/multiplatform-mobile-concurrency-overview.html + - /docs/kmm-concurrency-overview.html + - /docs/multiplatform-mobile-concurrent-mutability.html + - /docs/kmm-concurrent-mutability.html + - /docs/multiplatform-mobile-concurrency-and-coroutines.html + - /docs/kmm-concurrency-and-coroutines.html + to: /docs/native-memory-manager.html