Skip to content

Mutex for Kotlin/Common #508

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions atomicfu/api/atomicfu.api
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,15 @@ public final class kotlinx/atomicfu/locks/ParkingSupport {
public final fun unpark (Ljava/lang/Thread;)V
}

public final class kotlinx/atomicfu/locks/SynchronousMutex {
public fun <init> ()V
public final fun lock ()V
public final fun tryLock ()Z
public final fun tryLock-LRDsOJo (J)Z
public final fun unlock ()V
}

public final class kotlinx/atomicfu/locks/SynchronousMutexKt {
public static final fun withLock (Lkotlinx/atomicfu/locks/SynchronousMutex;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
}

18 changes: 10 additions & 8 deletions atomicfu/api/atomicfu.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ final class <#A: kotlin/Any?> kotlinx.atomicfu/AtomicRef { // kotlinx.atomicfu/A
final fun compareAndSet(#A, #A): kotlin/Boolean // kotlinx.atomicfu/AtomicRef.compareAndSet|compareAndSet(1:0;1:0){}[0]
}

final class kotlinx.atomicfu.locks/SynchronousMutex { // kotlinx.atomicfu.locks/SynchronousMutex|null[0]
constructor <init>() // kotlinx.atomicfu.locks/SynchronousMutex.<init>|<init>(){}[0]

final fun lock() // kotlinx.atomicfu.locks/SynchronousMutex.lock|lock(){}[0]
final fun tryLock(): kotlin/Boolean // kotlinx.atomicfu.locks/SynchronousMutex.tryLock|tryLock(){}[0]
final fun tryLock(kotlin.time/Duration): kotlin/Boolean // kotlinx.atomicfu.locks/SynchronousMutex.tryLock|tryLock(kotlin.time.Duration){}[0]
final fun unlock() // kotlinx.atomicfu.locks/SynchronousMutex.unlock|unlock(){}[0]
}

final class kotlinx.atomicfu/AtomicBoolean { // kotlinx.atomicfu/AtomicBoolean|null[0]
final var value // kotlinx.atomicfu/AtomicBoolean.value|{}value[0]
// Targets: [native]
Expand Down Expand Up @@ -278,6 +287,7 @@ final inline fun (kotlinx.atomicfu/AtomicLong).kotlinx.atomicfu/getAndUpdate(kot
final inline fun (kotlinx.atomicfu/AtomicLong).kotlinx.atomicfu/loop(kotlin/Function1<kotlin/Long, kotlin/Unit>): kotlin/Nothing // kotlinx.atomicfu/loop|loop@kotlinx.atomicfu.AtomicLong(kotlin.Function1<kotlin.Long,kotlin.Unit>){}[0]
final inline fun (kotlinx.atomicfu/AtomicLong).kotlinx.atomicfu/update(kotlin/Function1<kotlin/Long, kotlin/Long>) // kotlinx.atomicfu/update|update@kotlinx.atomicfu.AtomicLong(kotlin.Function1<kotlin.Long,kotlin.Long>){}[0]
final inline fun (kotlinx.atomicfu/AtomicLong).kotlinx.atomicfu/updateAndGet(kotlin/Function1<kotlin/Long, kotlin/Long>): kotlin/Long // kotlinx.atomicfu/updateAndGet|updateAndGet@kotlinx.atomicfu.AtomicLong(kotlin.Function1<kotlin.Long,kotlin.Long>){}[0]
final inline fun <#A: kotlin/Any?> (kotlinx.atomicfu.locks/SynchronousMutex).kotlinx.atomicfu.locks/withLock(kotlin/Function0<#A>): #A // kotlinx.atomicfu.locks/withLock|withLock@kotlinx.atomicfu.locks.SynchronousMutex(kotlin.Function0<0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?> (kotlinx.atomicfu/AtomicRef<#A>).kotlinx.atomicfu/getAndUpdate(kotlin/Function1<#A, #A>): #A // kotlinx.atomicfu/getAndUpdate|getAndUpdate@kotlinx.atomicfu.AtomicRef<0:0>(kotlin.Function1<0:0,0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?> (kotlinx.atomicfu/AtomicRef<#A>).kotlinx.atomicfu/loop(kotlin/Function1<#A, kotlin/Unit>): kotlin/Nothing // kotlinx.atomicfu/loop|loop@kotlinx.atomicfu.AtomicRef<0:0>(kotlin.Function1<0:0,kotlin.Unit>){0§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?> (kotlinx.atomicfu/AtomicRef<#A>).kotlinx.atomicfu/update(kotlin/Function1<#A, #A>) // kotlinx.atomicfu/update|update@kotlinx.atomicfu.AtomicRef<0:0>(kotlin.Function1<0:0,0:0>){0§<kotlin.Any?>}[0]
Expand All @@ -297,14 +307,6 @@ open annotation class kotlinx.atomicfu.locks/ExperimentalThreadBlockingApi : kot
constructor <init>() // kotlinx.atomicfu.locks/ExperimentalThreadBlockingApi.<init>|<init>(){}[0]
}

// Targets: [native]
final class kotlinx.atomicfu.locks/NativeMutexNode { // kotlinx.atomicfu.locks/NativeMutexNode|null[0]
constructor <init>() // kotlinx.atomicfu.locks/NativeMutexNode.<init>|<init>(){}[0]

final fun lock() // kotlinx.atomicfu.locks/NativeMutexNode.lock|lock(){}[0]
final fun unlock() // kotlinx.atomicfu.locks/NativeMutexNode.unlock|unlock(){}[0]
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume removing this implementation detail is safe, but cc @fzhinkin: he probably knows if this was already used by someone else.


// Targets: [native]
final class kotlinx.atomicfu.locks/ParkingHandle // kotlinx.atomicfu.locks/ParkingHandle|null[0]

Expand Down
20 changes: 17 additions & 3 deletions atomicfu/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ kotlin {

jvmTest {
dependencies {
implementation("org.jetbrains.kotlinx:lincheck:2.39")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-test")
implementation("org.jetbrains.kotlin:kotlin-test-junit")
Expand Down Expand Up @@ -206,7 +207,14 @@ val transformedTestFU_current by tasks.registering(Test::class) {
dependsOn(transformFU)
classpath = files(configurations.getByName("jvmTestRuntimeClasspath"), classesPostTransformFU)
testClassesDirs = project.files(classesPostTransformFU)
exclude("**/*LFTest.*", "**/TraceToStringTest.*", "**/AtomicfuReferenceJsTest.*")
exclude(
"**/*LFTest.*",
"**/TraceToStringTest.*",
"**/AtomicfuReferenceJsTest.*",
"**/NativeMutexLincheckTest.*",
"**/NativeMutexLincheckReentrantTest.*",
"**/NativeMutexTimeoutLincheckTest.*"
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point, maybe it's worth it to put all these tests in a separate directory / adopt a shared naming scheme and exclude it as a whole?

filter { isFailOnNoMatchingTests = false }
launcherForJdk(LAUNCHER_JDK_VERSION)
}
Expand All @@ -220,7 +228,10 @@ val transformedTestBOTH_current by tasks.registering(Test::class) {
"**/TraceToStringTest.*",
"**/TopLevelGeneratedDeclarationsReflectionTest.*",
"**/SyntheticFUFieldsTest.*",
"**/AtomicfuReferenceJsTest.*"
"**/AtomicfuReferenceJsTest.*",
"**/NativeMutexLincheckTest.*",
"**/NativeMutexLincheckReentrantTest.*",
"**/NativeMutexTimeoutLincheckTest.*"
)
filter { isFailOnNoMatchingTests = false }
launcherForJdk(LAUNCHER_JDK_VERSION)
Expand All @@ -235,7 +246,10 @@ val transformedTestVH by tasks.registering(Test::class) {
"**/TraceToStringTest.*",
"**/TopLevelGeneratedDeclarationsReflectionTest.*",
"**/SyntheticFUFieldsTest.*",
"**/AtomicfuReferenceJsTest.*"
"**/AtomicfuReferenceJsTest.*",
"**/NativeMutexLincheckTest.*",
"**/NativeMutexLincheckReentrantTest.*",
"**/NativeMutexTimeoutLincheckTest.*"
)
filter { isFailOnNoMatchingTests = false }

Expand Down

This file was deleted.

This file was deleted.

10 changes: 0 additions & 10 deletions atomicfu/src/appleMain/kotlin/kotlinx/atomicfu/locks/ThreadId.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package kotlinx.atomicfu.locks

import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.time.Duration

/**
* Mutual exclusion for Kotlin Multiplatform.
*
* It can protect a shared resource or critical section from multiple thread accesses.
* Threads can acquire the lock by calling [lock] and release the lock by calling [unlock].
*
* When a thread calls [lock] while another thread is locked, it will suspend until the lock is released.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* When a thread calls [lock] while another thread is locked, it will suspend until the lock is released.
* When a thread calls [lock] while another thread is locked, it will block until the lock is released.

There is an unfortunate clash of terminology: suspend in Kotlin basically always means asynchronous suspensions.

* When multiple threads are waiting for the lock, they will acquire it in a fair order (first in first out).
* On JVM, a [lock] call can skip the queue if it happens in between a thread releasing and the first in queue acquiring.
*
* It is reentrant, meaning the lock holding thread can call [lock] multiple times without suspending.
* To release the lock (after multiple [lock] calls) an equal number of [unlock] calls are required.
*
* This Mutex should not be used in combination with coroutines and `suspend` functions
* as it blocks the waiting thread.
* Use the `Mutex` from the coroutines library instead.
*
* ```Kotlin
* mutex.withLock {
* // Critical section only executed by
* // one thread at a time.
* }
* ```
*/
public expect class SynchronousMutex() {
/**
* Tries to lock this mutex, returning `false` if this mutex is already locked.
*
* It is recommended to use [withLock] for safety reasons, so that the acquired lock is always
* released at the end of your critical section, and [unlock] is never invoked before a successful
* lock acquisition.
*
* (JVM only) this call can potentially skip line.
*/
public fun tryLock(): Boolean

/**
* Tries to lock this mutex within the given [timeout] period,
* returning `false` if the duration passed without locking.
*
* Note: when [tryLock] succeeds the lock needs to be released by [unlock].
* When [tryLock] does not succeed the lock does not have to be released.
*
* (JVM only) throws Interrupted exception when thread is interrupted while waiting for lock.
*/
public fun tryLock(timeout: Duration): Boolean

/**
* Locks the mutex, suspends the thread until the lock is acquired.
*
* It is recommended to use [withLock] for safety reasons, so that the acquired lock is always
* released at the end of your critical section, and [unlock] is never invoked before a successful
* lock acquisition.
*/
public fun lock()

/**
* Releases the lock.
* Throws [IllegalStateException] when the current thread is not holding the lock.
*
* It is recommended to use [withLock] for safety reasons, so that the acquired lock is always
* released at the end of the critical section, and [unlock] is never invoked before a successful
* lock acquisition.
*/
public fun unlock()
}

/**
* Executes the given code [block] under this mutex's lock.
*
* @return result of [block]
*/
@OptIn(ExperimentalContracts::class)
public inline fun <T> SynchronousMutex.withLock(block: () -> T): T {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
lock()
return try {
block()
} finally {
unlock()
}
}
Loading