Skip to content

Mutex for Kotlin Common #494

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

Closed
wants to merge 6 commits into from
Closed
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
17 changes: 17 additions & 0 deletions atomicfu/api/atomicfu.api
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,20 @@ public final class kotlinx/atomicfu/TraceKt {
public static final fun named (Lkotlinx/atomicfu/TraceBase;Ljava/lang/String;)Lkotlinx/atomicfu/TraceBase;
}

public final class kotlinx/atomicfu/locks/Mutex {
public fun <init> ()V
public final fun getReentrantLock ()Ljava/util/concurrent/locks/ReentrantLock;
public final fun isLocked ()Z
public final fun lock ()V
public final fun tryLock ()Z
public final fun unlock ()V
}

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

public final class kotlinx/atomicfu/locks/ThreadIdentifier_jvmKt {
public static final fun currentThreadId ()J
}

161 changes: 96 additions & 65 deletions atomicfu/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ kotlin {
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-test")
implementation("org.jetbrains.kotlin:kotlin-test-junit")
implementation("org.jetbrains.kotlinx:lincheck:2.34")
implementation(libs.junit.junit)
}
}
Expand All @@ -98,82 +99,113 @@ kotlin {

// Support of all non-deprecated targets from the official tier list: https://kotlinlang.org/docs/native-target-support.html
kotlin {
// Tier 1
macosX64()
macosArm64()
iosSimulatorArm64()
iosX64()

// Tier 2
linuxX64()
linuxArm64()
watchosSimulatorArm64()
watchosX64()
watchosArm32()
watchosArm64()
tvosSimulatorArm64()
tvosX64()
tvosArm64()
iosArm64()

// Tier 3
androidNativeArm32()
androidNativeArm64()
androidNativeX86()
androidNativeX64()
mingwX64()
watchosDeviceArm64()
val appleTargets = listOf(
// Tier 1
macosX64(),
macosArm64(),
iosSimulatorArm64(),
iosX64(),

// Tier 2,
watchosSimulatorArm64(),
watchosX64(),
watchosArm32(),
watchosArm64(),
tvosSimulatorArm64(),
tvosX64(),
tvosArm64(),
iosArm64(),

// Tier 3
watchosDeviceArm64(),
)

@Suppress("DEPRECATION") //https://github.com/Kotlin/kotlinx-atomicfu/issues/207
linuxArm32Hfp()

@OptIn(ExperimentalKotlinGradlePluginApi::class)
applyDefaultHierarchyTemplate {
group("native") {
group("nativeUnixLike") {
withLinux()
withApple()
val linuxTargets = listOf(
// Tier 2,
linuxX64(),
linuxArm64(),
)

val androidNativeTargets = listOf(
// Tier 3
androidNativeArm32(),
androidNativeArm64(),
androidNativeX86(),
androidNativeX64(),
)

val windowsTargets = listOf(
mingwX64(),
)

(linuxTargets + androidNativeTargets).forEach {
it.compilations.getByName("main").cinterops {
// This is a hack to fix commonization bug: KT-73136
val ulock by creating {
defFile(project.file("src/nativeInterop/cinterop/stub.def"))
packageName = "stub"
}
}
group("androidNative32Bit") {
withAndroidNativeX86()
withCompilations { compilation ->
(compilation.target as? KotlinNativeTarget)?.konanTarget?.name == "android_arm32"
val posixparking by creating {
defFile(project.file("src/nativeInterop/cinterop/posixparking.def"))
packageName = "platform.posix"
}
}
group("androidNative64Bit") {
withAndroidNativeArm64()
withAndroidNativeX64()
}

}

sourceSets {
val nativeUnixLikeMain by getting {
kotlin.srcDir("src/nativeUnixLikeMain/kotlin")
dependsOn(nativeMain.get())

appleTargets.forEach {
it.compilations.getByName("main").cinterops {
val ulock by creating {
defFile(project.file("src/nativeInterop/cinterop/ulock.def"))
packageName = "platform.darwin.ulock"
includeDirs("${project.rootDir}/atomicfu/src/nativeInterop/cinterop")
}
val posixparking by creating {
defFile(project.file("src/nativeInterop/cinterop/posixparking.def"))
packageName = "platform.posix"
}
}

val androidNative32BitMain by getting {
kotlin.srcDir("src/androidNative32BitMain/kotlin")
dependsOn(nativeMain.get())
}

windowsTargets.forEach {
it.binaries.all {
linkerOpts += "-lSynchronization"
}

val androidNative64BitMain by getting {
kotlin.srcDir("src/androidNative64BitMain/kotlin")
dependsOn(nativeMain.get())
it.compilations.getByName("main").cinterops {
// This is a hack to fix commonization bug: KT-73136
val ulock by creating {
defFile(project.file("src/nativeInterop/cinterop/stub.def"))
packageName = "stub"
}
val posixparking by creating {
defFile(project.file("src/nativeInterop/cinterop/posixparking.def"))
packageName = "platform.posix"
}
}
}

val androidNative32BitTest by getting {
kotlin.srcDir("src/androidNative32BitTest/kotlin")
dependsOn(nativeTest.get())
@Suppress("DEPRECATION") //https://github.com/Kotlin/kotlinx-atomicfu/issues/207
linuxArm32Hfp {
compilations.getByName("main").cinterops {
// This is a hack to fix commonization bug: KT-73136
val ulock by creating {
defFile(project.file("src/nativeInterop/cinterop/stub.def"))
packageName = "stub"
}
val posixparking by creating {
defFile(project.file("src/nativeInterop/cinterop/posixparking.def"))
packageName = "platform.posix"
}
}
}

val androidNative64BitTest by getting {
kotlin.srcDir("src/androidNative64BitTest/kotlin")
dependsOn(nativeTest.get())
}
applyDefaultHierarchyTemplate()
sourceSets {
val linux64Main by creating { dependsOn(nativeMain.get()) }
linuxX64Main.get().dependsOn(linux64Main)
linuxArm64Main.get().dependsOn(linux64Main)

val linux32Main by creating { dependsOn(nativeMain.get()) }
linuxArm32HfpMain.get().dependsOn(linux32Main)
}

// atomicfu-cinterop-interop.klib with an empty interop.def file will still be published for compatibility reasons (see KT-68411)
Expand Down Expand Up @@ -378,4 +410,3 @@ val jvmTest by tasks.getting(Test::class) {
)
// run them only for transformed code
}

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

internal actual object FutexParkingDelegator: ParkingDelegator {
actual override fun createFutexPtr(): Long = PosixParkingDelegator.createFutexPtr()
actual override fun wait(futexPrt: Long): Boolean = PosixParkingDelegator.wait(futexPrt)
actual override fun wake(futexPrt: Long): Int = PosixParkingDelegator.wake(futexPrt)
actual override fun manualDeallocate(futexPrt: Long) = PosixParkingDelegator.manualDeallocate(futexPrt)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package kotlinx.atomicfu.locks

import kotlinx.cinterop.ptr

import kotlinx.cinterop.*
import platform.posix.*
import kotlin.concurrent.*
Expand All @@ -22,7 +24,7 @@ public actual class NativeMutexNode {
pthread_mutex_unlock(pMutex.ptr)
}

actual fun unlock() {
actual fun unlock() {
pthread_mutex_lock(pMutex.ptr)
isLocked = false
pthread_cond_broadcast(pCond.ptr)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package kotlinx.atomicfu.locks

import kotlinx.cinterop.*
import platform.darwin.UInt32
import platform.darwin.UInt64Var
import platform.darwin.ulock.__ulock_wait
import platform.darwin.ulock.__ulock_wake

@OptIn(ExperimentalForeignApi::class)
internal actual object FutexParkingDelegator: ParkingDelegator {
actual override fun createFutexPtr(): Long {
val signal = nativeHeap.alloc<UInt64Var>()
signal.value = 0u
return signal.ptr.toLong()
}

actual override fun wait(futexPrt: Long): Boolean {
val cPointer = futexPrt.toCPointer<UInt64Var>() ?: throw IllegalStateException("Could not create C Pointer from futex ref")
val result = __ulock_wait(UL_COMPARE_AND_WAIT, cPointer, 0u, 0u)
nativeHeap.free(cPointer)
// THere is very little information about ulock so not sure what returned int stands for an interrupt
// In any case it should be 0
return result != 0
}

actual override fun wake(futexPrt: Long): Int {
return __ulock_wake(UL_COMPARE_AND_WAIT, futexPrt.toCPointer<UInt64Var>(), 0u)
}

actual override fun manualDeallocate(futexPrt: Long) {
val cPointer = futexPrt.toCPointer<UInt64Var>() ?: throw IllegalStateException("Could not create C Pointer from futex ref")
nativeHeap.free(cPointer)
}

private const val UL_COMPARE_AND_WAIT: UInt32 = 1u
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package kotlinx.atomicfu.locks

import kotlinx.cinterop.ptr

import kotlinx.cinterop.*
import platform.posix.*
import kotlin.concurrent.Volatile
import kotlin.concurrent.*

public actual class NativeMutexNode {

Expand All @@ -22,7 +24,7 @@ public actual class NativeMutexNode {
pthread_mutex_unlock(pMutex.ptr)
}

actual fun unlock() {
actual fun unlock() {
pthread_mutex_lock(pMutex.ptr)
isLocked = false
pthread_cond_broadcast(pCond.ptr)
Expand Down
30 changes: 30 additions & 0 deletions atomicfu/src/commonMain/kotlin/kotlinx/atomicfu/locks/Mutex.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package kotlinx.atomicfu.locks

import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

/**
* Multiplatform mutex.
* On native based on futex(-like) system calls.
* On JVM delegates to ReentrantLock.
*/
expect class Mutex() {
fun isLocked(): Boolean
fun tryLock(): Boolean
fun lock()
fun unlock()
}
@OptIn(ExperimentalContracts::class)
fun <T> Mutex.withLock(block: () -> T): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
lock()
return try {
block()
} finally {
unlock()
}
}

Loading