ThreadController interface extracted from Thread descriptor

This commit is contained in:
Andrew Golovashevich 2025-09-12 07:23:30 +03:00
parent 650213fc4c
commit 34debfcb7b
5 changed files with 219 additions and 188 deletions

View File

@ -4,11 +4,12 @@ import ru.landgrafhomyak.multitasking_0.fibers.Fiber
@Suppress("EXPECT_ACTUAL_IR_INCOMPATIBILITY")
public expect class Thread {
override fun toString(): String
/**
* Name of thread for debug purposes.
*/
public val name: String
override fun toString(): String
public companion object {
/**

View File

@ -2,8 +2,6 @@ package ru.landgrafhomyak.multitasking_0.threads
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.locks.LockSupport
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
import java.lang.Thread as jThread
import ru.landgrafhomyak.multitasking_0.fibers.Fiber
import ru.landgrafhomyak.multitasking_0.SingleThreadEventLoop
@ -11,129 +9,21 @@ import ru.landgrafhomyak.multitasking_0.WrongCallerThreadException
import ru.landgrafhomyak.multitasking_0.threads.sync.ResumeThreadCallback
@Suppress("AMBIGUOUS_EXPECTS", "ACTUAL_WITHOUT_EXPECT")
public actual sealed class Thread {
public actual class Thread(
@JvmField
internal val _nativeThread: jThread
) {
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
actual override fun toString(): String = "<platform native thread name='${this._nativeThread.name}'>"
public actual val name: String
get() {
this._sync.withLock {
if (this._isExplicitlyDestroyed)
throw IllegalStateException("Thread destroyed")
}
return this._nativeThread.name
}
get() = this._nativeThread.name
public actual val isDaemon: Boolean
get() {
this._sync.withLock {
if (this._isExplicitlyDestroyed)
throw IllegalStateException("Thread destroyed")
}
return this._nativeThread.isDaemon
}
public actual val state: State
get() {
this._sync.withLock {
if (this._isExplicitlyDestroyed)
return State.DESTROYED
when (this._nativeThread.state) {
jThread.State.NEW -> return State.NEW
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
else
return State.FINISHED_WITH_ERROR
}
}
}
}
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
when (this._nativeThread.state) {
jThread.State.NEW, jThread.State.RUNNABLE, jThread.State.BLOCKED, jThread.State.WAITING, jThread.State.TIMED_WAITING ->
throw IllegalStateException("Thread not finished yet")
jThread.State.TERMINATED -> throw IllegalStateException("Thread finished without uncaught exceptions")
}
}
}
public actual fun start() {
this._sync.withLock {
if (this._isExplicitlyDestroyed)
throw IllegalStateException("Thread already was started, finished and destroyed")
// stdlib's errors aren't verbose
when (this._nativeThread.state) {
jThread.State.NEW -> {}
jThread.State.TERMINATED -> throw IllegalStateException("Thread already was started and finished")
jThread.State.RUNNABLE, jThread.State.BLOCKED, jThread.State.WAITING, jThread.State.TIMED_WAITING ->
throw IllegalStateException("Thread already was started")
}
this._nativeThread.start()
}
}
/**
* Requires explicit synchronization
*/
protected fun _assertJoinAllowed() {
if (this._isExplicitlyDestroyed)
throw IllegalStateException("Can't join on destroyed thread")
if (this._nativeThread.state == jThread.State.NEW)
throw IllegalStateException("Can't join on thread that isn't started")
}
public actual abstract fun join()
public actual fun releaseResources() {
this._sync.withLock {
if (this._isExplicitlyDestroyed)
throw IllegalStateException("Thread already destroyed")
when (this._nativeThread.state) {
jThread.State.NEW -> {}
jThread.State.TERMINATED -> {}
jThread.State.RUNNABLE, jThread.State.BLOCKED, jThread.State.WAITING, jThread.State.TIMED_WAITING ->
throw IllegalStateException("Thread already was started")
}
this._isExplicitlyDestroyed = true
}
}
public actual enum class State {
NEW,
STARTED,
FINISHED_SUCCESSFULLY,
FINISHED_WITH_ERROR,
DESTROYED
}
private object CurrentThreadVariable : ThreadLocal<Thread?>() {
override fun initialValue(): Thread? {
return ExternalThread(jThread.currentThread())
return Thread(jThread.currentThread())
}
}
@ -146,8 +36,8 @@ public actual sealed class Thread {
get() = this._tl_currentThread.get()?._threadLocalMethods ?: throw RuntimeException("This java thread can't be wrapped")
@JvmStatic
public actual fun create(name: String, isDaemon: Boolean, routine: ThreadRoutine): Thread =
DrivenPlatformThread(name, isDaemon, routine)
public actual fun create(name: String, isDaemon: Boolean, routine: ThreadRoutine): ThreadController =
ThreadControllerImpl(name, isDaemon, routine)
}

View File

@ -0,0 +1,132 @@
package ru.landgrafhomyak.multitasking_0.threads
import java.util.concurrent.CountDownLatch
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
import java.lang.Thread as jThread
@Suppress("JoinDeclarationAndAssignment")
public class ThreadControllerImpl : ThreadController {
public override val thread: Thread
private val _sync: ReentrantLock = ReentrantLock()
private var _isExplicitlyDestroyed: Boolean = false
private var _uncaughtException: Throwable? = null
private val _threadFinishedWaiter: CountDownLatch
internal constructor(name: String, isDaemon: Boolean, routine: ThreadRoutine) {
this._threadFinishedWaiter = CountDownLatch(1)
this.thread = Thread(jThread.ofPlatform().name(name).daemon(isDaemon).unstarted(this.Kernel(routine)))
}
private inner class Kernel(private val _routine: ThreadRoutine) : Runnable {
override fun run() {
try {
Thread._tl_currentThread.set(this@ThreadControllerImpl.thread)
this._routine.runThread(this@ThreadControllerImpl.thread)
} catch (t: Throwable) {
this@ThreadControllerImpl._sync.withLock {
this@ThreadControllerImpl._uncaughtException = t
}
} finally {
this@ThreadControllerImpl._threadFinishedWaiter.countDown()
}
}
}
override fun toString(): String = "<controller of platform native thread '${this.thread._nativeThread.name}'>"
public override val isDaemon: Boolean
get() {
this._sync.withLock {
if (this._isExplicitlyDestroyed)
throw IllegalStateException("Thread destroyed")
}
return this.thread._nativeThread.isDaemon
}
public override val state: ThreadController.State
get() {
this._sync.withLock {
if (this._isExplicitlyDestroyed)
return ThreadController.State.DESTROYED
when (this.thread._nativeThread.state) {
jThread.State.NEW -> return ThreadController.State.NEW
jThread.State.RUNNABLE -> return ThreadController.State.STARTED
jThread.State.BLOCKED -> return ThreadController.State.STARTED
jThread.State.WAITING -> return ThreadController.State.STARTED
jThread.State.TIMED_WAITING -> return ThreadController.State.STARTED
jThread.State.TERMINATED -> {
if (this._uncaughtException == null)
return ThreadController.State.FINISHED_SUCCESSFULLY
else
return ThreadController.State.FINISHED_WITH_ERROR
}
}
}
}
public override val uncaughtException: Throwable
get() {
this._sync.withLock {
if (this._isExplicitlyDestroyed)
throw IllegalStateException("Thread destroyed")
val e = this._uncaughtException
if (e != null)
return e
when (this.thread._nativeThread.state) {
jThread.State.NEW, jThread.State.RUNNABLE, jThread.State.BLOCKED, jThread.State.WAITING, jThread.State.TIMED_WAITING ->
throw IllegalStateException("Thread not finished yet")
jThread.State.TERMINATED -> throw IllegalStateException("Thread finished without uncaught exceptions")
}
}
}
public override fun start() {
this._sync.withLock {
if (this._isExplicitlyDestroyed)
throw IllegalStateException("Thread already was started, finished and destroyed")
// stdlib's errors aren't verbose
when (this.thread._nativeThread.state) {
jThread.State.NEW -> {}
jThread.State.TERMINATED -> throw IllegalStateException("Thread already was started and finished")
jThread.State.RUNNABLE, jThread.State.BLOCKED, jThread.State.WAITING, jThread.State.TIMED_WAITING ->
throw IllegalStateException("Thread already was started")
}
this.thread._nativeThread.start()
}
}
override fun join() {
this._sync.withLock {
if (this._isExplicitlyDestroyed)
throw IllegalStateException("Can't join on destroyed thread")
if (this.thread._nativeThread.state == jThread.State.NEW)
throw IllegalStateException("Can't join on thread that isn't started")
}
// stdlib's join uses Object.wait which may block caller virtual thread, so let wait in way virtual threads support
this._threadFinishedWaiter.await()
this.thread._nativeThread.join()
}
public override fun releaseResources() {
this._sync.withLock {
if (this._isExplicitlyDestroyed)
throw IllegalStateException("Thread already destroyed")
when (this.thread._nativeThread.state) {
jThread.State.NEW -> {}
jThread.State.TERMINATED -> {}
jThread.State.RUNNABLE, jThread.State.BLOCKED, jThread.State.WAITING, jThread.State.TIMED_WAITING ->
throw IllegalStateException("Thread already was started")
}
this._isExplicitlyDestroyed = true
}
}
}

View File

@ -8,77 +8,12 @@ import ru.landgrafhomyak.multitasking_0.fibers.Fiber
@ExpectRefinement
@Suppress("EXPECT_ACTUAL_IR_INCOMPATIBILITY")
public expect class Thread {
/**
* Name of thread for debug purposes.
*
* After [releaseResources()][Thread.releaseResources] become inaccessible.
*/
public val name: String
override fun toString(): String
/**
* 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.
* Name of thread for debug purposes.
*/
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()
/**
* 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,
/**
* 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 val name: String
public companion object {
/**
@ -101,7 +36,7 @@ public expect class Thread {
* @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 create(name: String, isDaemon: Boolean, routine: ThreadRoutine): ThreadController
}
}

View File

@ -0,0 +1,73 @@
package ru.landgrafhomyak.multitasking_0.threads
public interface ThreadController {
/**
* Thread controlled by this instance.
*/
public val thread: Thread
/**
* 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()][ThreadController.releaseResources] call allowed.
*
* Calling this method before thread [started][ThreadController.start] is error.
*/
public fun join()
/**
* Confirms that all resources used by underlying functionality are released.
* Can be called only once.
* Can be called before thread [started][ThreadController.start] or after thread [finishes][ThreadController.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][ThreadController.State.FINISHED_WITH_ERROR] state,
* in other [states][ThreadController.state] will throw error, even after [releaseResources()][ThreadController.releaseResources].
*/
public val uncaughtException: Throwable
/**
* Returns `false` if thread prevents process from being finished while thread is running.
*
* After [releaseResources()][ThreadController.releaseResources] become inaccessible.
*/
public val isDaemon: Boolean
public enum class State {
/**
* Thread [created][Thread.create] but not [started][ThreadController.start] yet.
*/
NEW,
/**
* Thread [started][ThreadController.start], but not finished yet.
*/
STARTED,
/**
* Thread finished ([routine][ThreadRoutine] returned) normally.
*/
FINISHED_SUCCESSFULLY,
/**
* Thread [finished with error][ThreadController.uncaughtException], that wasn't caught by [routine][ThreadRoutine].
*/
FINISHED_WITH_ERROR,
/**
* All resources allocated by this [ThreadController] object are released and reference to descriptor can be lost.
*/
DESTROYED
}
}