Skip to content
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

Back-port the Apple lock (without the Apple-specific qos parts) to other Posix platforms. #517

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -1,31 +1,51 @@
/*
* Copyright 2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.atomicfu.locks

import kotlinx.cinterop.*
import kotlinx.cinterop.Arena
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.alloc
import kotlinx.cinterop.ptr
import platform.posix.*
import kotlin.concurrent.Volatile

public actual class NativeMutexNode {
@OptIn(ExperimentalForeignApi::class)
actual class NativeMutexNode {
actual var next: NativeMutexNode? = null

@Volatile
private var isLocked = false
private val pMutex = nativeHeap.alloc<pthread_mutex_t>().apply { pthread_mutex_init(ptr, null) }
private val pCond = nativeHeap.alloc<pthread_cond_t>().apply { pthread_cond_init(ptr, null) }
private val arena: Arena = Arena()
private val cond: pthread_cond_t = arena.alloc()
private val mutex: pthread_mutex_t = arena.alloc()
private val attr: pthread_mutexattr_tVar = arena.alloc()

internal actual var next: NativeMutexNode? = null

init {
require(pthread_cond_init(cond.ptr, null) == 0)
require(pthread_mutexattr_init(attr.ptr) == 0)
require(pthread_mutexattr_settype(attr.ptr, PTHREAD_MUTEX_ERRORCHECK.toInt()) == 0)
require(pthread_mutex_init(mutex.ptr, attr.ptr) == 0)
}

actual fun lock() {
pthread_mutex_lock(pMutex.ptr)
while (isLocked) { // wait till locked are available
pthread_cond_wait(pCond.ptr, pMutex.ptr)
}
isLocked = true
pthread_mutex_unlock(pMutex.ptr)
pthread_mutex_lock(mutex.ptr)
}

actual fun unlock() {
pthread_mutex_unlock(mutex.ptr)
}

internal actual fun wait(lockOwner: Long) {
pthread_cond_wait(cond.ptr, mutex.ptr)
}

internal actual fun notify() {
pthread_cond_signal(cond.ptr)
}

actual fun unlock() {
pthread_mutex_lock(pMutex.ptr)
isLocked = false
pthread_cond_broadcast(pCond.ptr)
pthread_mutex_unlock(pMutex.ptr)
internal actual fun dispose() {
pthread_cond_destroy(cond.ptr)
pthread_mutex_destroy(mutex.ptr)
pthread_mutexattr_destroy(attr.ptr)
arena.clear()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,31 +1,50 @@
/*
* Copyright 2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.atomicfu.locks

import kotlinx.cinterop.*
import kotlinx.cinterop.Arena
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.alloc
import kotlinx.cinterop.ptr
import platform.posix.*
import kotlin.concurrent.Volatile

public actual class NativeMutexNode {
@OptIn(ExperimentalForeignApi::class)
actual class NativeMutexNode {
actual var next: NativeMutexNode? = null

@Volatile
private var isLocked = false
private val pMutex = nativeHeap.alloc<pthread_mutex_t>().apply { pthread_mutex_init(ptr, null) }
private val pCond = nativeHeap.alloc<pthread_cond_t>().apply { pthread_cond_init(ptr, null) }
private val arena: Arena = Arena()
private val cond: pthread_cond_t = arena.alloc()
private val mutex: pthread_mutex_t = arena.alloc()
private val attr: pthread_mutexattr_tVar = arena.alloc()

internal actual var next: NativeMutexNode? = null
init {
require(pthread_cond_init(cond.ptr, null) == 0)
require(pthread_mutexattr_init(attr.ptr) == 0)
require(pthread_mutexattr_settype(attr.ptr, PTHREAD_MUTEX_ERRORCHECK.toInt()) == 0)
require(pthread_mutex_init(mutex.ptr, attr.ptr) == 0)
}

actual fun lock() {
pthread_mutex_lock(pMutex.ptr)
while (isLocked) { // wait till locked are available
pthread_cond_wait(pCond.ptr, pMutex.ptr)
}
isLocked = true
pthread_mutex_unlock(pMutex.ptr)
pthread_mutex_lock(mutex.ptr)
}

actual fun unlock() {
pthread_mutex_unlock(mutex.ptr)
}

internal actual fun wait(lockOwner: Long) {
pthread_cond_wait(cond.ptr, mutex.ptr)
}

internal actual fun notify() {
pthread_cond_signal(cond.ptr)
}

actual fun unlock() {
pthread_mutex_lock(pMutex.ptr)
isLocked = false
pthread_cond_broadcast(pCond.ptr)
pthread_mutex_unlock(pMutex.ptr)
internal actual fun dispose() {
pthread_cond_destroy(cond.ptr)
pthread_mutex_destroy(mutex.ptr)
pthread_mutexattr_destroy(attr.ptr)
arena.clear()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import kotlinx.cinterop.alloc
import kotlinx.cinterop.ptr
import kotlinx.cinterop.IntVar
import kotlinx.cinterop.UIntVar
import kotlinx.cinterop.toCPointer
import kotlinx.cinterop.value
import platform.posix.pthread_cond_destroy
import platform.posix.pthread_cond_init
import platform.posix.pthread_cond_signal
Expand All @@ -23,21 +25,28 @@ import platform.posix.pthread_mutexattr_destroy
import platform.posix.pthread_mutexattr_init
import platform.posix.pthread_mutexattr_settype
import platform.posix.pthread_mutexattr_t
import platform.posix.pthread_get_qos_class_np
import platform.posix.pthread_override_t
import platform.posix.pthread_override_qos_class_end_np
import platform.posix.pthread_override_qos_class_start_np
import platform.posix.qos_class_self

import platform.posix.PTHREAD_MUTEX_ERRORCHECK

@OptIn(ExperimentalForeignApi::class)
internal class NativeMutexNode {
var next: NativeMutexNode? = null
actual class NativeMutexNode {
actual var next: NativeMutexNode? = null

private val arena: Arena = Arena()
private val cond: pthread_cond_t = arena.alloc()
private val mutex: pthread_mutex_t = arena.alloc()
private val attr: pthread_mutexattr_t = arena.alloc()
private var qosOverride: pthread_override_t? = null
private var qosOverrideQosClass: UInt = 0U

// Used locally as return parameters in donateQos
val lockOwnerQosClass = arena.alloc<UIntVar>()
val lockOwnerRelPrio = arena.alloc<IntVar>()
private val lockOwnerQosClass = arena.alloc<UIntVar>()
private val lockOwnerRelPrio = arena.alloc<IntVar>()

init {
require(pthread_cond_init(cond.ptr, null) == 0)
Expand All @@ -46,15 +55,55 @@ internal class NativeMutexNode {
require(pthread_mutex_init(mutex.ptr, attr.ptr) == 0)
}

fun lock() = require(pthread_mutex_lock(mutex.ptr) == 0)
actual fun lock() {
pthread_mutex_lock(mutex.ptr)
}

actual fun unlock() {
pthread_mutex_unlock(mutex.ptr)
}

internal actual fun notify() {
pthread_cond_signal(cond.ptr)
}

fun unlock() = require(pthread_mutex_unlock(mutex.ptr) == 0)
internal actual fun wait(lockOwner: Long) {
donateQos(lockOwner)
require(pthread_cond_wait(cond.ptr, mutex.ptr) == 0)
clearDonation()
}

fun wait() = require(pthread_cond_wait(cond.ptr, mutex.ptr) == 0)
private fun donateQos(lockOwner: Long) {
if (lockOwner == NO_OWNER) {
return
}
val ourQosClass = qos_class_self()
// Set up a new override if required:
if (qosOverride != null) {
// There is an existing override, but we need to go higher.
if (ourQosClass > qosOverrideQosClass) {
pthread_override_qos_class_end_np(qosOverride)
qosOverride = pthread_override_qos_class_start_np(lockOwner.toCPointer(), qos_class_self(), 0)
qosOverrideQosClass = ourQosClass
}
} else {
// No existing override, check if we need to set one up.
pthread_get_qos_class_np(lockOwner.toCPointer(), lockOwnerQosClass.ptr, lockOwnerRelPrio.ptr)
if (ourQosClass > lockOwnerQosClass.value) {
qosOverride = pthread_override_qos_class_start_np(lockOwner.toCPointer(), ourQosClass, 0)
qosOverrideQosClass = ourQosClass
}
}
}

fun notify() = require(pthread_cond_signal(cond.ptr) == 0)
private fun clearDonation() {
if (qosOverride != null) {
pthread_override_qos_class_end_np(qosOverride)
qosOverride = null
}
}

fun dispose() {
internal actual fun dispose() {
pthread_cond_destroy(cond.ptr)
pthread_mutex_destroy(mutex.ptr)
pthread_mutexattr_destroy(attr.ptr)
Expand Down
10 changes: 10 additions & 0 deletions atomicfu/src/appleMain/kotlin/kotlinx/atomicfu/locks/ThreadId.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright 2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.atomicfu.locks

import kotlinx.cinterop.toLong
import platform.posix.pthread_self

internal actual fun createThreadId() = pthread_self().toLong()
Original file line number Diff line number Diff line change
@@ -1,31 +1,50 @@
/*
* Copyright 2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.atomicfu.locks

import kotlinx.cinterop.*
import kotlinx.cinterop.Arena
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.alloc
import kotlinx.cinterop.ptr
import platform.posix.*
import kotlin.concurrent.Volatile

public actual class NativeMutexNode {
@OptIn(ExperimentalForeignApi::class)
actual class NativeMutexNode {
actual var next: NativeMutexNode? = null

@Volatile
private var isLocked = false
private val pMutex = nativeHeap.alloc<pthread_mutex_tVar>().apply { pthread_mutex_init(ptr, null) }
private val pCond = nativeHeap.alloc<pthread_cond_tVar>().apply { pthread_cond_init(ptr, null) }
private val arena: Arena = Arena()
private val cond: pthread_cond_tVar = arena.alloc()
private val mutex: pthread_mutex_tVar = arena.alloc()
private val attr: pthread_mutexattr_tVar = arena.alloc()

internal actual var next: NativeMutexNode? = null
init {
require(pthread_cond_init(cond.ptr, null) == 0)
require(pthread_mutexattr_init(attr.ptr) == 0)
require(pthread_mutexattr_settype(attr.ptr, PTHREAD_MUTEX_ERRORCHECK) == 0)
require(pthread_mutex_init(mutex.ptr, attr.ptr) == 0)
}

actual fun lock() {
pthread_mutex_lock(pMutex.ptr)
while (isLocked) { // wait till locked are available
pthread_cond_wait(pCond.ptr, pMutex.ptr)
}
isLocked = true
pthread_mutex_unlock(pMutex.ptr)
pthread_mutex_lock(mutex.ptr)
}

actual fun unlock() {
pthread_mutex_unlock(mutex.ptr)
}

internal actual fun wait(lockOwner: Long) {
pthread_cond_wait(cond.ptr, mutex.ptr)
}

internal actual fun notify() {
pthread_cond_signal(cond.ptr)
}

actual fun unlock() {
pthread_mutex_lock(pMutex.ptr)
isLocked = false
pthread_cond_broadcast(pCond.ptr)
pthread_mutex_unlock(pMutex.ptr)
internal actual fun dispose() {
pthread_cond_destroy(cond.ptr)
pthread_mutex_destroy(mutex.ptr)
pthread_mutexattr_destroy(attr.ptr)
arena.clear()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.atomicfu.locks


public expect class NativeMutexNode() {

internal var next: NativeMutexNode?

public fun lock()

public fun unlock()

// The lockOwner is used for qos donation on iOS
internal fun wait(lockOwner: Long)

internal fun notify()

internal fun dispose()
}
Loading