Skip to content

Commit

Permalink
Introduce korlibs-concurrent (Lock + NativeThread) + add logger, data…
Browse files Browse the repository at this point in the history
…structure and platform to K/N desktop targets (#2132)
  • Loading branch information
soywiz committed Jan 16, 2024
1 parent 55a84ad commit 3fe7a19
Show file tree
Hide file tree
Showing 32 changed files with 357 additions and 170 deletions.
11 changes: 10 additions & 1 deletion buildSrc/src/main/kotlin/korlibs/root/RootKorlibsPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,16 @@ object RootKorlibsPlugin {
val tvos by lazy { createPairSourceSet("tvos", iosTvos, project = project) }
val ios by lazy { createPairSourceSet("ios", iosTvos/*, iosMacos*/, project = project) }

if (project.name == "korlibs-time" || project.name == "korlibs-crypto") {
@Suppress("SimplifyBooleanWithConstants")
if (
false
|| project.name == "korlibs-time"
|| project.name == "korlibs-crypto"
|| project.name == "korlibs-concurrent"
|| project.name == "korlibs-logger"
|| project.name == "korlibs-datastructure"
|| project.name == "korlibs-platform"
) {
val macos by lazy { createPairSourceSet("macos", darwin, project = project) }
val linux by lazy { createPairSourceSet("linux", posix, project = project) }
val mingw by lazy { createPairSourceSet("mingw", native, project = project) }
Expand Down
1 change: 1 addition & 0 deletions korlibs-concurrent/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
15 changes: 15 additions & 0 deletions korlibs-concurrent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import korlibs.*

description = "Korlibs Concurrent"

project.extensions.extraProperties.properties.apply {
applyProjectProperties(
"https://raw.githubusercontent.com/korlibs/korge/main/korlibs-concurrent",
"Public Domain",
"https://raw.githubusercontent.com/korlibs/korge/main/korlibs-concurrent/LICENSE"
)
}

dependencies {
commonMainApi(libs.kotlinx.atomicfu)
}
64 changes: 64 additions & 0 deletions korlibs-concurrent/src/korlibs/concurrent/lock/Lock.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
@file:Suppress("PackageDirectoryMismatch")

package korlibs.concurrent.lock

import korlibs.concurrent.thread.*
import kotlin.time.*
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds

interface BaseLock {
fun notify(unit: Unit = Unit)
fun wait(time: Duration): Boolean
//fun lock()
//fun unlock()
}

//typealias Lock = BaseLock
//typealias NonRecursiveLock = BaseLock

//inline operator fun <T> BaseLock.invoke(callback: () -> T): T {
// lock()
// try {
// return callback()
// } finally {
// unlock()
// }
//}

/**
* Reentrant typical lock.
*/
expect class Lock() : BaseLock {
override fun notify(unit: Unit)
override fun wait(time: Duration): Boolean
inline operator fun <T> invoke(callback: () -> T): T
}

/**
* Optimized lock that cannot be called inside another lock,
* don't keep the current thread id, or a list of threads to awake
* It is lightweight and just requires an atomic.
* Does busy-waiting instead of sleeping the thread.
*/
expect class NonRecursiveLock() : BaseLock {
override fun notify(unit: Unit)
override fun wait(time: Duration): Boolean
inline operator fun <T> invoke(callback: () -> T): T
}

fun BaseLock.waitPrecise(time: Duration): Boolean {
val startTime = TimeSource.Monotonic.markNow()
val doWait = time - 10.milliseconds
val signaled = if (doWait > 0.seconds) wait(doWait) else false
if (!signaled && doWait > 0.seconds) {
val elapsed = startTime.elapsedNow()
//println(" !!!!! SLEEP EXACT: ${elapsed - time}")
NativeThread.sleepExact(time - elapsed)
}
return signaled
}

fun BaseLock.wait(time: Duration, precise: Boolean): Boolean {
return if (precise) waitPrecise(time) else wait(time)
}
70 changes: 70 additions & 0 deletions korlibs-concurrent/src/korlibs/concurrent/thread/NativeThread.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package korlibs.concurrent.thread

import kotlin.time.*
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds

// @TODO: Mark this as experimental or something so people know this is not fully supported in all the targets.
// @TODO: isSupported is required to be used.
expect class NativeThread(code: (NativeThread) -> Unit) {
companion object {
val isSupported: Boolean
val currentThreadId: Long
val currentThreadName: String?

fun gc(full: Boolean): Unit
fun sleep(time: Duration): Unit
inline fun spinWhile(cond: () -> Boolean): Unit
}
var userData: Any?
var threadSuggestRunning: Boolean
var priority: Int
var name: String?
var isDaemon: Boolean
fun start(): Unit
fun interrupt(): Unit
}

public fun nativeThread(
start: Boolean = true,
isDaemon: Boolean = false,
name: String? = null,
priority: Int = -1,
block: (NativeThread) -> Unit
): NativeThread {
val thread = NativeThread(block)
if (isDaemon) thread.isDaemon = true
if (priority > 0) thread.priority = priority
if (name != null) thread.name = name
// if (contextClassLoader != null) thread.contextClassLoader = contextClassLoader
if (start) thread.start()
return thread
}

fun NativeThread.Companion.sleep(time: Duration, exact: Boolean) {
if (exact) sleepExact(time) else sleep(time)
}

// https://stackoverflow.com/questions/13397571/precise-thread-sleep-needed-max-1ms-error#:~:text=Scheduling%20Fundamentals
// https://www.softprayog.in/tutorials/alarm-sleep-and-high-resolution-timers
fun NativeThread.Companion.sleepExact(time: Duration) {
val start = TimeSource.Monotonic.markNow()
//val imprecision = 10.milliseconds
//val imprecision = 1.milliseconds
val imprecision = 4.milliseconds
val javaSleep = time - imprecision
if (javaSleep >= 0.seconds) {
NativeThread.sleep(javaSleep)
}
NativeThread.spinWhile { start.elapsedNow() < time }
}

//fun NativeThread.Companion.sleepUntil(date: DateTime, exact: Boolean = true) {
// sleep(date - DateTime.now(), exact)
//}

inline fun NativeThread.Companion.sleepWhile(cond: () -> Boolean) {
while (cond()) {
NativeThread.sleep(1.milliseconds)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package korlibs.concurrent.thread

import kotlinx.cinterop.*
import platform.Foundation.*

actual val __currentThreadId: Long get() = NSThread.currentThread.objcPtr().toLong()
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package korlibs.datastructure.lock
package korlibs.concurrent.lock

import korlibs.time.*
import kotlin.time.*

actual class Lock actual constructor() : BaseLock {
var locked = false
Expand All @@ -15,7 +15,7 @@ actual class Lock actual constructor() : BaseLock {
actual override fun notify(unit: Unit) {
if (!locked) error("Must lock before notifying")
}
actual override fun wait(time: TimeSpan): Boolean {
actual override fun wait(time: Duration): Boolean {
if (!locked) error("Must lock before waiting")
return false
}
Expand All @@ -25,7 +25,7 @@ actual class NonRecursiveLock actual constructor() : BaseLock {
actual inline operator fun <T> invoke(callback: () -> T): T = callback()
actual override fun notify(unit: Unit) {
}
actual override fun wait(time: TimeSpan): Boolean {
actual override fun wait(time: Duration): Boolean {
return false
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package korlibs.datastructure.thread
package korlibs.concurrent.thread

import korlibs.datastructure.*
import korlibs.time.*
import kotlin.time.*

actual class NativeThread actual constructor(val code: (NativeThread) -> Unit) : Extra by Extra.Mixin() {
actual class NativeThread actual constructor(val code: (NativeThread) -> Unit) {
actual var userData: Any? = null
actual var isDaemon: Boolean = false
actual var threadSuggestRunning = true

Expand All @@ -28,7 +27,7 @@ actual class NativeThread actual constructor(val code: (NativeThread) -> Unit) :
actual fun gc(full: Boolean) {
}

actual fun sleep(time: TimeSpan) {
actual fun sleep(time: Duration) {
warnSleep
val start = TimeSource.Monotonic.markNow()
spinWhile { start.elapsedNow() < time }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package korlibs.datastructure.lock
package korlibs.concurrent.lock

import korlibs.time.*
import java.util.concurrent.atomic.*
import kotlin.time.*

private fun TimeSpan.toMillisNanos(): Pair<Long, Int> {
private fun Duration.toMillisNanos(): Pair<Long, Int> {
val nanoSeconds = inWholeNanoseconds
val millis = (nanoSeconds / 1_000_000L)
val nanos = (nanoSeconds % 1_000_000L).toInt()
Expand All @@ -16,15 +15,15 @@ actual class Lock actual constructor() : BaseLock {

actual override fun notify(unit: Unit) {
signaled.set(true)
(this as java.lang.Object).notifyAll()
(this as Object).notifyAll()
}

actual override fun wait(time: TimeSpan): Boolean {
actual override fun wait(time: Duration): Boolean {
val (millis, nanos) = time.toMillisNanos()
signaled.set(false)
//println("MyLock.wait: $time")
val time = TimeSource.Monotonic.measureTime {
(this as java.lang.Object).wait(millis, nanos)
(this as Object).wait(millis, nanos)
}
//println(" -> $time")
return signaled.get()
Expand All @@ -38,15 +37,15 @@ actual class NonRecursiveLock actual constructor() : BaseLock {

actual override fun notify(unit: Unit) {
signaled.set(true)
(this as java.lang.Object).notifyAll()
(this as Object).notifyAll()
}

actual override fun wait(time: TimeSpan): Boolean {
actual override fun wait(time: Duration): Boolean {
val (millis, nanos) = time.toMillisNanos()
signaled.set(false)
//println("MyLock.wait: $time")
val time = TimeSource.Monotonic.measureTime {
(this as java.lang.Object).wait(millis, nanos)
(this as Object).wait(millis, nanos)
}
//println(" -> $time")
return signaled.get()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package korlibs.datastructure.thread
package korlibs.concurrent.thread

import korlibs.datastructure.*
import korlibs.time.*
import kotlin.time.*
import kotlin.time.Duration.Companion.seconds

private fun TimeSpan.toMillisNanos(): Pair<Long, Int> {
private fun Duration.toMillisNanos(): Pair<Long, Int> {
val nanoSeconds = inWholeNanoseconds
val millis = (nanoSeconds / 1_000_000L)
val nanos = (nanoSeconds % 1_000_000L).toInt()
return millis to nanos
}

actual class NativeThread actual constructor(val code: (NativeThread) -> Unit) : Extra by Extra.Mixin() {
actual class NativeThread actual constructor(val code: (NativeThread) -> Unit) {
val thread = Thread { code(this) }
actual var userData: Any? = null

actual var threadSuggestRunning = true

Expand Down Expand Up @@ -46,7 +47,7 @@ actual class NativeThread actual constructor(val code: (NativeThread) -> Unit) :
System.gc()
}

actual fun sleep(time: TimeSpan) {
actual fun sleep(time: Duration) {
//val gcTime = measureTime { System.gc() }
//val compensatedTime = time - gcTime
val compensatedTime = time
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package korlibs.concurrent.thread

import platform.posix.*

actual val __currentThreadId: Long get() = pthread_self().toLong()
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package korlibs.concurrent.thread

import kotlinx.cinterop.*
import platform.posix.*

actual val __currentThreadId: Long get() = pthread_self().toLong()
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package korlibs.datastructure.lock
package korlibs.concurrent.lock

import korlibs.datastructure.thread.*
import korlibs.time.*
import korlibs.concurrent.thread.*
import platform.posix.*
import kotlin.concurrent.*
import kotlin.time.*
Expand Down Expand Up @@ -46,7 +45,7 @@ actual class Lock actual constructor() : BaseLock {
if (current != pthread_self()) error("Must lock the notify thread")
notified.value = true
}
actual override fun wait(time: TimeSpan): Boolean {
actual override fun wait(time: Duration): Boolean {
check(locked.value) { "Must wait inside a synchronization block" }
val start = TimeSource.Monotonic.markNow()
notified.value = false
Expand Down Expand Up @@ -86,7 +85,7 @@ actual class NonRecursiveLock actual constructor() : BaseLock {
actual override fun notify(unit: Unit) {
notified.value = true
}
actual override fun wait(time: TimeSpan): Boolean {
actual override fun wait(time: Duration): Boolean {
check(locked.value) { "Must wait inside a synchronization block" }
val start = TimeSource.Monotonic.markNow()
notified.value = false
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
@file:Suppress("PackageDirectoryMismatch")
package korlibs.datastructure.thread
package korlibs.concurrent.thread

import korlibs.datastructure.*
import korlibs.time.*
import kotlinx.cinterop.*
import platform.Foundation.*
import platform.posix.*
import kotlin.native.concurrent.*
import kotlin.native.runtime.*
import kotlin.time.*

actual class NativeThread actual constructor(val code: (NativeThread) -> Unit) : Extra by Extra.Mixin() {
actual class NativeThread actual constructor(val code: (NativeThread) -> Unit) {
actual var isDaemon: Boolean = false
actual var userData: Any? = null

actual var threadSuggestRunning: Boolean = true
var worker: Worker? = null
Expand Down Expand Up @@ -38,15 +37,15 @@ actual class NativeThread actual constructor(val code: (NativeThread) -> Unit) :

actual companion object {
actual val isSupported: Boolean get() = true
actual val currentThreadId: Long get() = NSThread.currentThread.objcPtr().toLong()
actual val currentThreadId: Long get() = korlibs.concurrent.thread.__currentThreadId
actual val currentThreadName: String? get() = "Thread-$currentThreadId"

@OptIn(NativeRuntimeApi::class)
actual fun gc(full: Boolean) {
GC.schedule()
}

actual fun sleep(time: TimeSpan): Unit {
actual fun sleep(time: Duration): Unit {
//platform.posix.nanosleep()
platform.posix.usleep(time.inWholeMicroseconds.toUInt())

Expand All @@ -59,3 +58,5 @@ actual class NativeThread actual constructor(val code: (NativeThread) -> Unit) :
}
}
}

internal expect val __currentThreadId: Long
Loading

0 comments on commit 3fe7a19

Please sign in to comment.