Docs for thread and minor fixes and improvements
This commit is contained in:
parent
8fab4c9e57
commit
85e693aba8
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
@ -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")
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user