-
Notifications
You must be signed in to change notification settings - Fork 65
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
base: develop
Are you sure you want to change the base?
Changes from all commits
e1bd019
a4d1a3b
00187e5
2c11bc5
464a124
7a35ef7
2ace4c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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") | ||
|
@@ -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.*" | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
} | ||
|
@@ -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) | ||
|
@@ -235,7 +246,10 @@ val transformedTestVH by tasks.registering(Test::class) { | |
"**/TraceToStringTest.*", | ||
"**/TopLevelGeneratedDeclarationsReflectionTest.*", | ||
"**/SyntheticFUFieldsTest.*", | ||
"**/AtomicfuReferenceJsTest.*" | ||
"**/AtomicfuReferenceJsTest.*", | ||
"**/NativeMutexLincheckTest.*", | ||
"**/NativeMutexLincheckReentrantTest.*", | ||
"**/NativeMutexTimeoutLincheckTest.*" | ||
) | ||
filter { isFailOnNoMatchingTests = false } | ||
|
||
|
This file was deleted.
This file was deleted.
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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There is an unfortunate clash of terminology: |
||||||
* 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() | ||||||
} | ||||||
} |
There was a problem hiding this comment.
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.