Docs for thread and minor fixes and improvements

This commit is contained in:
Andrew Golovashevich 2025-09-09 11:47:25 +03:00
parent 8fab4c9e57
commit 85e693aba8
10 changed files with 306 additions and 31 deletions

View File

@ -0,0 +1,10 @@
package ru.landgrafhomyak.multitasking_0
import kotlin.RuntimeException
public class WrongCallerThreadException : RuntimeException {
public constructor() : super()
public constructor(message: String?) : super(message)
public constructor(message: String?, cause: Throwable?) : super(message, cause)
public constructor(cause: Throwable?) : super(cause)
}

View File

@ -3,12 +3,24 @@ package ru.landgrafhomyak.multitasking_0.threads
import ru.landgrafhomyak.multitasking_0.Fiber
public expect class Thread {
/**
* Name of thread for debug purposes.
*/
public val name: String
override fun toString(): String
public var runningFiber: Fiber?
public companion object {
public fun currentThread(): Thread
/**
* Thread-local getter that returns object with access to some thread-local meta-information.
*
* Passing this object to another thread is not allowed and his members will throw error.
*
*
* ***Java's [virtual threads](https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html) note:***
* If called inside [virtual thread used as fiber][ru.landgrafhomyak.multitasking_0.threads.impl.java_virtual_threads.JavaVirtualThreadFiber],
* it will return object associated with [Fiber.resumedOnThread] instead of actual thread.
* If a virtual thread is created another way, it will return its own descriptor.
*/
public val current: ThreadLocalMethods
}
}

View File

@ -0,0 +1,58 @@
package ru.landgrafhomyak.multitasking_0.threads
import ru.landgrafhomyak.multitasking_0.Fiber
import ru.landgrafhomyak.multitasking_0.SingleThreadEventLoop
import ru.landgrafhomyak.multitasking_0.WrongCallerThreadException
public expect sealed interface ThreadLocalMethods {
/**
* Returns descriptor of current thread.
*
* @throws WrongCallerThreadException if called on thread different that caller of [Thread.current].
*/
public fun get(): Thread
/**
* Returns descriptor of [fiber][Fiber] that is currently [running (or emulates running)][Fiber.resume]
* in this thread.
*
* If set, condition `Thread.current.runningFiber.resumedOnThread === Thread.current.get()` always true
*
* [Fiber][Fiber] implementations must control this property explicitly with [enterFiber()][ThreadLocalMethods.enterFiber]
* and [exitFiber()][ThreadLocalMethods.exitFiber]
*
* @throws WrongCallerThreadException if called on thread different that caller of [Thread.current].
*/
public val runningFiber: Fiber?
/**
* Informs thread that execution flow was switched to [fiber][Fiber].
*
* @param fiberToEnter descriptor of fiber that is [going to run][Fiber.resume].
* @param privateToken secret reference that should be used to call [exitFiber()][ThreadLocalMethods.exitFiber].
* Need to avoid clearing [information about running fiber][ThreadLocalMethods.runningFiber]
* by anyone except implementation of this fiber.
* @return [fiber][Fiber] in which this function was called or `null` if it was called in thread.
*
* @throws WrongCallerThreadException if called on thread different that caller of [Thread.current].
*/
public fun enterFiber(fiberToEnter: Fiber, privateToken: Any): Fiber?
/**
* Informs thread that execution flow was returned from [fiber][Fiber].
*
* @param fiberToExit descriptor of fiber that is [going to return execution flow][Fiber.yield].
* @param privateToken secret reference that was passed to [enterFiber()][ThreadLocalMethods.exitFiber].
* Used to check that this function called by same caller as [enterFiber()][ThreadLocalMethods.exitFiber].
* @param fiberToRestore return value of corresponding [enterFiber()][ThreadLocalMethods.exitFiber] call.
*
* @throws IllegalStateException if [fiberToExit] or [fiberToRestore] doesn't match.
* @throws IllegalArgumentException if [privateToken] doesn't match.
* @throws WrongCallerThreadException if called on thread different that caller of [Thread.current].
*/
public fun exitFiber(fiberToExit: Fiber, privateToken: Any, fiberToRestore: Fiber?)
public val runningEventLoop: SingleThreadEventLoop?
public fun setEventLoop(eventLoop: SingleThreadEventLoop, privateToken: Any)
public fun clearEventLoop(eventLoop: SingleThreadEventLoop, privateToken: Any)
}

View File

