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