1# AtomicFU 2 3[![Kotlin Beta](https://kotl.in/badges/beta.svg)](https://kotlinlang.org/docs/components-stability.html) 4[![JetBrains official project](https://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) 5[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0) 6[![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) 7 8>Note on Beta status: the plugin is in its active development phase and changes from release to release. 9>We do provide a compatibility of atomicfu-transformed artifacts between releases, but we do not provide 10>strict compatibility guarantees on plugin API and its general stability between Kotlin versions. 11 12**Atomicfu** is a multiplatform library that provides the idiomatic and effective way of using atomic operations in Kotlin. 13 14## Table of contents 15- [Features](#features) 16- [Example](#example) 17- [Quickstart](#quickstart) 18 - [Apply plugin to a project](#apply-plugin) 19 - [Gradle configuration](#gradle-configuration) 20 - [Maven configuration](#maven-configuration) 21- [Usage constraints](#usage-constraints) 22- [Transformation modes](#transformation-modes) 23 - [Atomicfu compiler plugin](#atomicfu-compiler-plugin) 24- [Options for post-compilation transformation](#options-for-post-compilation-transformation) 25 - [JVM options](#jvm-options) 26 - [JS options](#js-options) 27- [More features](#more-features) 28 - [Arrays of atomic values](#arrays-of-atomic-values) 29 - [User-defined extensions on atomics](#user-defined-extensions-on-atomics) 30 - [Locks](#locks) 31 - [Tracing operations](#tracing-operations) 32- [Kotlin/Native support](#kotlin-native-support) 33 34 35## Features 36 37* Code it like a boxed value `atomic(0)`, but run it in production efficiently: 38 * as `java.util.concurrent.atomic.AtomicXxxFieldUpdater` on Kotlin/JVM 39 * as a plain unboxed value on Kotlin/JS 40* Multiplatform: write common Kotlin code with atomics that compiles for Kotlin JVM, JS, and Native backends: 41 * Compile-only dependency for JVM and JS (no runtime dependencies) 42 * Compile and runtime dependency for Kotlin/Native 43* Use Kotlin-specific extensions (e.g. inline `loop`, `update`, `updateAndGet` functions). 44* Use atomic arrays, user-defined extensions on atomics and locks (see [more features](#more-features)). 45* [Tracing operations](#tracing-operations) for debugging. 46 47## Example 48 49Let us declare a `top` variable for a lock-free stack implementation: 50 51```kotlin 52import kotlinx.atomicfu.* // import top-level functions from kotlinx.atomicfu 53 54private val top = atomic<Node?>(null) 55``` 56 57Use `top.value` to perform volatile reads and writes: 58 59```kotlin 60fun isEmpty() = top.value == null // volatile read 61fun clear() { top.value = null } // volatile write 62``` 63 64Use `compareAndSet` function directly: 65 66```kotlin 67if (top.compareAndSet(expect, update)) ... 68``` 69 70Use higher-level looping primitives (inline extensions), for example: 71 72```kotlin 73top.loop { cur -> // while(true) loop that volatile-reads current value 74 ... 75} 76``` 77 78Use high-level `update`, `updateAndGet`, and `getAndUpdate`, 79when possible, for idiomatic lock-free code, for example: 80 81```kotlin 82fun push(v: Value) = top.update { cur -> Node(v, cur) } 83fun pop(): Value? = top.getAndUpdate { cur -> cur?.next } ?.value 84``` 85 86Declare atomic integers and longs using type inference: 87 88```kotlin 89val myInt = atomic(0) // note: integer initial value 90val myLong = atomic(0L) // note: long initial value 91``` 92 93Integer and long atomics provide all the usual `getAndIncrement`, `incrementAndGet`, `getAndAdd`, `addAndGet`, and etc 94operations. They can be also atomically modified via `+=` and `-=` operators. 95 96## Quickstart 97### Apply plugin 98#### Gradle configuration 99 100Gradle configuration is supported for all platforms, minimal version is Gradle 6.8. 101 102In top-level build file: 103 104<details open> 105<summary>Kotlin</summary> 106 107```kotlin 108buildscript { 109 repositories { 110 mavenCentral() 111 } 112 113 dependencies { 114 classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.18.5") 115 } 116} 117 118apply(plugin = "kotlinx-atomicfu") 119``` 120</details> 121 122<details> 123<summary>Groovy</summary> 124 125```groovy 126buildscript { 127 repositories { 128 mavenCentral() 129 } 130 dependencies { 131 classpath 'org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.18.5' 132 } 133} 134 135apply plugin: 'kotlinx-atomicfu' 136``` 137</details> 138 139#### Maven configuration 140 141Maven configuration is supported for JVM projects. 142 143 144<details open> 145<summary>Declare atomicfu version</summary> 146 147```xml 148<properties> 149 <atomicfu.version>0.18.5</atomicfu.version> 150</properties> 151``` 152 153</details> 154 155<details> 156<summary>Declare provided dependency on the AtomicFU library</summary> 157 158```xml 159<dependencies> 160 <dependency> 161 <groupId>org.jetbrains.kotlinx</groupId> 162 <artifactId>atomicfu</artifactId> 163 <version>${atomicfu.version}</version> 164 <scope>provided</scope> 165 </dependency> 166</dependencies> 167``` 168 169</details> 170 171Configure build steps so that Kotlin compiler puts classes into a different `classes-pre-atomicfu` directory, 172which is then transformed to a regular `classes` directory to be used later by tests and delivery. 173 174<details> 175<summary>Build steps</summary> 176 177```xml 178<build> 179 <plugins> 180 <!-- compile Kotlin files to staging directory --> 181 <plugin> 182 <groupId>org.jetbrains.kotlin</groupId> 183 <artifactId>kotlin-maven-plugin</artifactId> 184 <version>${kotlin.version}</version> 185 <executions> 186 <execution> 187 <id>compile</id> 188 <phase>compile</phase> 189 <goals> 190 <goal>compile</goal> 191 </goals> 192 <configuration> 193 <output>${project.build.directory}/classes-pre-atomicfu</output> 194 </configuration> 195 </execution> 196 </executions> 197 </plugin> 198 <!-- transform classes with AtomicFU plugin --> 199 <plugin> 200 <groupId>org.jetbrains.kotlinx</groupId> 201 <artifactId>atomicfu-maven-plugin</artifactId> 202 <version>${atomicfu.version}</version> 203 <executions> 204 <execution> 205 <goals> 206 <goal>transform</goal> 207 </goals> 208 <configuration> 209 <input>${project.build.directory}/classes-pre-atomicfu</input> 210 <!-- "VH" to use Java 9 VarHandle, "BOTH" to produce multi-version code --> 211 <variant>FU</variant> 212 </configuration> 213 </execution> 214 </executions> 215 </plugin> 216 </plugins> 217</build> 218``` 219 220</details> 221 222## Usage constraints 223 224* Declare atomic variables as `private val` or `internal val`. You can use just (public) `val`, 225 but make sure they are not directly accessed outside of your Kotlin module (outside of the source set). 226 Access to the atomic variable itself shall be encapsulated. 227* Only simple operations on atomic variables _directly_ are supported. 228 * Do not read references on atomic variables into local variables, 229 e.g. `top.compareAndSet(...)` is ok, while `val tmp = top; tmp...` is not. 230 * Do not leak references on atomic variables in other way (return, pass as params, etc). 231* Do not introduce complex data flow in parameters to atomic variable operations, 232 i.e. `top.value = complex_expression` and `top.compareAndSet(cur, complex_expression)` are not supported 233 (more specifically, `complex_expression` should not have branches in its compiled representation). 234 Extract `complex_expression` into a variable when needed. 235* Use the following convention if you need to expose the value of atomic property to the public: 236 237```kotlin 238private val _foo = atomic<T>(initial) // private atomic, convention is to name it with leading underscore 239public var foo: T by _foo // public delegated property (val/var) 240``` 241 242## Transformation modes 243 244Basically, Atomicfu library provides an effective usage of atomic values by performing the transformations of the compiled code. 245For JVM and JS there 2 transformation modes available: 246* **Post-compilation transformation** that modifies the compiled bytecode or `*.js` files. 247* **IR transformation** that is performed by the atomicfu compiler plugin. 248 249### Atomicfu compiler plugin 250 251Compiler plugin transformation is less fragile than transformation of the compiled sources 252as it depends on the compiler IR tree. 253 254To turn on IR transformation set these properties in your `gradle.properties` file: 255 256<details open> 257<summary>For Kotlin >= 1.7.20</summary> 258 259```groovy 260kotlinx.atomicfu.enableJvmIrTransformation=true // for JVM IR transformation 261kotlinx.atomicfu.enableJsIrTransformation=true // for JS IR transformation 262``` 263 264</details> 265 266<details> 267 268 269<summary> For Kotlin >= 1.6.20 and Kotlin < 1.7.20</summary> 270 271```groovy 272kotlinx.atomicfu.enableIrTransformation=true // only JS IR transformation is supported 273``` 274 275</details> 276 277Also for JS backend make sure that `ir` or `both` compiler mode is set: 278 279```groovy 280kotlin.js.compiler=ir // or both 281``` 282 283 284## Options for post-compilation transformation 285 286Some configuration options are available for _post-compilation transform tasks_ on JVM and JS. 287 288To set configuration options you should create `atomicfu` section in a `build.gradle` file, 289like this: 290```groovy 291atomicfu { 292 dependenciesVersion = '0.18.5' 293} 294``` 295 296### JVM options 297 298To turn off transformation for Kotlin/JVM set option `transformJvm` to `false`. 299 300Configuration option `jvmVariant` defines the Java class that replaces atomics during bytecode transformation. 301Here are the valid options: 302- `FU` – atomics are replaced with [AtomicXxxFieldUpdater](https://docs.oracle.com/javase/10/docs/api/java/util/concurrent/atomic/AtomicIntegerFieldUpdater.html). 303- `VH` – atomics are replaced with [VarHandle](https://docs.oracle.com/javase/9/docs/api/java/lang/invoke/VarHandle.html), 304 this option is supported for JDK 9+. 305- `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+. 306 307### JS options 308 309To turn off transformation for Kotlin/JS set option `transformJs` to `false`. 310 311Here are all available configuration options (with their defaults): 312```groovy 313atomicfu { 314 dependenciesVersion = '0.18.5' // set to null to turn-off auto dependencies 315 transformJvm = true // set to false to turn off JVM transformation 316 jvmVariant = "FU" // JVM transformation variant: FU,VH, or BOTH 317 transformJs = true // set to false to turn off JVM transformation 318} 319``` 320 321## More features 322 323AtomicFU provides some additional features that you can use. 324 325### Arrays of atomic values 326 327You can declare arrays of all supported atomic value types. 328By default arrays are transformed into the corresponding `java.util.concurrent.atomic.Atomic*Array` instances. 329 330If you configure `variant = "VH"` an array will be transformed to plain array using 331[VarHandle](https://docs.oracle.com/javase/9/docs/api/java/lang/invoke/VarHandle.html) to support atomic operations. 332 333```kotlin 334val a = atomicArrayOfNulls<T>(size) // similar to Array constructor 335 336val x = a[i].value // read value 337a[i].value = x // set value 338a[i].compareAndSet(expect, update) // do atomic operations 339``` 340 341### User-defined extensions on atomics 342 343You can define you own extension functions on `AtomicXxx` types but they must be `inline` and they cannot 344be public and be used outside of the module they are defined in. For example: 345 346```kotlin 347@Suppress("NOTHING_TO_INLINE") 348private inline fun AtomicBoolean.tryAcquire(): Boolean = compareAndSet(false, true) 349``` 350 351### Locks 352 353This project includes `kotlinx.atomicfu.locks` package providing multiplatform locking primitives that 354require no additional runtime dependencies on Kotlin/JVM and Kotlin/JS with a library implementation for 355Kotlin/Native. 356 357* `SynchronizedObject` is designed for inheritance. You write `class MyClass : SynchronizedObject()` and then 358use `synchronized(instance) { ... }` extension function similarly to the 359[synchronized](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/synchronized.html) 360function from the standard library that is available for JVM. The `SynchronizedObject` superclass gets erased 361(transformed to `Any`) on JVM and JS, with `synchronized` leaving no trace in the code on JS and getting 362replaced with built-in monitors for locking on JVM. 363 364* `ReentrantLock` is designed for delegation. You write `val lock = reentrantLock()` to construct its instance and 365use `lock`/`tryLock`/`unlock` functions or `lock.withLock { ... }` extension function similarly to the way 366[jucl.ReentrantLock](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantLock.html) 367is used on JVM. On JVM it is a typealias to the later class, erased on JS. 368 369> Note that package `kotlinx.atomicfu.locks` is experimental explicitly even while atomicfu is experimental itself, 370> meaning that no ABI guarantees are provided whatsoever. API from this package is not recommended to use in libraries 371> that other projects depend on. 372 373### Tracing operations 374 375You can debug your tests tracing atomic operations with a special trace object: 376 377```kotlin 378private val trace = Trace() 379private val current = atomic(0, trace) 380 381fun update(x: Int): Int { 382 // custom trace message 383 trace { "calling update($x)" } 384 // automatic tracing of modification operations 385 return current.getAndAdd(x) 386} 387``` 388 389All trace messages are stored in a cyclic array inside `trace`. 390 391You can optionally set the size of trace's message array and format function. For example, 392you can add a current thread name to the traced messages: 393 394```kotlin 395private val trace = Trace(size = 64) { 396 index, // index of a trace message 397 text // text passed when invoking trace { text } 398 -> "$index: [${Thread.currentThread().name}] $text" 399} 400``` 401 402`trace` is only seen before transformation and completely erased after on Kotlin/JVM and Kotlin/JS. 403 404## Kotlin Native support 405 406Atomic references for Kotlin/Native are based on 407[FreezableAtomicReference](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/-freezable-atomic-reference/-init-.html) 408and every reference that is stored to the previously 409[frozen](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/freeze.html) 410(shared with another thread) atomic is automatically frozen, too. 411 412Since Kotlin/Native does not generally provide binary compatibility between versions, 413you should use the same version of Kotlin compiler as was used to build AtomicFU. 414See [gradle.properties](gradle.properties) in AtomicFU project for its `kotlin_version`. 415 416