@ -0,0 +1,21 @@
package ru.landgrafhomyak.multitasking_0.threads
import kotlin.jvm.JvmField
import ru.landgrafhomyak.multitasking_0.Fiber
internal class _FibersStackNode(
@JvmField
val caller: _FibersStackNode?,
@JvmField
val fiber: Fiber,
@JvmField
val privateToken: Any,
) {
fun assertExit(fiberToExit: Fiber, privateToken: Any, fiberToRestore: Fiber?) {
if (this.fiber !== fiberToExit || this.caller?.fiber !== fiberToRestore)
throw IllegalStateException("fiberToExit or fiberToRestore doesn't match")
if (this.privateToken !== privateToken)
throw IllegalArgumentException("privateToken doesn't match")
}
}

View File

@ -2,12 +2,11 @@ package ru.landgrafhomyak.multitasking_0.threads
import java.util.concurrent.CountDownLatch
import java.lang.Thread as jThread
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
@Suppress("JoinDeclarationAndAssignment")
internal class DrivenPlatformThread : Thread {
override fun toString(): String = TODO()
override fun toString(): String = "<native platform thread (externally created) name='${this.name}'${if (this._nativeThread.isVirtual) " (virtual)" else ""}>"
override val _nativeThread: jThread

View File

@ -4,7 +4,7 @@ import java.lang.Thread as jThread
import kotlin.concurrent.withLock
internal class ExternalThread : Thread {
override fun toString(): String = TODO()
override fun toString(): String = "<native platform thread name='${this.name}'>"
override val _nativeThread: jThread

View File

@ -4,24 +4,38 @@ import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
import java.lang.Thread as jThread
import ru.landgrafhomyak.multitasking_0.Fiber
import ru.landgrafhomyak.multitasking_0.SingleThreadEventLoop
import ru.landgrafhomyak.multitasking_0.WrongCallerThreadException
@Suppress("AMBIGUOUS_EXPECTS", "ACTUAL_WITHOUT_EXPECT")
public actual sealed class Thread {
public actual var runningFiber: Fiber? = null
private val _threadLocalMethods = this.ThreadLocalMethodsImpl()
protected abstract val _nativeThread: jThread
protected val _sync: ReentrantLock = ReentrantLock()
protected var _isExplicitlyDestroyed: Boolean = false
protected var _uncaughtException: Throwable? = null
actual abstract override fun toString(): String
public actual val name: String
get() = this._nativeThread.name
get() {
this._sync.withLock {
if (this._isExplicitlyDestroyed)
throw IllegalStateException("Thread destroyed")
}
return this._nativeThread.name
}
public actual val isDaemon: Boolean
get() = this._nativeThread.isDaemon
get() {
this._sync.withLock {
if (this._isExplicitlyDestroyed)
throw IllegalStateException("Thread destroyed")
}
return this._nativeThread.isDaemon
}
public actual val state: State
get() {
this._sync.withLock {
@ -30,10 +44,10 @@ public actual sealed class Thread {
when (this._nativeThread.state) {
jThread.State.NEW -> return State.NEW
jThread.State.RUNNABLE -> return State.NOT_FINISHED
jThread.State.BLOCKED -> return State.NOT_FINISHED
jThread.State.WAITING -> return State.NOT_FINISHED
jThread.State.TIMED_WAITING -> return State.NOT_FINISHED
jThread.State.RUNNABLE -> return State.STARTED
jThread.State.BLOCKED -> return State.STARTED
jThread.State.WAITING -> return State.STARTED
jThread.State.TIMED_WAITING -> return State.STARTED
jThread.State.TERMINATED -> {
if (this._uncaughtException == null)
return State.FINISHED_SUCCESSFULLY
@ -47,6 +61,9 @@ public actual sealed class Thread {
public actual val uncaughtException: Throwable
get() {
this._sync.withLock {
if (this._isExplicitlyDestroyed)
throw IllegalStateException("Thread destroyed")
val e = this._uncaughtException
if (e != null)
return e
@ -105,7 +122,7 @@ public actual sealed class Thread {
public actual enum class State {
NEW,
NOT_FINISHED,
STARTED,
FINISHED_SUCCESSFULLY,
FINISHED_WITH_ERROR,
DESTROYED
@ -122,11 +139,80 @@ public actual sealed class Thread {
internal val _tl_currentThread: ThreadLocal<Thread?> get() = CurrentThreadVariable
@JvmStatic
public actual fun create(name: String, isDaemon: Boolean, routine: ThreadRoutine): Thread =
DrivenPlatformThread(name, isDaemon, routine)
public actual val current: ThreadLocalMethods
get() = this._tl_currentThread.get()?._threadLocalMethods ?: throw RuntimeException("This java thread can't be wrapped")
@JvmStatic
public actual fun currentThread(): Thread =
this._tl_currentThread.get() ?: throw RuntimeException("This java thread can't be wrapped")
public actual fun create(name: String, isDaemon: Boolean, routine: ThreadRoutine): Thread =
DrivenPlatformThread(name, isDaemon, routine)
}
private inner class ThreadLocalMethodsImpl : ThreadLocalMethods {
private fun _assertThread() {
if (Thread._tl_currentThread.get() != this@Thread)
throw WrongCallerThreadException("Reference returned by 'Thread.current' must be used only in thread where it was produced")
}
override fun get(): Thread {
this._assertThread()
return this@Thread
}
private var _fibersStack: _FibersStackNode? = null
override val runningFiber: Fiber?
get() {
this._assertThread()
return this._fibersStack?.fiber
}
override fun enterFiber(fiberToEnter: Fiber, privateToken: Any): Fiber? {
this._assertThread()
val caller = this._fibersStack
this._fibersStack = _FibersStackNode(caller, fiberToEnter, privateToken)
return caller?.fiber
}
override fun exitFiber(fiberToExit: Fiber, privateToken: Any, fiberToRestore: Fiber?) {
this._assertThread()
val top = this._fibersStack
if (top == null)
throw IllegalStateException("There is no running fiber to exit")
top.assertExit(fiberToExit, privateToken, fiberToRestore)
this._fibersStack = top.caller
}
private var _runningEventLoop: SingleThreadEventLoop? = null
private var _runningEventLoopToken: Any? = null
override val runningEventLoop: SingleThreadEventLoop?
get() {
this._assertThread()
return this._runningEventLoop
}
override fun setEventLoop(eventLoop: SingleThreadEventLoop, privateToken: Any) {
this._assertThread()
if (this._runningEventLoop != null)
throw IllegalStateException("There is already a running event loop here")
this._runningEventLoop = eventLoop
this._runningEventLoopToken = privateToken
}
override fun clearEventLoop(eventLoop: SingleThreadEventLoop, privateToken: Any) {
this._assertThread()
if (this._runningEventLoop !== eventLoop)
throw IllegalStateException("Currently another event loop is running")
if (this._runningEventLoopToken !== privateToken)
throw IllegalArgumentException("privateToken doesn't match")
this._runningEventLoop = null
this._runningEventLoopToken = null
}
}
}

View File

@ -0,0 +1,16 @@
package ru.landgrafhomyak.multitasking_0.threads
import ru.landgrafhomyak.multitasking_0.Fiber
import ru.landgrafhomyak.multitasking_0.SingleThreadEventLoop
public actual sealed interface ThreadLocalMethods {
public actual fun get(): Thread
public actual val runningFiber: Fiber?
public actual fun enterFiber(fiberToEnter: Fiber, privateToken: Any): Fiber?
public actual fun exitFiber(fiberToExit: Fiber, privateToken: Any, fiberToRestore: Fiber?)
public actual val runningEventLoop: SingleThreadEventLoop?
public actual fun setEventLoop(eventLoop: SingleThreadEventLoop, privateToken: Any)
public actual fun clearEventLoop(eventLoop: SingleThreadEventLoop, privateToken: Any)
}

View File

@ -1,5 +1,6 @@
package ru.landgrafhomyak.multitasking_0.threads.impl.java_virtual_threads
import java.lang.Object as jObject
import java.lang.Thread as jThread
import java.util.concurrent.locks.Condition
import java.util.concurrent.locks.ReentrantLock
@ -7,6 +8,7 @@ import kotlin.concurrent.withLock
import ru.landgrafhomyak.multitasking_0.ExecutionState
import ru.landgrafhomyak.multitasking_0.Fiber
import ru.landgrafhomyak.multitasking_0.FiberRoutine
import ru.landgrafhomyak.multitasking_0.WrongCallerThreadException
import ru.landgrafhomyak.multitasking_0.threads.Thread as wThread
public class JavaVirtualThreadFiber : Fiber {
@ -64,7 +66,7 @@ public class JavaVirtualThreadFiber : Fiber {
override fun yield() {
if (jThread.currentThread() !== this._thisVThread)
TODO("err")
throw WrongCallerThreadException("yield() must be called only inside it's fiber")
this._syncLock.withLock {
when (this._state) {
@ -101,15 +103,15 @@ public class JavaVirtualThreadFiber : Fiber {
ExecutionState.FINISHED_SUCCESSFULLY, ExecutionState.FINISHED_WITH_ERROR -> throw IllegalStateException("Fiber already finished")
}
val currentThread = wThread.currentThread()
this._resumedOnThread = currentThread
this._resumedOnFiber = currentThread.runningFiber
currentThread.runningFiber = this
val token = jObject()
val currentThread = wThread.current
this._resumedOnThread = currentThread.get()
this._resumedOnFiber = currentThread.enterFiber(this, token)
this._syncCond.signal()
this._syncCond.await()
currentThread.runningFiber = this._resumedOnFiber
currentThread.exitFiber(this, token, this.resumedOnFiber)
this._resumedOnThread = null
this._resumedOnFiber = null
}

View File

@ -3,33 +3,104 @@ package ru.landgrafhomyak.multitasking_0.threads
import kotlin.experimental.ExpectRefinement
import ru.landgrafhomyak.multitasking_0.Fiber
// didn't work because AMBIGUOUS_EXPECTS
@OptIn(ExperimentalMultiplatform::class)
@ExpectRefinement
public expect class Thread {
/**
* Name of thread for debug purposes.
*
* After [releaseResources()][Thread.releaseResources] become inaccessible.
*/
public val name: String
override fun toString(): String
public var runningFiber: Fiber?
/**
* Start execution of [routine][ThreadRoutine] passed to [create()][Thread.create].
*
* Doesn't define when any resources used by underlying functionality (e.g., stack) must be allocated.
*/
public fun start()
/**
* Blocks caller thread or fiber until [releaseResources()][Thread.releaseResources] call allowed.
*
* Calling this method before thread [started][Thread.start] is error.
*/
public fun join()
public val state: State
public val uncaughtException: Throwable
/**
* Confirms that all resources used by underlying functionality are released.
* Can be called only once.
* Can be called before thread [started][Thread.start] or after thread [finishes][Thread.join].
*/
public fun releaseResources()
/**
* State of thread. Always accessible.
*/
public val state: State
/**
* Exception that wasn't uncaught by [routine][ThreadRoutine] and terminated thread.
*
* Accessible only in [FINISHED_WITH_ERROR][Thread.State.FINISHED_WITH_ERROR] state,
* in other [states][Thread.state] will throw error, even after [releaseResources()][Thread.releaseResources].
*/
public val uncaughtException: Throwable
/**
* Returns `false` if thread prevents process from being finished while thread is running.
*
* After [releaseResources()][Thread.releaseResources] become inaccessible.
*/
public val isDaemon: Boolean
public enum class State {
/**
* Thread [created][Thread.create] but not [started][Thread.start] yet.
*/
NEW,
NOT_FINISHED,
/**
* Thread [started][Thread.start], but not finished yet.
*/
STARTED,
/**
* Thread finished ([routine][ThreadRoutine] returned) normally.
*/
FINISHED_SUCCESSFULLY,
/**
* Thread [finished with error][Thread.uncaughtException], that wasn't caught by [routine][ThreadRoutine].
*/
FINISHED_WITH_ERROR,
/**
* All resources allocated by this [Thread] object are released and reference to descriptor can be lost.
*/
DESTROYED
}
public companion object {
/**
* Thread-local getter that returns object with access to some thread-local meta-information.
*
* Passing this object to another thread is not allowed and his members will throw error.
*
*
* ***Java's [virtual threads](https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html) note:***
* If called inside [virtual thread used as fiber][ru.landgrafhomyak.multitasking_0.threads.impl.java_virtual_threads.JavaVirtualThreadFiber],
* it will return object associated with [Fiber.resumedOnThread] instead of actual thread.
* If a virtual thread is created another way, it will return its own descriptor.
*/
public val current: ThreadLocalMethods
/**
* Creates new [thread][Thread] in [unstarted][Thread.State.NEW] state.
*
* @param name name of thread for debug purposes.
* @param isDaemon should thread prevent process from being finished while thread is running.
* @param routine
*/
public fun create(name: String, isDaemon: Boolean, routine: ThreadRoutine): Thread
public fun currentThread(): Thread
}
}