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

Introduce korlibs-concurrent (Lock + NativeThread) + add logger, datastructure and platform to K/N desktop targets #2132

Merged
merged 6 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • imports are not adviced since they make it hard to determine which classes are from which imports.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rationale behind the star imports:

KorGE uses star imports because it relies a lot on extension members. It was a pain in the ass adding all those members manually for people starting. Users typically don't care where classes come from as long as the code works.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but it makes it pain in the ass to refactor. For our current task forexample it is important to see which methods are used from which packages, when we want to reduce the dependency towards a package.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why it is a pain in the ass? Doesn't @Deprecated has a parameter to specify replacement to do it automatically inside the IDE?

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not leave in comment code in PR. YAGNI: You aren't gonna need it. If you do, you can readd it from earlier commit.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is old code that was like that, I'm okay with removing those lines. So feel free to do so.

//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
Loading