diff options
Diffstat (limited to 'README.md')
-rw-r--r-- | README.md | 424 |
1 files changed, 234 insertions, 190 deletions
@@ -1,30 +1,49 @@ # AtomicFU -[![JetBrains incubator project](https://jb.gg/badges/incubator.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) +[![Kotlin Beta](https://kotl.in/badges/beta.svg)](https://kotlinlang.org/docs/components-stability.html) +[![JetBrains official project](https://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) [![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0) -[![Download](https://api.bintray.com/packages/kotlin/kotlinx/kotlinx.atomicfu/images/download.svg) ](https://bintray.com/kotlin/kotlinx/kotlinx.atomicfu/_latestVersion) - -The idiomatic way to use atomic operations in Kotlin. - -* Code it like `AtomicReference/Int/Long`, but run it in production efficiently as `AtomicXxxFieldUpdater` on Kotlin/JVM - and as plain unboxed values on Kotlin/JS. -* Use Kotlin-specific extensions (e.g. inline `updateAndGet` and `getAndUpdate` functions). -* Compile-time dependency only (no runtime dependencies). - * Post-compilation bytecode transformer that declares all the relevant field updaters for you on [Kotlin/JVM](#jvm). - * Post-compilation JavaScript files transformer on [Kotlin/JS](#js). -* Multiplatform: - * [Kotlin/Native](#native) is supported. - * However, Kotlin/Native works as library dependency at the moment (unlike Kotlin/JVM and Kotlin/JS). - * This enables writing [common](#common) Kotlin code with atomics that compiles for JVM, JS, and Native. -* [Gradle](#gradle-build-setup) for all platforms and [Maven](#maven-build-setup) for JVM are supported. -* [Additional features](#additional-features) include: - * [JDK9 VarHandle](#varhandles-with-java-9). - * [Arrays of atomic values](#arrays-of-atomic-values). - * [User-defined extensions on atomics](#user-defined-extensions-on-atomics) - * [Locks](#locks) - * [Testing of lock-free data structures](#testing-lock-free-data-structures-on-jvm). - * [Tracing operations](#tracing-operations) - +[![Maven Central](https://img.shields.io/maven-central/v/org.jetbrains.kotlinx/atomicfu)](https://search.maven.org/artifact/org.jetbrains.kotlinx/atomicfu/0.18.5/pom) + +>Note on Beta status: the plugin is in its active development phase and changes from release to release. +>We do provide a compatibility of atomicfu-transformed artifacts between releases, but we do not provide +>strict compatibility guarantees on plugin API and its general stability between Kotlin versions. + +**Atomicfu** is a multiplatform library that provides the idiomatic and effective way of using atomic operations in Kotlin. + +## Table of contents +- [Features](#features) +- [Example](#example) +- [Quickstart](#quickstart) + - [Apply plugin to a project](#apply-plugin) + - [Gradle configuration](#gradle-configuration) + - [Maven configuration](#maven-configuration) +- [Usage constraints](#usage-constraints) +- [Transformation modes](#transformation-modes) + - [Atomicfu compiler plugin](#atomicfu-compiler-plugin) +- [Options for post-compilation transformation](#options-for-post-compilation-transformation) + - [JVM options](#jvm-options) + - [JS options](#js-options) +- [More features](#more-features) + - [Arrays of atomic values](#arrays-of-atomic-values) + - [User-defined extensions on atomics](#user-defined-extensions-on-atomics) + - [Locks](#locks) + - [Tracing operations](#tracing-operations) +- [Kotlin/Native support](#kotlin-native-support) + + +## Features + +* Code it like a boxed value `atomic(0)`, but run it in production efficiently: + * as `java.util.concurrent.atomic.AtomicXxxFieldUpdater` on Kotlin/JVM + * as a plain unboxed value on Kotlin/JS +* Multiplatform: write common Kotlin code with atomics that compiles for Kotlin JVM, JS, and Native backends: + * Compile-only dependency for JVM and JS (no runtime dependencies) + * Compile and runtime dependency for Kotlin/Native +* Use Kotlin-specific extensions (e.g. inline `loop`, `update`, `updateAndGet` functions). +* Use atomic arrays, user-defined extensions on atomics and locks (see [more features](#more-features)). +* [Tracing operations](#tracing-operations) for debugging. + ## Example Let us declare a `top` variable for a lock-free stack implementation: @@ -74,107 +93,67 @@ val myLong = atomic(0L) // note: long initial value Integer and long atomics provide all the usual `getAndIncrement`, `incrementAndGet`, `getAndAdd`, `addAndGet`, and etc operations. They can be also atomically modified via `+=` and `-=` operators. -## Dos and Don'ts - -* Declare atomic variables as `private val` or `internal val`. You can use just (public) `val`, - but make sure they are not directly accessed outside of your Kotlin module (outside of the source set). - Access to the atomic variable itself shall be encapsulated. -* Only simple operations on atomic variables _directly_ are supported. - * Do not read references on atomic variables into local variables, - e.g. `top.compareAndSet(...)` is Ok, while `val tmp = top; tmp...` is not. - * Do not leak references on atomic variables in other way (return, pass as params, etc). -* Do not introduce complex data flow in parameters to atomic variable operations, - i.e. `top.value = complex_expression` and `top.compareAndSet(cur, complex_expression)` are not supported - (more specifically, `complex_expression` should not have branches in its compiled representation). - Extract `complex_expression` into a variable when needed. -* Use the following convention if you need to expose the value of atomic property to the public: - -```kotlin -private val _foo = atomic<T>(initial) // private atomic, convention is to name it with leading underscore -public var foo: T by _foo // public delegated property (val/var) -``` - -## Gradle build setup +## Quickstart +### Apply plugin +#### Gradle configuration -Building with Gradle is supported for all platforms. +Gradle configuration is supported for all platforms, minimal version is Gradle 6.8. -### JVM +In top-level build file: -You will need Gradle 4.10 or later. -Add and apply AtomicFU plugin. It adds all the corresponding dependencies -and transformations automatically. -See [additional configuration](#additional-configuration) if that needs tweaking. +<details open> +<summary>Kotlin</summary> -```groovy +```kotlin buildscript { - ext.atomicfu_version = '0.16.0' + repositories { + mavenCentral() + } dependencies { - classpath "org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicfu_version" + classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.18.5") } } -apply plugin: 'kotlinx-atomicfu' +apply(plugin = "kotlinx-atomicfu") ``` +</details> -### JS - -Configure add apply plugin just like for [JVM](#jvm). - -### Native - -This library is available for Kotlin/Native (`atomicfu-native`). -Kotlin/Native uses Gradle metadata and needs Gradle version 5.3 or later. -See [Gradle Metadata 1.0 announcement](https://blog.gradle.org/gradle-metadata-1.0) for more details. -Apply the corresponding plugin just like for [JVM](#jvm). - -Atomic references for Kotlin/Native are based on -[FreezableAtomicReference](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/-freezable-atomic-reference/-init-.html) -and every reference that is stored to the previously -[frozen](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/freeze.html) -(shared with another thread) atomic is automatically frozen, too. - -Since Kotlin/Native does not generally provide binary compatibility between versions, -you should use the same version of Kotlin compiler as was used to build AtomicFU. -See [gradle.properties](gradle.properties) in AtomicFU project for its `kotlin_version`. - -### Common - -If you write a common code that should get compiled or different platforms, add `org.jetbrains.kotlinx:atomicfu-common` -to your common code dependencies or apply `kotlinx-atomicfu` plugin that adds this dependency automatically: +<details> +<summary>Groovy</summary> ```groovy -dependencies { - compile "org.jetbrains.kotlinx:atomicfu-common:$atomicfu_version" +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.18.5' + } } + +apply plugin: 'kotlinx-atomicfu' ``` +</details> -### Additional configuration - -There are the following additional parameters (with their defaults): +#### Maven configuration -```groovy -atomicfu { - dependenciesVersion = '0.16.0' // set to null to turn-off auto dependencies - transformJvm = true // set to false to turn off JVM transformation - transformJs = true // set to false to turn off JS transformation - variant = "FU" // JVM transformation variant: FU,VH, or BOTH - verbose = false // set to true to be more verbose -} -``` +Maven configuration is supported for JVM projects. -## Maven build setup -Declare AtomicFU version: +<details open> +<summary>Declare atomicfu version</summary> ```xml <properties> - <atomicfu.version>0.16.0</atomicfu.version> + <atomicfu.version>0.18.5</atomicfu.version> </properties> ``` -Declare _provided_ dependency on the AtomicFU library -(the users of the resulting artifact will not have a dependency on AtomicFU library): +</details> + +<details> +<summary>Declare provided dependency on the AtomicFU library</summary> ```xml <dependencies> @@ -187,71 +166,161 @@ Declare _provided_ dependency on the AtomicFU library </dependencies> ``` +</details> + Configure build steps so that Kotlin compiler puts classes into a different `classes-pre-atomicfu` directory, which is then transformed to a regular `classes` directory to be used later by tests and delivery. +<details> +<summary>Build steps</summary> + ```xml <build> - <plugins> - <!-- compile Kotlin files to staging directory --> - <plugin> - <groupId>org.jetbrains.kotlin</groupId> - <artifactId>kotlin-maven-plugin</artifactId> - <version>${kotlin.version}</version> - <executions> - <execution> - <id>compile</id> - <phase>compile</phase> - <goals> - <goal>compile</goal> - </goals> - <configuration> - <output>${project.build.directory}/classes-pre-atomicfu</output> - <!-- "VH" to use Java 9 VarHandle, "BOTH" to produce multi-version code --> - <variant>FU</variant> - </configuration> - </execution> - </executions> - </plugin> - <!-- transform classes with AtomicFU plugin --> - <plugin> - <groupId>org.jetbrains.kotlinx</groupId> - <artifactId>atomicfu-maven-plugin</artifactId> - <version>${atomicfu.version}</version> - <executions> - <execution> - <goals> - <goal>transform</goal> - </goals> - <configuration> - <input>${project.build.directory}/classes-pre-atomicfu</input> - </configuration> - </execution> - </executions> - </plugin> - </plugins> + <plugins> + <!-- compile Kotlin files to staging directory --> + <plugin> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-maven-plugin</artifactId> + <version>${kotlin.version}</version> + <executions> + <execution> + <id>compile</id> + <phase>compile</phase> + <goals> + <goal>compile</goal> + </goals> + <configuration> + <output>${project.build.directory}/classes-pre-atomicfu</output> + </configuration> + </execution> + </executions> + </plugin> + <!-- transform classes with AtomicFU plugin --> + <plugin> + <groupId>org.jetbrains.kotlinx</groupId> + <artifactId>atomicfu-maven-plugin</artifactId> + <version>${atomicfu.version}</version> + <executions> + <execution> + <goals> + <goal>transform</goal> + </goals> + <configuration> + <input>${project.build.directory}/classes-pre-atomicfu</input> + <!-- "VH" to use Java 9 VarHandle, "BOTH" to produce multi-version code --> + <variant>FU</variant> + </configuration> + </execution> + </executions> + </plugin> + </plugins> </build> ``` -## Additional features +</details> -AtomicFU provides some additional features that you can optionally use. +## Usage constraints -### VarHandles with Java 9 +* Declare atomic variables as `private val` or `internal val`. You can use just (public) `val`, + but make sure they are not directly accessed outside of your Kotlin module (outside of the source set). + Access to the atomic variable itself shall be encapsulated. +* Only simple operations on atomic variables _directly_ are supported. + * Do not read references on atomic variables into local variables, + e.g. `top.compareAndSet(...)` is ok, while `val tmp = top; tmp...` is not. + * Do not leak references on atomic variables in other way (return, pass as params, etc). +* Do not introduce complex data flow in parameters to atomic variable operations, + i.e. `top.value = complex_expression` and `top.compareAndSet(cur, complex_expression)` are not supported + (more specifically, `complex_expression` should not have branches in its compiled representation). + Extract `complex_expression` into a variable when needed. +* Use the following convention if you need to expose the value of atomic property to the public: + +```kotlin +private val _foo = atomic<T>(initial) // private atomic, convention is to name it with leading underscore +public var foo: T by _foo // public delegated property (val/var) +``` + +## Transformation modes + +Basically, Atomicfu library provides an effective usage of atomic values by performing the transformations of the compiled code. +For JVM and JS there 2 transformation modes available: +* **Post-compilation transformation** that modifies the compiled bytecode or `*.js` files. +* **IR transformation** that is performed by the atomicfu compiler plugin. + +### Atomicfu compiler plugin + +Compiler plugin transformation is less fragile than transformation of the compiled sources +as it depends on the compiler IR tree. + +To turn on IR transformation set these properties in your `gradle.properties` file: + +<details open> +<summary>For Kotlin >= 1.7.20</summary> + +```groovy +kotlinx.atomicfu.enableJvmIrTransformation=true // for JVM IR transformation +kotlinx.atomicfu.enableJsIrTransformation=true // for JS IR transformation +``` + +</details> + +<details> + + +<summary> For Kotlin >= 1.6.20 and Kotlin < 1.7.20</summary> + +```groovy +kotlinx.atomicfu.enableIrTransformation=true // only JS IR transformation is supported +``` + +</details> + +Also for JS backend make sure that `ir` or `both` compiler mode is set: -AtomicFU can produce code that uses Java 9 -[VarHandle](https://docs.oracle.com/javase/9/docs/api/java/lang/invoke/VarHandle.html) -instead of `AtomicXxxFieldUpdater`. Configure transformation `variant` in Gradle build file: - +```groovy +kotlin.js.compiler=ir // or both +``` + + +## Options for post-compilation transformation + +Some configuration options are available for _post-compilation transform tasks_ on JVM and JS. + +To set configuration options you should create `atomicfu` section in a `build.gradle` file, +like this: ```groovy atomicfu { - variant = "VH" + dependenciesVersion = '0.18.5' } -``` - -It can also create [JEP 238](https://openjdk.java.net/jeps/238) multi-release jar file with both -`AtomicXxxFieldUpdater` for JDK<=8 and `VarHandle` for for JDK9+ if you -set `variant` to `"BOTH"`. +``` + +### JVM options + +To turn off transformation for Kotlin/JVM set option `transformJvm` to `false`. + +Configuration option `jvmVariant` defines the Java class that replaces atomics during bytecode transformation. +Here are the valid options: +- `FU` – atomics are replaced with [AtomicXxxFieldUpdater](https://docs.oracle.com/javase/10/docs/api/java/util/concurrent/atomic/AtomicIntegerFieldUpdater.html). +- `VH` – atomics are replaced with [VarHandle](https://docs.oracle.com/javase/9/docs/api/java/lang/invoke/VarHandle.html), + this option is supported for JDK 9+. +- `BOTH` – [multi-release jar file](https://openjdk.java.net/jeps/238) will be created with both `AtomicXxxFieldUpdater` for JDK <= 8 and `VarHandle` for JDK 9+. + +### JS options + +To turn off transformation for Kotlin/JS set option `transformJs` to `false`. + +Here are all available configuration options (with their defaults): +```groovy +atomicfu { + dependenciesVersion = '0.18.5' // set to null to turn-off auto dependencies + transformJvm = true // set to false to turn off JVM transformation + jvmVariant = "FU" // JVM transformation variant: FU,VH, or BOTH + transformJs = true // set to false to turn off JVM transformation +} +``` + +## More features + +AtomicFU provides some additional features that you can use. ### Arrays of atomic values @@ -297,46 +366,9 @@ use `lock`/`tryLock`/`unlock` functions or `lock.withLock { ... }` extension fun [jucl.ReentrantLock](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantLock.html) is used on JVM. On JVM it is a typealias to the later class, erased on JS. -Condition variables (`notify`/`wait` and `signal`/`await`) are not supported. - -### Testing lock-free data structures on JVM - -You can optionally test lock-freedomness of lock-free data structures using -[`LockFreedomTestEnvironment`](atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/LockFreedomTestEnvironment.kt) class. -See example in [`LockFreeQueueLFTest`](atomicfu/src/jvmTest/kotlin/kotlinx/atomicfu/test/LockFreeQueueLFTest.kt). -Testing is performed by pausing one (random) thread before or after a random state-update operation and -making sure that all other threads can still make progress. - -In order to make those test to actually perform lock-freedomness testing you need to configure an additional -execution of tests with the original (non-transformed) classes for Maven: - -```xml -<build> - <plugins> - <!-- additional test execution with surefire on non-transformed files --> - <plugin> - <artifactId>maven-surefire-plugin</artifactId> - <executions> - <execution> - <id>lockfree-test</id> - <phase>test</phase> - <goals> - <goal>test</goal> - </goals> - <configuration> - <classesDirectory>${project.build.directory}/classes-pre-atomicfu</classesDirectory> - <includes> - <include>**/*LFTest.*</include> - </includes> - </configuration> - </execution> - </executions> - </plugin> - </plugins> -</build> -``` - -For Gradle there is nothing else to add. Tests are always run using original (non-transformed) classes. +> Note that package `kotlinx.atomicfu.locks` is experimental explicitly even while atomicfu is experimental itself, +> meaning that no ABI guarantees are provided whatsoever. API from this package is not recommended to use in libraries +> that other projects depend on. ### Tracing operations @@ -369,3 +401,15 @@ private val trace = Trace(size = 64) { `trace` is only seen before transformation and completely erased after on Kotlin/JVM and Kotlin/JS. +## Kotlin Native support + +Atomic references for Kotlin/Native are based on +[FreezableAtomicReference](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/-freezable-atomic-reference/-init-.html) +and every reference that is stored to the previously +[frozen](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/freeze.html) +(shared with another thread) atomic is automatically frozen, too. + +Since Kotlin/Native does not generally provide binary compatibility between versions, +you should use the same version of Kotlin compiler as was used to build AtomicFU. +See [gradle.properties](gradle.properties) in AtomicFU project for its `kotlin_version`. + |