kotlinx-atomicfu

repository·master·Indexed 22 days ago

https://github.com/kotlin/kotlinx-atomicfu

A multiplatform Kotlin library providing an idiomatic API for atomic operations. It uses a compiler plugin to transform high-level atomic code into efficient, platform-specific implementations, such as AtomicFieldUpdater on the JVM, unboxed values on JS, and atomic intrinsics on Native. The library supports lock-free logic via compareAndSet and update functions, as well as experimental multiplatform locking primitives like SynchronizedObject and ReentrantLock.

Tokens
3.4K
Snippets
11
Records
15
Agent score
28%

What's inside kotlinx-atomicfu

  1. Use kotlinx.atomicfu for shared atomic operations in Kotlin/JS

    master

    kotlinx.atomicfu provides an idiomatic way to perform atomic operations in Kotlin/JS. Its primary benefit is enabling code sharing between Kotlin/JS and Kotlin/JVM, allowing you to write atomic logic once and use it across different platforms.

    private val top = atomic<Node?>(null)
  2. How AtomicFU works across platforms

    master

    AtomicFU provides an idiomatic Kotlin API that is transformed into efficient low-level operations depending on the target platform:

    • JVM: Atomic values are represented as plain values and updated using java.util.concurrent.atomic.AtomicXxxFieldUpdater from the Java standard library.
    • JS: Atomic values are represented as plain values (unboxed).
    • Native: Atomic operations are delegated to Kotlin/Native atomic intrinsics.
    • Wasm: Atomic values remain boxed, and the kotlinx-atomicfu library is used as a runtime dependency (no IR transformation).
  3. Usage constraints for AtomicFU

    master

    To ensure the compiler plugin can correctly transform your code into efficient atomic operations, follow these constraints:

    1. Encapsulation: Declare atomic variables as private val or internal val. Do not allow direct access to the atomic variable itself outside of your Kotlin module. To expose the value, use a delegated property:
      private val _foo = atomic<T>(initial)
      public var foo: T by _foo
    2. No Leaking References: Do not read references of atomic variables into local variables (e.g., val tmp = top is forbidden) or leak them via return values or parameters.
    3. Simple Operations Only: Avoid complex data flow in parameters. Expressions like top.value = complex_expression or top.compareAndSet(cur, complex_expression) are not supported if the expression contains branches. Extract the expression into a local variable first.
    4. Direct Access: Only simple operations performed directly on the atomic variable are supported.
  4. Configure AtomicFU for Maven

    master

    Maven configuration is supported for JVM projects. You must declare the version, provide the dependency with provided scope, and configure build steps to transform the classes.

    1. Declare version and dependency

    <properties>
         <atomicfu.version>0.33.0</atomicfu.version>
    </properties> 
    
    <dependencies>
        <dependency>
            <groupId>org.jetbrains.kotlinx</groupId>
            <artifactId>atomicfu</artifactId>
            <version>${atomicfu.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    2. Configure Build Steps

    Configure the kotlin-maven-plugin to output to a staging directory (classes-pre-atomicfu), then use the atomicfu-maven-plugin to transform those classes.

    <build>
      <plugins>
        <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>
        <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>
  5. Apply the AtomicFU Gradle plugin

    master

    To use AtomicFU in a Gradle project, add the plugin to your top-level build file. The plugin ID is org.jetbrains.kotlinx.atomicfu.

    Kotlin DSL

    plugins {
         id("org.jetbrains.kotlinx.atomicfu") version "0.33.0"
    }

    Groovy DSL

    plugins {
        id 'org.jetbrains.kotlinx.atomicfu' version '0.33.0'
    }

    Legacy Application

    If you are using the buildscript approach:

    Kotlin:

    buildscript {
      repositories {
        mavenCentral()
      }
      dependencies {
        classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.33.0")
      }
    }
    apply(plugin = "org.jetbrains.kotlinx.atomicfu")

    Groovy:

    buildscript {
        repositories {
            mavenCentral()
        }
        dependencies {
            classpath 'org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.33.0'
        }
    }
    apply plugin: 'org.jetbrains.kotlinx.atomicfu'
    plugins {
         id("org.jetbrains.kotlinx.atomicfu") version "0.33.0"
    }
  6. Requirements for AtomicFU

    master

    To use the current version of the AtomicFU Gradle plugin, ensure your project meets the following requirements:

    • Gradle: 8.2 or newer
    • Kotlin: 2.2.0 or newer

    Note on Kotlin version: The plugin relies on the Kotlin Gradle Plugin (KGP) version present in your project. If you use a custom Kotlin compiler version or modify the Kotlin Native compiler version via the kotlin.native.version property, be aware of this dependency.

  7. Enable Gradle Build Scans for AtomicFU

    master

    To automatically upload AtomicFU build reports to a Gradle Develocity server, add the following to your $GRADLE_USER_HOME/gradle.properties file:

    org.jetbrains.atomicfu.build.scan.enabled=true
    # optionally provide a username that will be attached to each report
    org.jetbrains.atomicfu.build.scan.username=John Wick
    org.jetbrains.atomicfu.build.scan.enabled=true
    org.jetbrains.atomicfu.build.scan.username=John Wick
  8. Configure post-compilation transformation options

    master

    You can configure how AtomicFU transforms atomics on the JVM and JS by adding an atomicfu block to your build.gradle file. This is useful for controlling which Java classes replace atomics during bytecode transformation.

    Available Configuration Options:

    • dependenciesVersion: The version of AtomicFU dependencies to use (set to null to turn off auto dependencies).
    • transformJvm: Boolean flag to enable or disable JVM transformation (defaults to true).
    • jvmVariant: Defines the replacement strategy for JVM atomics:
      • FU: Uses AtomicXxxFieldUpdater.
      • VH: Uses VarHandle (requires JDK 9+).
      • BOTH: Creates a multi-release JAR containing both FU (for JDK <= 8) and VH (for JDK 9+).

    Note for JS users: The transformJs flag is disabled by default and has no effect since version 0.26.0. You should remove it from your configuration.

    atomicfu {
      dependenciesVersion = '0.33.0'
      transformJvm = true
      jvmVariant = "FU"
    }
  9. Enable AtomicFU IR transformations

    master

    To enable the compiler plugin's IR transformations for specific backends, add the following properties to your gradle.properties file:

    kotlinx.atomicfu.enableJvmIrTransformation=true
    kotlinx.atomicfu.enableNativeIrTransformation=true
    kotlinx.atomicfu.enableJsIrTransformation=true

    Note: These properties are required for Kotlin version >= 1.9.0. For JS backend, ensure the compiler mode is set to ir or both (kotlin.js.compiler=ir).

    kotlinx.atomicfu.enableJvmIrTransformation=true
    kotlinx.atomicfu.enableNativeIrTransformation=true
    kotlinx.atomicfu.enableJsIrTransformation=true
  10. Use AtomicFU atomic operations

    master

    AtomicFU allows you to declare atomic variables and perform lock-free operations using idiomatic Kotlin syntax.

    Declaration

    Use atomic(initialValue) to declare an atomic variable. For integers and longs, type inference works automatically:

    import kotlinx.atomicfu.*
    
    private val top = atomic<Node?>(null) // Atomic reference
    val myInt = atomic(0)               // Atomic integer
    val myLong = atomic(0L)              // Atomic long

    Volatile Reads and Writes

    Use the .value property for volatile access:

    fun isEmpty() = top.value == null  // volatile read
    fun clear() { top.value = null }   // volatile write

    Atomic Updates

    For lock-free logic, use compareAndSet or higher-level extension functions like update, updateAndGet, and getAndUpdate:

    // Direct CAS
    if (top.compareAndSet(expect, update)) { ... }
    
    // Idiomatic updates
    fun push(v: Value) = top.update { cur -> Node(v, cur) }
    fun pop(): Value? = top.getAndUpdate { cur -> cur?.next }?.value
    
    // Looping primitive
    top.loop { cur -> 
       // while(true) loop that volatile-reads current value
    }

    Integer/Long Operations

    Atomic integers and longs support standard operations like getAndIncrement(), incrementAndGet(), getAndAdd(), etc., as well as += and -= operators.

    import kotlinx.atomicfu.*
    
    private val top = atomic<Node?>(null) 
    
    fun isEmpty() = top.value == null
    fun clear() { top.value = null }
    
    if (top.compareAndSet(expect, update)) { /* ... */ }
    
    top.loop { cur ->
       // while(true) loop that volatile-reads current value
    }
    
    fun push(v: Value) = top.update { cur -> Node(v, cur) }
    fun pop(): Value? = top.getAndUpdate { cur -> cur?.next }?.value
    
    val myInt = atomic(0)
    val myLong = atomic(0L)
  11. Use arrays of atomic values

    master

    AtomicFU allows you to declare arrays of supported atomic types. By default, these are transformed into java.util.concurrent.atomic.Atomic*Array instances on the JVM. If you set jvmVariant = "VH", they are transformed into plain arrays using VarHandle for atomic operations.

    Use atomicArrayOfNulls<T>(size) to initialize an atomic array.

    val a = atomicArrayOfNulls<T>(size) // similar to Array constructor
    
    val x = a[i].value // read value
    a[i].value = x // set value
    a[i].compareAndSet(expect, update) // do atomic operations
  12. Define user-defined extensions on atomics

    master

    You can create custom extension functions for AtomicXxx types. To ensure they work correctly with the transformation process, they must be:

    1. Marked as inline.
    2. Not public (they cannot be used outside the module they are defined in).
    @Suppress("NOTHING_TO_INLINE")
    private inline fun AtomicBoolean.tryAcquire(): Boolean = compareAndSet(false, true)