From 34debfcb7be06c63eab42a83e3e113c8923ac025 Mon Sep 17 00:00:00 2001 From: Andrew Golovashevich Date: Fri, 12 Sep 2025 07:23:30 +0300 Subject: [PATCH] ThreadController interface extracted from Thread descriptor --- .../multitasking_0/threads/Thread.kt | 3 +- .../multitasking_0/threads/Thread.kt | 128 ++--------------- .../threads/ThreadControllerImpl.kt | 132 ++++++++++++++++++ .../multitasking_0/threads/Thread.kt | 71 +--------- .../threads/ThreadController.kt | 73 ++++++++++ 5 files changed, 219 insertions(+), 188 deletions(-) create mode 100644 src/jvmMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/ThreadControllerImpl.kt create mode 100644 src/multithreadPlatformMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/ThreadController.kt diff --git a/src/commonMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/Thread.kt b/src/commonMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/Thread.kt index a11f0db..76d9a9f 100644 --- a/src/commonMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/Thread.kt +++ b/src/commonMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/Thread.kt @@ -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 { /** diff --git a/src/jvmMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/Thread.kt b/src/jvmMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/Thread.kt index 4cf552f..f3a26f1 100644 --- a/src/jvmMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/Thread.kt +++ b/src/jvmMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/Thread.kt @@ -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 = "" 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() { 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) } diff --git a/src/jvmMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/ThreadControllerImpl.kt b/src/jvmMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/ThreadControllerImpl.kt new file mode 100644 index 0000000..8565295 --- /dev/null +++ b/src/jvmMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/ThreadControllerImpl.kt @@ -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 = "" + + 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 + } + } +} \ No newline at end of file diff --git a/src/multithreadPlatformMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/Thread.kt b/src/multithreadPlatformMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/Thread.kt index 96f279e..9df70e5 100644 --- a/src/multithreadPlatformMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/Thread.kt +++ b/src/multithreadPlatformMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/Thread.kt @@ -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 } } diff --git a/src/multithreadPlatformMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/ThreadController.kt b/src/multithreadPlatformMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/ThreadController.kt new file mode 100644 index 0000000..67df646 --- /dev/null +++ b/src/multithreadPlatformMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/ThreadController.kt @@ -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 + } +} +