Skip to content

Commit c7df10f

Browse files
committed
Thread parking for kotlin/common
1 parent 5e72de0 commit c7df10f

File tree

26 files changed

+1439
-103
lines changed

26 files changed

+1439
-103
lines changed

atomicfu/api/atomicfu.api

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,11 @@ public final class kotlinx/atomicfu/TraceKt {
135135
public static final fun named (Lkotlinx/atomicfu/TraceBase;Ljava/lang/String;)Lkotlinx/atomicfu/TraceBase;
136136
}
137137

138+
public final class kotlinx/atomicfu/locks/ParkingSupport {
139+
public static final field INSTANCE Lkotlinx/atomicfu/locks/ParkingSupport;
140+
public final fun currentThreadHandle ()Ljava/lang/Thread;
141+
public final fun park-LRDsOJo (J)V
142+
public final fun parkUntil (Lkotlin/time/TimeMark;)V
143+
public final fun unpark (Ljava/lang/Thread;)V
144+
}
145+

atomicfu/build.gradle.kts

Lines changed: 55 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,35 @@ plugins {
1010

1111
kotlin {
1212
jvmToolchain(8)
13+
14+
// Tier 1
15+
macosX64()
16+
macosArm64()
17+
iosSimulatorArm64()
18+
iosX64()
19+
20+
// Tier 2
21+
linuxX64()
22+
linuxArm64()
23+
watchosSimulatorArm64()
24+
watchosX64()
25+
watchosArm32()
26+
watchosArm64()
27+
tvosSimulatorArm64()
28+
tvosX64()
29+
tvosArm64()
30+
iosArm64()
31+
32+
// Tier 3
33+
androidNativeArm32()
34+
androidNativeArm64()
35+
androidNativeX86()
36+
androidNativeX64()
37+
mingwX64()
38+
watchosDeviceArm64()
39+
40+
@Suppress("DEPRECATION") //https://github.com/Kotlin/kotlinx-atomicfu/issues/207
41+
linuxArm32Hfp()
1342

1443
// JS -- always
1544
js(IR) {
@@ -34,6 +63,31 @@ kotlin {
3463
nodejs()
3564
}
3665

66+
@OptIn(ExperimentalKotlinGradlePluginApi::class)
67+
applyDefaultHierarchyTemplate {
68+
common {
69+
group("jsAndWasmShared") {
70+
withJs()
71+
withWasmJs()
72+
withWasmWasi()
73+
}
74+
group("concurrent") {
75+
withJvm()
76+
group("native") {
77+
group("nativeUnixLike") {
78+
group("apple") {
79+
withApple()
80+
}
81+
group("linux") {
82+
withLinux()
83+
}
84+
}
85+
withMingwX64()
86+
}
87+
}
88+
}
89+
}
90+
3791
sourceSets {
3892
commonMain.dependencies {
3993
implementation("org.jetbrains.kotlin:kotlin-stdlib") {
@@ -46,13 +100,8 @@ kotlin {
46100
implementation("org.jetbrains.kotlin:kotlin-test-common")
47101
implementation("org.jetbrains.kotlin:kotlin-test-annotations-common")
48102
}
49-
50-
val jsAndWasmSharedMain by creating {
51-
dependsOn(commonMain.get())
52-
}
53-
103+
54104
jsMain {
55-
dependsOn(jsAndWasmSharedMain)
56105
dependencies {
57106
compileOnly("org.jetbrains.kotlin:kotlin-dom-api-compat")
58107
}
@@ -64,21 +113,12 @@ kotlin {
64113
}
65114
}
66115

67-
wasmJsMain {
68-
dependsOn(jsAndWasmSharedMain)
69-
}
70-
71116
wasmJsTest {
72117
dependencies {
73118
implementation("org.jetbrains.kotlin:kotlin-test-wasm-js")
74119
}
75120
}
76121

77-
78-
wasmWasiMain {
79-
dependsOn(jsAndWasmSharedMain)
80-
}
81-
82122
wasmWasiTest {
83123
dependencies {
84124
implementation("org.jetbrains.kotlin:kotlin-test-wasm-wasi")
@@ -94,93 +134,6 @@ kotlin {
94134
}
95135
}
96136
}
97-
}
98-
99-
// Support of all non-deprecated targets from the official tier list: https://kotlinlang.org/docs/native-target-support.html
100-
kotlin {
101-
// Tier 1
102-
macosX64()
103-
macosArm64()
104-
iosSimulatorArm64()
105-
iosX64()
106-
107-
// Tier 2
108-
linuxX64()
109-
linuxArm64()
110-
watchosSimulatorArm64()
111-
watchosX64()
112-
watchosArm32()
113-
watchosArm64()
114-
tvosSimulatorArm64()
115-
tvosX64()
116-
tvosArm64()
117-
iosArm64()
118-
119-
// Tier 3
120-
androidNativeArm32()
121-
androidNativeArm64()
122-
androidNativeX86()
123-
androidNativeX64()
124-
mingwX64()
125-
watchosDeviceArm64()
126-
127-
@Suppress("DEPRECATION") //https://github.com/Kotlin/kotlinx-atomicfu/issues/207
128-
linuxArm32Hfp()
129-
130-
@OptIn(ExperimentalKotlinGradlePluginApi::class)
131-
applyDefaultHierarchyTemplate {
132-
group("nativeUnixLike") {
133-
withLinux()
134-
}
135-
group("androidNative32Bit") {
136-
withAndroidNativeX86()
137-
withCompilations { compilation ->
138-
(compilation.target as? KotlinNativeTarget)?.konanTarget?.name == "android_arm32"
139-
}
140-
}
141-
group("androidNative64Bit") {
142-
withAndroidNativeArm64()
143-
withAndroidNativeX64()
144-
}
145-
}
146-
147-
sourceSets {
148-
val nativeNonAppleMain by creating {
149-
kotlin.srcDir("src/nativeNonAppleMain/kotlin")
150-
dependsOn(nativeMain.get())
151-
}
152-
153-
val nativeUnixLikeMain by getting {
154-
kotlin.srcDir("src/nativeUnixLikeMain/kotlin")
155-
dependsOn(nativeNonAppleMain)
156-
}
157-
158-
val androidNative32BitMain by getting {
159-
kotlin.srcDir("src/androidNative32BitMain/kotlin")
160-
dependsOn(nativeNonAppleMain)
161-
}
162-
163-
val androidNative64BitMain by getting {
164-
kotlin.srcDir("src/androidNative64BitMain/kotlin")
165-
dependsOn(nativeNonAppleMain)
166-
}
167-
168-
val mingwMain by getting {
169-
kotlin.srcDir("src/mingwMain/kotlin")
170-
dependsOn(nativeNonAppleMain)
171-
}
172-
173-
val androidNative32BitTest by getting {
174-
kotlin.srcDir("src/androidNative32BitTest/kotlin")
175-
dependsOn(nativeTest.get())
176-
}
177-
178-
val androidNative64BitTest by getting {
179-
kotlin.srcDir("src/androidNative64BitTest/kotlin")
180-
dependsOn(nativeTest.get())
181-
}
182-
183-
}
184137

185138
// atomicfu-cinterop-interop.klib with an empty interop.def file will still be published for compatibility reasons (see KT-68411)
186139
// This block can be removed when this issue in K/N compiler is resolved: KT-60874
@@ -387,4 +340,3 @@ val jvmTest by tasks.getting(Test::class) {
387340
)
388341
// run them only for transformed code
389342
}
390-
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package kotlinx.atomicfu.locks
2+
3+
import kotlinx.cinterop.*
4+
import kotlinx.cinterop.alloc
5+
import kotlinx.cinterop.free
6+
import kotlinx.cinterop.pointed
7+
import kotlinx.cinterop.ptr
8+
import platform.posix.*
9+
10+
@OptIn(ExperimentalForeignApi::class, UnsafeNumber::class)
11+
internal actual object ParkingDelegator {
12+
actual fun createRef(): ParkingData {
13+
val mut = nativeHeap.alloc<pthread_mutex_t>().ptr
14+
val cond = nativeHeap.alloc<pthread_cond_t>().ptr
15+
val attr = nativeHeap.alloc<pthread_condattr_tVar>().ptr
16+
callAndVerify(0) { pthread_mutex_init(mut, null) }
17+
callAndVerify(0) { pthread_condattr_init(attr) }
18+
callAndVerify(0) { pthread_condattr_setclock(attr, CLOCK_MONOTONIC) }
19+
callAndVerify(0) { pthread_cond_init(cond, attr) }
20+
21+
callAndVerify(0) { pthread_condattr_destroy(attr) }
22+
nativeHeap.free(attr)
23+
return ParkingData(mut, cond)
24+
}
25+
26+
actual inline fun wait(ref: ParkingData, shouldWait: () -> Boolean){
27+
callAndVerify(0) { pthread_mutex_lock(ref.mut) }
28+
if (shouldWait()) callAndVerify(0) { pthread_cond_wait(ref.cond, ref.mut) }
29+
callAndVerify(0) { pthread_mutex_unlock(ref.mut) }
30+
}
31+
32+
actual inline fun timedWait(ref: ParkingData, nanos: Long, shouldWait: () -> Boolean): Unit = memScoped {
33+
val ts = alloc<timespec>().ptr
34+
35+
// Add nanos to current time
36+
callAndVerify(0) { clock_gettime(CLOCK_MONOTONIC.convert(), ts) }
37+
ts.pointed.tv_sec = ts.pointed.tv_sec.addNanosToSeconds(nanos)
38+
ts.pointed.tv_nsec = (ts.pointed.tv_nsec + nanos % 1_000_000_000).convert()
39+
//Fix overflow
40+
if (ts.pointed.tv_nsec >= 1_000_000_000) {
41+
ts.pointed.tv_sec = ts.pointed.tv_sec.addNanosToSeconds(1_000_000_000)
42+
ts.pointed.tv_nsec -= 1_000_000_000
43+
}
44+
callAndVerify(0) { pthread_mutex_lock(ref.mut) }
45+
if (shouldWait()) callAndVerify(0, ETIMEDOUT) { pthread_cond_timedwait(ref.cond, ref.mut, ts) }
46+
callAndVerify(0) { pthread_mutex_unlock(ref.mut) }
47+
}
48+
49+
actual fun wake(ref: ParkingData) {
50+
callAndVerify(0) { pthread_mutex_lock(ref.mut) }
51+
callAndVerify(0) { pthread_cond_signal(ref.cond) }
52+
callAndVerify(0) { pthread_mutex_unlock(ref.mut) }
53+
}
54+
55+
actual fun destroyRef(ref: ParkingData) {
56+
callAndVerify(0) { pthread_mutex_destroy(ref.mut) }
57+
callAndVerify(0) { pthread_cond_destroy(ref.cond) }
58+
nativeHeap.free(ref.mut)
59+
nativeHeap.free(ref.cond)
60+
}
61+
}
62+
internal actual class ParkingData @OptIn(UnsafeNumber::class) constructor(val mut: CPointer<pthread_mutex_t>, val cond: CPointer<pthread_cond_t>)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package kotlinx.atomicfu.locks
2+
3+
import kotlinx.cinterop.CPointer
4+
import platform.posix.CLOCK_REALTIME
5+
import platform.posix.*
6+
7+
// CLOCK_REALTIME should be equal to CLOCK_SYSTEM on darwin.
8+
// https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/mach/clock_types.h#L70-L73
9+
// Where CLOCK_CALENDAR is the time from epoch.
10+
internal actual val clockId: Int
11+
get() = CLOCK_REALTIME
12+
13+
actual fun setClock(attr: CPointer<pthread_condattr_t>): Int = 0
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package kotlinx.atomicfu.locks
2+
3+
/**
4+
* Object that stores references that need to be manually destroyed and deallocated,
5+
* after native pthread_cond_wait usage.
6+
*/
7+
internal expect class ParkingData
8+
9+
/**
10+
* Internal utility that delegates the thread suspending and resuming to pthread_cond_wait on native.
11+
* On jvm delegates to LockSupport.Park.
12+
*/
13+
internal expect object ParkingDelegator {
14+
fun createRef(): ParkingData
15+
fun wait(ref: ParkingData, shouldWait: () -> Boolean)
16+
fun timedWait(ref: ParkingData, nanos: Long, shouldWait: () -> Boolean)
17+
fun wake(ref: ParkingData)
18+
fun destroyRef(ref: ParkingData)
19+
}
20+
21+
/**
22+
* Adds nano seconds to current time in seconds.
23+
* Clamps for Int.
24+
*/
25+
internal fun Int.addNanosToSeconds(nanos: Long): Int =
26+
(this + nanos / 1_000_000_000).coerceIn(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()).toInt()
27+
internal fun Long.addNanosToSeconds(nanos: Long): Long {
28+
29+
// Should never happen as this is checked in `ThreadParker`
30+
check(nanos >= 0) { "Cannot wait for a negative number of nanoseconds" }
31+
val result = this + nanos / 1_000_000_000
32+
33+
// Overflow check: should never happen since this is very far into the future.
34+
check(!(this xor result < 0 && this >= 0)) { "Nano seconds addition overflowed, current time in seconds is $this" }
35+
return result
36+
}

0 commit comments

Comments
 (0)