diff --git a/buildSrc/src/main/kotlin/korlibs/root/RootKorlibsPlugin.kt b/buildSrc/src/main/kotlin/korlibs/root/RootKorlibsPlugin.kt index 6d346d67c4..e4d0efa565 100644 --- a/buildSrc/src/main/kotlin/korlibs/root/RootKorlibsPlugin.kt +++ b/buildSrc/src/main/kotlin/korlibs/root/RootKorlibsPlugin.kt @@ -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) } diff --git a/korlibs-concurrent/.gitignore b/korlibs-concurrent/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/korlibs-concurrent/.gitignore @@ -0,0 +1 @@ +/build diff --git a/korlibs-concurrent/build.gradle.kts b/korlibs-concurrent/build.gradle.kts new file mode 100644 index 0000000000..a19b170a85 --- /dev/null +++ b/korlibs-concurrent/build.gradle.kts @@ -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) +} diff --git a/korlibs-concurrent/src/korlibs/concurrent/lock/Lock.kt b/korlibs-concurrent/src/korlibs/concurrent/lock/Lock.kt new file mode 100644 index 0000000000..ea1f8fa4f3 --- /dev/null +++ b/korlibs-concurrent/src/korlibs/concurrent/lock/Lock.kt @@ -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 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 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 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) +} diff --git a/korlibs-concurrent/src/korlibs/concurrent/thread/NativeThread.kt b/korlibs-concurrent/src/korlibs/concurrent/thread/NativeThread.kt new file mode 100644 index 0000000000..c57487625e --- /dev/null +++ b/korlibs-concurrent/src/korlibs/concurrent/thread/NativeThread.kt @@ -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) + } +} diff --git a/korlibs-concurrent/src@darwin/korlibs/concurrent/thread/NativeThread.native.darwin.kt b/korlibs-concurrent/src@darwin/korlibs/concurrent/thread/NativeThread.native.darwin.kt new file mode 100644 index 0000000000..394ab54a49 --- /dev/null +++ b/korlibs-concurrent/src@darwin/korlibs/concurrent/thread/NativeThread.native.darwin.kt @@ -0,0 +1,6 @@ +package korlibs.concurrent.thread + +import kotlinx.cinterop.* +import platform.Foundation.* + +actual val __currentThreadId: Long get() = NSThread.currentThread.objcPtr().toLong() diff --git a/korlibs-datastructure/src@js/korlibs/datastructure/lock/LockJs.kt b/korlibs-concurrent/src@js/korlibs/concurrent/lock/Lock.js.kt similarity index 80% rename from korlibs-datastructure/src@js/korlibs/datastructure/lock/LockJs.kt rename to korlibs-concurrent/src@js/korlibs/concurrent/lock/Lock.js.kt index e020cd11c0..11cf4ea2eb 100644 --- a/korlibs-datastructure/src@js/korlibs/datastructure/lock/LockJs.kt +++ b/korlibs-concurrent/src@js/korlibs/concurrent/lock/Lock.js.kt @@ -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 @@ -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 } @@ -25,7 +25,7 @@ actual class NonRecursiveLock actual constructor() : BaseLock { actual inline operator fun 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 } } diff --git a/korlibs-datastructure/src@js/korlibs/datastructure/thread/NativeThread.kt b/korlibs-concurrent/src@js/korlibs/concurrent/thread/NativeThread.js.kt similarity index 87% rename from korlibs-datastructure/src@js/korlibs/datastructure/thread/NativeThread.kt rename to korlibs-concurrent/src@js/korlibs/concurrent/thread/NativeThread.js.kt index 3c01c34862..be45f3b2b8 100644 --- a/korlibs-datastructure/src@js/korlibs/datastructure/thread/NativeThread.kt +++ b/korlibs-concurrent/src@js/korlibs/concurrent/thread/NativeThread.js.kt @@ -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 @@ -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 } diff --git a/korlibs-datastructure/src@jvmAndroid/korlibs/datastructure/lock/LockJvm.kt b/korlibs-concurrent/src@jvmAndroid/korlibs/concurrent/lock/Lock.jvm.kt similarity index 74% rename from korlibs-datastructure/src@jvmAndroid/korlibs/datastructure/lock/LockJvm.kt rename to korlibs-concurrent/src@jvmAndroid/korlibs/concurrent/lock/Lock.jvm.kt index 248d49d0b4..20ce5a7a31 100644 --- a/korlibs-datastructure/src@jvmAndroid/korlibs/datastructure/lock/LockJvm.kt +++ b/korlibs-concurrent/src@jvmAndroid/korlibs/concurrent/lock/Lock.jvm.kt @@ -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 { +private fun Duration.toMillisNanos(): Pair { val nanoSeconds = inWholeNanoseconds val millis = (nanoSeconds / 1_000_000L) val nanos = (nanoSeconds % 1_000_000L).toInt() @@ -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() @@ -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() diff --git a/korlibs-datastructure/src@jvmAndroid/korlibs/datastructure/thread/NativeThread.kt b/korlibs-concurrent/src@jvmAndroid/korlibs/concurrent/thread/NativeThread.jvm.kt similarity index 87% rename from korlibs-datastructure/src@jvmAndroid/korlibs/datastructure/thread/NativeThread.kt rename to korlibs-concurrent/src@jvmAndroid/korlibs/concurrent/thread/NativeThread.jvm.kt index 0ff2acca63..63fc2e6588 100644 --- a/korlibs-datastructure/src@jvmAndroid/korlibs/datastructure/thread/NativeThread.kt +++ b/korlibs-concurrent/src@jvmAndroid/korlibs/concurrent/thread/NativeThread.jvm.kt @@ -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 { +private fun Duration.toMillisNanos(): Pair { 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 @@ -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 diff --git a/korlibs-concurrent/src@linux/korlibs/concurrent/thread/NativeThread.native.darwin.kt b/korlibs-concurrent/src@linux/korlibs/concurrent/thread/NativeThread.native.darwin.kt new file mode 100644 index 0000000000..c8b30cbd6a --- /dev/null +++ b/korlibs-concurrent/src@linux/korlibs/concurrent/thread/NativeThread.native.darwin.kt @@ -0,0 +1,5 @@ +package korlibs.concurrent.thread + +import platform.posix.* + +actual val __currentThreadId: Long get() = pthread_self().toLong() diff --git a/korlibs-concurrent/src@mingw/korlibs/concurrent/thread/NativeThread.native.darwin.kt b/korlibs-concurrent/src@mingw/korlibs/concurrent/thread/NativeThread.native.darwin.kt new file mode 100644 index 0000000000..6d03609fc2 --- /dev/null +++ b/korlibs-concurrent/src@mingw/korlibs/concurrent/thread/NativeThread.native.darwin.kt @@ -0,0 +1,6 @@ +package korlibs.concurrent.thread + +import kotlinx.cinterop.* +import platform.posix.* + +actual val __currentThreadId: Long get() = pthread_self().toLong() diff --git a/korlibs-datastructure/src@darwin/korlibs/datastructure/lock/LockNative.kt b/korlibs-concurrent/src@native/korlibs/concurrent/lock/Lock.native.kt similarity index 93% rename from korlibs-datastructure/src@darwin/korlibs/datastructure/lock/LockNative.kt rename to korlibs-concurrent/src@native/korlibs/concurrent/lock/Lock.native.kt index fdee529afe..4b15b9eae6 100644 --- a/korlibs-datastructure/src@darwin/korlibs/datastructure/lock/LockNative.kt +++ b/korlibs-concurrent/src@native/korlibs/concurrent/lock/Lock.native.kt @@ -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.* @@ -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 @@ -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 diff --git a/korlibs-datastructure/src@darwin/korlibs/datastructure/thread/NativeThread.kt b/korlibs-concurrent/src@native/korlibs/concurrent/thread/NativeThread.native.kt similarity index 81% rename from korlibs-datastructure/src@darwin/korlibs/datastructure/thread/NativeThread.kt rename to korlibs-concurrent/src@native/korlibs/concurrent/thread/NativeThread.native.kt index f9288b3127..1ecca6183f 100644 --- a/korlibs-datastructure/src@darwin/korlibs/datastructure/thread/NativeThread.kt +++ b/korlibs-concurrent/src@native/korlibs/concurrent/thread/NativeThread.native.kt @@ -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 @@ -38,7 +37,7 @@ 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) @@ -46,7 +45,7 @@ actual class NativeThread actual constructor(val code: (NativeThread) -> Unit) : GC.schedule() } - actual fun sleep(time: TimeSpan): Unit { + actual fun sleep(time: Duration): Unit { //platform.posix.nanosleep() platform.posix.usleep(time.inWholeMicroseconds.toUInt()) @@ -59,3 +58,5 @@ actual class NativeThread actual constructor(val code: (NativeThread) -> Unit) : } } } + +internal expect val __currentThreadId: Long diff --git a/korlibs-datastructure/src@wasmJs/korlibs/datastructure/lock/LockJs.kt b/korlibs-concurrent/src@wasmJs/korlibs/concurrent/lock/Lock.wasmJs.kt similarity index 80% rename from korlibs-datastructure/src@wasmJs/korlibs/datastructure/lock/LockJs.kt rename to korlibs-concurrent/src@wasmJs/korlibs/concurrent/lock/Lock.wasmJs.kt index 6b994d9b42..13ecc7e8b6 100644 --- a/korlibs-datastructure/src@wasmJs/korlibs/datastructure/lock/LockJs.kt +++ b/korlibs-concurrent/src@wasmJs/korlibs/concurrent/lock/Lock.wasmJs.kt @@ -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 @@ -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 } @@ -25,7 +25,7 @@ actual class NonRecursiveLock actual constructor() : BaseLock { actual inline operator fun 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 } } diff --git a/korlibs-datastructure/src@wasmJs/korlibs/datastructure/thread/NativeThread.kt b/korlibs-concurrent/src@wasmJs/korlibs/concurrent/thread/NativeThread.wasmJs.kt similarity index 87% rename from korlibs-datastructure/src@wasmJs/korlibs/datastructure/thread/NativeThread.kt rename to korlibs-concurrent/src@wasmJs/korlibs/concurrent/thread/NativeThread.wasmJs.kt index c380fbadbc..9656a0c2a9 100644 --- a/korlibs-datastructure/src@wasmJs/korlibs/datastructure/thread/NativeThread.kt +++ b/korlibs-concurrent/src@wasmJs/korlibs/concurrent/thread/NativeThread.wasmJs.kt @@ -1,12 +1,11 @@ -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 isDaemon: Boolean = false actual var threadSuggestRunning = true + actual var userData: Any? = null actual fun start() { threadSuggestRunning = true @@ -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 } diff --git a/korlibs-datastructure/build.gradle.kts b/korlibs-datastructure/build.gradle.kts index 0a8412f0f3..a127c9f5aa 100644 --- a/korlibs-datastructure/build.gradle.kts +++ b/korlibs-datastructure/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { commonTestApi(libs.kotlinx.coroutines.test) commonMainApi(project(":korlibs-time")) commonMainApi(project(":korlibs-platform")) + commonMainApi(project(":korlibs-concurrent")) } fun doGenerateKdsTemplates() { diff --git a/korlibs-datastructure/src/korlibs/datastructure/_Datastructure_lock.kt b/korlibs-datastructure/src/korlibs/datastructure/_Datastructure_lock.kt index c79db0a326..f53f2cd4ca 100644 --- a/korlibs-datastructure/src/korlibs/datastructure/_Datastructure_lock.kt +++ b/korlibs-datastructure/src/korlibs/datastructure/_Datastructure_lock.kt @@ -2,62 +2,18 @@ package korlibs.datastructure.lock -import korlibs.datastructure.thread.* import korlibs.time.* -import kotlin.time.* - -interface BaseLock { - fun notify(unit: Unit = Unit) - fun wait(time: TimeSpan): Boolean - //fun lock() - //fun unlock() -} - -//typealias Lock = BaseLock -//typealias NonRecursiveLock = BaseLock - -//inline operator fun 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: TimeSpan): Boolean - inline operator fun 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: TimeSpan): Boolean - inline operator fun invoke(callback: () -> T): T -} - -fun BaseLock.waitPrecise(time: TimeSpan): 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: TimeSpan, precise: Boolean): Boolean { - return if (precise) waitPrecise(time) else wait(time) -} +import korlibs.concurrent.lock.waitPrecise as waitPreciseConcurrent +import korlibs.concurrent.lock.wait as waitConcurrent + +@Deprecated("Use korlibs.concurrent.lock package") +typealias BaseLock = korlibs.concurrent.lock.BaseLock +@Deprecated("Use korlibs.concurrent.lock package") +typealias Lock = korlibs.concurrent.lock.Lock +@Deprecated("Use korlibs.concurrent.lock package") +typealias NonRecursiveLock = korlibs.concurrent.lock.NonRecursiveLock + +@Deprecated("Use korlibs.concurrent.lock package") +fun BaseLock.waitPrecise(time: TimeSpan): Boolean = this.waitPreciseConcurrent(time) +@Deprecated("Use korlibs.concurrent.lock package") +fun BaseLock.wait(time: TimeSpan, precise: Boolean): Boolean = this.waitConcurrent(time, precise) diff --git a/korlibs-datastructure/src/korlibs/datastructure/_Datastructure_thread.kt b/korlibs-datastructure/src/korlibs/datastructure/_Datastructure_thread.kt index 13b481c361..14e9fd255c 100644 --- a/korlibs-datastructure/src/korlibs/datastructure/_Datastructure_thread.kt +++ b/korlibs-datastructure/src/korlibs/datastructure/_Datastructure_thread.kt @@ -4,66 +4,40 @@ package korlibs.datastructure.thread import korlibs.datastructure.* import korlibs.time.* -import kotlin.time.* +import korlibs.concurrent.thread.sleep as sleepConcurrent +import korlibs.concurrent.thread.sleepExact as sleepExactConcurrent +import korlibs.concurrent.thread.sleepWhile as sleepWhileConcurrent -expect class NativeThread(code: (NativeThread) -> Unit) : Extra { - companion object { - val isSupported: Boolean - val currentThreadId: Long - val currentThreadName: String? +@Deprecated("Use korlibs.concurrent.thread package") +typealias NativeThread = korlibs.concurrent.thread.NativeThread - fun gc(full: Boolean): Unit - fun sleep(time: TimeSpan): Unit - inline fun spinWhile(cond: () -> Boolean): Unit +val korlibs.concurrent.thread.NativeThread.extra: Extra get() { + if (this.userData == null) { + this.userData = Extra.Mixin() } - var threadSuggestRunning: Boolean - var priority: Int - var name: String? - var isDaemon: Boolean - fun start(): Unit - fun interrupt(): Unit + return this.userData as Extra } +@Deprecated("Use korlibs.concurrent.thread package") 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 -} +): NativeThread = korlibs.concurrent.thread.nativeThread(start, isDaemon, name, priority, block) -fun NativeThread.Companion.sleep(time: TimeSpan, exact: Boolean) { - if (exact) sleepExact(time) else sleep(time) -} +@Deprecated("Use korlibs.concurrent.thread package") +fun korlibs.concurrent.thread.NativeThread.Companion.sleep(time: TimeSpan, exact: Boolean) = sleepConcurrent(time, exact) // 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: TimeSpan) { - 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 } -} +@Deprecated("Use korlibs.concurrent.thread package") +fun korlibs.concurrent.thread.NativeThread.Companion.sleepExact(time: TimeSpan) = sleepExactConcurrent(time) +@Deprecated("Use korlibs.concurrent.thread package") +inline fun korlibs.concurrent.thread.NativeThread.Companion.sleepWhile(cond: () -> Boolean) = sleepWhileConcurrent(cond) -fun NativeThread.Companion.sleepUntil(date: DateTime, exact: Boolean = true) { +// Extension from DateTime +fun korlibs.concurrent.thread.NativeThread.Companion.sleepUntil(date: DateTime, exact: Boolean = true) { sleep(date - DateTime.now(), exact) } - -fun NativeThread.Companion.sleepWhile(cond: () -> Boolean) { - while (cond()) { - NativeThread.sleep(1.milliseconds) - } -} diff --git a/korlibs-datastructure/src@darwin/korlibs/datastructure/Native.kt b/korlibs-datastructure/src@native/korlibs/datastructure/Native.kt similarity index 100% rename from korlibs-datastructure/src@darwin/korlibs/datastructure/Native.kt rename to korlibs-datastructure/src@native/korlibs/datastructure/Native.kt diff --git a/korlibs-datastructure/src@darwin/korlibs/datastructure/concurrent/ConcurrentDeque.kt b/korlibs-datastructure/src@native/korlibs/datastructure/concurrent/ConcurrentDeque.kt similarity index 100% rename from korlibs-datastructure/src@darwin/korlibs/datastructure/concurrent/ConcurrentDeque.kt rename to korlibs-datastructure/src@native/korlibs/datastructure/concurrent/ConcurrentDeque.kt diff --git a/korlibs-datastructure/src@darwin/korlibs/datastructure/internal/InternalNative.kt b/korlibs-datastructure/src@native/korlibs/datastructure/internal/InternalNative.kt similarity index 100% rename from korlibs-datastructure/src@darwin/korlibs/datastructure/internal/InternalNative.kt rename to korlibs-datastructure/src@native/korlibs/datastructure/internal/InternalNative.kt diff --git a/korlibs-datastructure/src@darwin/korlibs/datastructure/iterators/Native.kt b/korlibs-datastructure/src@native/korlibs/datastructure/iterators/Native.kt similarity index 100% rename from korlibs-datastructure/src@darwin/korlibs/datastructure/iterators/Native.kt rename to korlibs-datastructure/src@native/korlibs/datastructure/iterators/Native.kt diff --git a/korlibs-datastructure/test/korlibs/datastructure/thread/ThreadTest.kt b/korlibs-datastructure/test/korlibs/datastructure/thread/ThreadTest.kt new file mode 100644 index 0000000000..0d69c0a7ea --- /dev/null +++ b/korlibs-datastructure/test/korlibs/datastructure/thread/ThreadTest.kt @@ -0,0 +1,15 @@ +package korlibs.datastructure.thread + +import korlibs.datastructure.* +import kotlin.test.* + +class ThreadTest { + @Test + fun test() { + val KEY = "hello" + val VALUE = "world" + val extra = NativeThread { }.extra + extra.setExtra(KEY, VALUE) + assertEquals(VALUE, extra.getExtra(KEY)) + } +} diff --git a/korlibs-logger/build.gradle.kts b/korlibs-logger/build.gradle.kts index 5d00ac236a..0852b5962c 100644 --- a/korlibs-logger/build.gradle.kts +++ b/korlibs-logger/build.gradle.kts @@ -11,13 +11,5 @@ project.extensions.extraProperties.properties.apply { } dependencies { - commonMainApi(libs.kotlinx.coroutines.core) commonMainApi(libs.kotlinx.atomicfu) - commonTestApi(libs.kotlinx.coroutines.test) - commonMainApi(libs.kotlinx.atomicfu) - commonTestApi(libs.kotlinx.coroutines.test) - commonMainApi(project(":korlibs-time")) - commonMainApi(project(":korlibs-crypto")) - commonMainApi(project(":korlibs-platform")) - commonMainApi(project(":korlibs-datastructure")) } diff --git a/korlibs-logger/src/korlibs/logger/Logger.kt b/korlibs-logger/src/korlibs/logger/Logger.kt index 54f80892bb..31db81bc0b 100644 --- a/korlibs-logger/src/korlibs/logger/Logger.kt +++ b/korlibs-logger/src/korlibs/logger/Logger.kt @@ -1,7 +1,6 @@ package korlibs.logger -import korlibs.datastructure.lock.* -import korlibs.time.* +import kotlinx.atomicfu.locks.* import kotlin.time.* /** @@ -41,7 +40,7 @@ class Logger private constructor(val name: String, val normalizedName: String, v val isLocalOutputSet: Boolean get() = optOutput != null companion object { - private val Logger_lock = Lock() + private val Logger_lock = SynchronizedObject() private val Logger_loggers = HashMap() /** The default [Level] used for all [Logger] that doesn't have its [Logger.level] set */ @@ -51,7 +50,7 @@ class Logger private constructor(val name: String, val normalizedName: String, v var defaultOutput: Output = DefaultLogOutput /** Gets a [Logger] from its [name] */ - operator fun invoke(name: String): Logger = Logger_lock { + operator fun invoke(name: String): Logger = synchronized(Logger_lock) { val normalizedName = normalizeName(name) if (Logger_loggers[normalizedName] == null) { val logger = Logger(name, normalizedName, true) diff --git a/korlibs-logger/src@js/korlibs/logger/Logger.js.kt b/korlibs-logger/src@js/korlibs/logger/Logger.js.kt index e484328640..9a6fca43a0 100644 --- a/korlibs-logger/src@js/korlibs/logger/Logger.js.kt +++ b/korlibs-logger/src@js/korlibs/logger/Logger.js.kt @@ -1,6 +1,5 @@ package korlibs.logger -import korlibs.platform.* import kotlinx.browser.document actual object Console : BaseConsole() { @@ -20,14 +19,17 @@ actual object DefaultLogOutput : Logger.Output { override fun output(logger: Logger, level: Logger.Level, msg: Any?) = Logger.ConsoleLogOutput.output(logger, level, msg) } -external private val process: dynamic -external private val Deno: dynamic +private external val process: dynamic +private external val Deno: dynamic + +private val isDenoJs: Boolean by lazy { js("(typeof Deno === 'object' && Deno.statSync)").unsafeCast() } +private val isNodeJs: Boolean by lazy { js("((typeof process !== 'undefined') && process.release && (process.release.name.search(/node|io.js/) !== -1))").unsafeCast() } internal actual val miniEnvironmentVariables: Map by lazy { when { - Platform.isJsNodeJs -> jsObjectToMap(process.env) - Platform.isJsDenoJs -> jsObjectToMap(Deno.env) + isNodeJs -> jsObjectToMap(process.env) + isDenoJs -> jsObjectToMap(Deno.env) js("(typeof document !== 'undefined')") -> QueryString_decode((document.location?.search ?: "").trimStart('?')).map { it.key to (it.value.firstOrNull() ?: it.key) }.toMap() else -> mapOf() } diff --git a/korlibs-logger/src@jvm/korlibs/logger/Logger.jvm.kt b/korlibs-logger/src@jvm/korlibs/logger/Logger.jvm.kt index d2b9ef3e75..037f2c9f76 100644 --- a/korlibs-logger/src@jvm/korlibs/logger/Logger.jvm.kt +++ b/korlibs-logger/src@jvm/korlibs/logger/Logger.jvm.kt @@ -1,7 +1,8 @@ package korlibs.logger import korlibs.logger.Console.color -import korlibs.time.* +import java.text.SimpleDateFormat +import java.util.Date import java.util.logging.* actual object Console : BaseConsole() { @@ -24,6 +25,8 @@ actual object Console : BaseConsole() { internal actual val miniEnvironmentVariables: Map by lazy { System.getenv() } actual object DefaultLogOutput : Logger.Output { + private val DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + override fun output(logger: Logger, level: Logger.Level, msg: Any?) { if (logger.nativeLogger == null) { logger.nativeLogger = java.util.logging.Logger.getLogger(logger.name).also { nativeLogger -> @@ -37,7 +40,7 @@ actual object DefaultLogOutput : Logger.Output { record.level.intValue() >= Level.WARNING.intValue() -> AnsiEscape.Color.YELLOW else -> AnsiEscape.Color.WHITE } - val time = DateTime.fromUnixMillis(record.millis).format(DateFormat.FORMAT2) + val time = DATE_FORMAT.format(Date(System.currentTimeMillis())) out.println("$time[${Thread.currentThread()}]: ${record.level}: ${record.loggerName} - ${record.message}".color(color)) } override fun flush() = Unit diff --git a/korlibs-logger/src@linux/korlibs/logger/Logger.linux.kt b/korlibs-logger/src@linux/korlibs/logger/Logger.linux.kt new file mode 100644 index 0000000000..cc158e6a3e --- /dev/null +++ b/korlibs-logger/src@linux/korlibs/logger/Logger.linux.kt @@ -0,0 +1,28 @@ +@file:OptIn(ExperimentalForeignApi::class) + +package korlibs.logger + +import kotlinx.cinterop.* + +actual object Console : BaseConsole() + +actual object DefaultLogOutput : Logger.Output { + override fun output(logger: Logger, level: Logger.Level, msg: Any?) = Logger.ConsoleLogOutput.output(logger, level, msg) +} + +internal actual val miniEnvironmentVariables: Map by lazy { + getEnvs() +} + +private fun getEnvs(): Map { + val out = LinkedHashMap() + val env = platform.posix.__environ + var n = 0 + while (true) { + val line = env?.get(n++)?.toKString() + if (line == null || line.isNullOrBlank()) break + val parts = line.split('=', limit = 2) + out[parts[0]] = parts.getOrElse(1) { parts[0] } + } + return out +} diff --git a/korlibs-logger/src@mingw/korlibs/logger/Logger.mingw.kt b/korlibs-logger/src@mingw/korlibs/logger/Logger.mingw.kt new file mode 100644 index 0000000000..302da51cb6 --- /dev/null +++ b/korlibs-logger/src@mingw/korlibs/logger/Logger.mingw.kt @@ -0,0 +1,42 @@ +@file:OptIn(ExperimentalForeignApi::class) + +package korlibs.logger + +import kotlinx.cinterop.* +import platform.windows.* + +actual object Console : BaseConsole() + +actual object DefaultLogOutput : Logger.Output { + override fun output(logger: Logger, level: Logger.Level, msg: Any?) = Logger.ConsoleLogOutput.output(logger, level, msg) +} + +internal actual val miniEnvironmentVariables: Map by lazy { getEnvs() } + +private fun readStringsz(ptr: CPointer?): List { + if (ptr == null) return listOf() + var n = 0 + var lastP = 0 + val out = arrayListOf() + while (true) { + val c: Int = ptr[n++].toInt() + if (c == 0) { + val startPtr: CPointer = (ptr + lastP)!! + val str = startPtr.toKString() + if (str.isEmpty()) break + out += str + lastP = n + } + } + return out +} + +private fun getEnvs(): Map { + val envs = GetEnvironmentStringsW() ?: return mapOf() + val lines = readStringsz(envs) + FreeEnvironmentStringsW(envs) + return lines.map { + val parts = it.split('=', limit = 2) + parts[0] to parts.getOrElse(1) { parts[0] } + }.toMap() +} diff --git a/korlibs-platform/src@darwin/korlibs/platform/Current.apple.kt b/korlibs-platform/src@native/korlibs/platform/Current.apple.kt similarity index 100% rename from korlibs-platform/src@darwin/korlibs/platform/Current.apple.kt rename to korlibs-platform/src@native/korlibs/platform/Current.apple.kt diff --git a/settings.gradle.kts b/settings.gradle.kts index f7271f4a4d..06ea2501a8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,6 +25,8 @@ fun isPropertyTrue(name: String): Boolean { val inCI = isPropertyTrue("CI") val disabledExtraKorgeLibs = isPropertyTrue("DISABLED_EXTRA_KORGE_LIBS") +include(":korlibs-concurrent") +include(":korlibs-logger") include(":korlibs-platform") include(":korlibs-datastructure") include(":korlibs-time") @@ -37,7 +39,6 @@ include(":korge-gradle-plugin") include(":korge-gradle-plugin-common") include(":korge-gradle-plugin-settings") include(":korge-reload-agent") -include("korlibs-logger") if (System.getenv("DISABLE_SANDBOX") != "true") { include(":korge-sandbox") }