Thread implementation for windows

This commit is contained in:
Andrew Golovashevich 2025-09-15 08:25:07 +03:00
parent 67079c1565
commit b570518b03
10 changed files with 867 additions and 4 deletions

View File

@ -56,6 +56,16 @@ kotlin {
}
}
mingwX64().compilations["main"].cinterops {
create("windows") {
definitionFile = rootDir.resolve("src/windowsMain/c/windows.c2kt_def")
packageName = "ru.landgrafhomyak.multitasking_0._c"
includeDirs(rootDir.resolve("src/windowsMain/c/Include"))
}
}
sourceSets {
val commonMain by getting {
dependencies {
@ -72,10 +82,16 @@ kotlin {
mingwX64().compilations["main"].defaultSourceSet {
dependencies {
implementation("ru.landgrafhomyak.utility:highlevel-try-finally:0.6")
implementation("ru.landgrafhomyak.utility:closeable-state-1:1.2")
implementation("ru.landgrafhomyak.utility:closeable-state-1:1.3")
implementation("ru.landgrafhomyak.utility:kotlin-native-interop-utilities-0:0.1")
}
}
mingwX64().compilations["test"].defaultSourceSet {
dependencies {
implementation(kotlin("test"))
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.7.1")
}
}
val notJvmMain by creating { dependsOn(commonMain) }
val notJvmTest by creating { dependsOn(commonTest) }
@ -109,6 +125,15 @@ kotlin {
val windowsTest by creating { dependsOn(multithreadPlatformTest) }
mingwX64().compilations["main"].defaultSourceSet.dependsOn(windowsMain)
mingwX64().compilations["test"].defaultSourceSet.dependsOn(windowsTest)
val x64Main by creating { dependsOn(commonMain) }
val x64Test by creating { dependsOn(commonTest) }
mingwX64().compilations["main"].defaultSourceSet.dependsOn(x64Main)
mingwX64().compilations["test"].defaultSourceSet.dependsOn(x64Test)
linuxX64().compilations["main"].defaultSourceSet.dependsOn(x64Main)
linuxX64().compilations["test"].defaultSourceSet.dependsOn(x64Test)
macosX64().compilations["main"].defaultSourceSet.dependsOn(x64Main)
macosX64().compilations["test"].defaultSourceSet.dependsOn(x64Test)
}
}

View File

@ -2,3 +2,4 @@ kotlin.native.ignoreDisabledTargets=true
kotlin.mpp.applyDefaultHierarchyTemplate=false
kotlin.native.enableKlibsCrossCompilation=true
kotlin.stdlib.default.dependency=false
kotlin.mpp.enableCInteropCommonization=true

View File

@ -7,7 +7,7 @@ import ru.landgrafhomyak.multitasking_0.WrongCallerThreadException
import ru.landgrafhomyak.multitasking_0.threads.sync.ResumeThreadCallback
@Suppress("AMBIGUOUS_EXPECTS", "ACTUAL_WITHOUT_EXPECT")
public actual class Thread(
public actual class Thread internal constructor(
@JvmField
internal val _nativeThread: jThread,
) {
@ -42,7 +42,7 @@ public actual class Thread(
private class ThreadLocalMethodsImpl(thread: Thread) : _CommonThreadLocalMethods(thread), ThreadLocalMethods {
override fun _assertCurrentThread() {
if (Thread._tl_currentThread.get() !== this.thread)
throw WrongCallerThreadException("Reference returned by 'Thread.current' must be used only in thread where it was produced")
throw WrongCallerThreadException("Object returned by 'Thread.current' must be used only in thread where it was produced")
}
override fun yield() {

View File

@ -0,0 +1,117 @@
#pragma once
#include <windows.h>
#if 0
# define ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_USE_FUTEX
#endif
struct ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC {
CRITICAL_SECTION data__sync;
CONDITION_VARIABLE data__sync_notify;
CRITICAL_SECTION *sync;
CONDITION_VARIABLE *sync_notify;
#ifndef ru_landgrafhomyak_multitasking_0_threads_ExternalThreadDescriptorsGC_USE_FUTEX
CRITICAL_SECTION data__thread_ready_cs;
CONDITION_VARIABLE data__thread_ready_cv;
CRITICAL_SECTION *thread_ready_cs;
CONDITION_VARIABLE *thread_ready_cv;
#endif
};
static void ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_init(
struct ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC *self
) {
self->sync = &(self->data__sync);
self->sync_notify = &(self->data__sync_notify);
InitializeCriticalSection(self->sync);
#ifndef ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_USE_FUTEX
self->thread_ready_cs = &(self->data__thread_ready_cs);
self->thread_ready_cv = &(self->data__thread_ready_cv);
InitializeCriticalSection(self->thread_ready_cs);
InitializeConditionVariable(self->thread_ready_cv);
#endif
}
static void ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_deinit(
struct ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC *self
) {
DeleteCriticalSection(self->sync);
#ifndef ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_USE_FUTEX
DeleteCriticalSection(self->thread_ready_cs);
#endif
}
struct ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage {
SIZE_T slots_used;
HANDLE handles[MAXIMUM_WAIT_OBJECTS];
void *wrappers[MAXIMUM_WAIT_OBJECTS - 1];
};
static void ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage_init(
struct ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage *self,
HANDLE notifier
) {
self->slots_used = 0;
self->handles[0] = notifier;
}
static _Bool ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage_tryAdd(
struct ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage *self,
HANDLE handle,
void *wrapper
) {
if (self->slots_used >= MAXIMUM_WAIT_OBJECTS - 1)
return 1;
self->wrappers[self->slots_used++] = wrapper;
self->handles[self->slots_used] = handle;
return 0;
}
static DWORD ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage_wait(
struct ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage *self,
SIZE_T slots_count_cached
) {
return WaitForMultipleObjects(
slots_count_cached + 1,
self->handles,
FALSE,
0
);
}
static void *ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage_removeSlotAndCloseHandle(
struct ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage *self,
SIZE_T pos
) {
pos--;
void *wrapper = self->wrappers[pos];
if (0 == CloseHandle(self->handles[pos + 1]))
return NULL;
for (SIZE_T i = pos + 1; i < self->slots_used; i++) {
self->handles[i] = self->handles[i + 1];
self->wrappers[i - 1] = self->wrappers[i];
}
self->slots_used--;
return wrapper;
}
static SIZE_T ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage_shitToNextWorker(
struct ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage *self,
struct ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage *next
) {
if (next != NULL) {
while (next->slots_used < (MAXIMUM_WAIT_OBJECTS - 1) && self->slots_used > 0) {
self->slots_used--;
next->wrappers[next->slots_used++] = self->wrappers[self->slots_used];
next->handles[next->slots_used] = self->handles[self->slots_used + 1];
}
}
return self->slots_used;
}

View File

@ -0,0 +1,14 @@
#pragma once
#include <windows.h>
static inline HANDLE ru_landgrafhomyak_multitasking_0_duplicateHandle(HANDLE handle) {
HANDLE new_handle = NULL;
DuplicateHandle(
GetCurrentProcess(), handle,
GetCurrentProcess(), &new_handle,
0, FALSE, DUPLICATE_SAME_ACCESS
);
return new_handle;
}

View File

@ -0,0 +1,2 @@
headers = ru/landgrafhomyak/multitasking_0/utility.h ru/landgrafhomyak/multitasking_0/handle_and_pointer_background_garbage_collector.h
headerFilter = ru/landgrafhomyak/multitasking_0/**.h

View File

@ -0,0 +1,184 @@
package ru.landgrafhomyak.multitasking_0
import kotlinx.cinterop.COpaquePointer
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.StableRef
import kotlinx.cinterop.reinterpret
import platform.windows.CRITICAL_SECTION
import platform.windows.CloseHandle
import platform.windows.CreateEventA
import platform.windows.EnterCriticalSection
import platform.windows.FALSE
import platform.windows.HANDLE
import platform.windows.HEAP_NO_SERIALIZE
import platform.windows.HeapAlloc
import platform.windows.HeapCreate
import platform.windows.HeapDestroy
import platform.windows.HeapFree
import platform.windows.InitializeCriticalSection
import platform.windows.LeaveCriticalSection
import platform.windows.MAXIMUM_WAIT_OBJECTS
import platform.windows.SetEvent
import platform.windows.WAIT_ABANDONED_0
import platform.windows.WAIT_FAILED
import platform.windows.WAIT_OBJECT_0
import platform.windows.WAIT_TIMEOUT
import ru.landgrafhomyak.multitasking_0._c.ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC
import ru.landgrafhomyak.multitasking_0._c.ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage
import ru.landgrafhomyak.multitasking_0._c.ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage_init
import ru.landgrafhomyak.multitasking_0._c.ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage_removeSlotAndCloseHandle
import ru.landgrafhomyak.multitasking_0._c.ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage_shitToNextWorker
import ru.landgrafhomyak.multitasking_0._c.ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage_tryAdd
import ru.landgrafhomyak.multitasking_0._c.ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage_wait
import ru.landgrafhomyak.multitasking_0.threads.Thread
import ru.landgrafhomyak.multitasking_0.threads.ThreadController
import ru.landgrafhomyak.multitasking_0.threads.ThreadRoutine
import ru.landgrafhomyak.utility.closeable_state_1.CloseableState
import ru.landgrafhomyak.utility.closeable_state_1.ManualStateManipulation
import ru.landgrafhomyak.utility.closeable_state_1.OwnedUsagesCounter
import ru.landgrafhomyak.utility.closeable_state_1.withUse
import ru.landgrafhomyak.utility.highlevel_try_finally.safeAutoClose1
import ru.landgrafhomyak.utility.highlevel_try_finally.safeAutoClose2
import ru.landgrafhomyak.utility.kotlin_native_interop_stdlib_0.sizeOfUL
import ru.landgrafhomyak.utility.kotlin_native_interop_stdlib_0.windows.WindowsApiException
import ru.landgrafhomyak.utility.kotlin_native_interop_stdlib_0.windows.nullToWinApiErr
import ru.landgrafhomyak.utility.kotlin_native_interop_stdlib_0.windows.zeroToWinApiErr
/**
*
*/
@Suppress("JoinDeclarationAndAssignment")
@OptIn(ExperimentalForeignApi::class)
internal abstract class HandleAndPointerBackgroundGarbageCollector {
protected abstract fun _destroyPtr(ptr: COpaquePointer)
protected abstract fun _generateThreadName(): String
private val _heap: HANDLE
private val _sync: CPointer<CRITICAL_SECTION>
private val _stableRef: StableRef<HandleAndPointerBackgroundGarbageCollector>
private var _lastWorker: WorkerData?
protected val _state: CloseableState.AllowsConcurrency
init {
this._state = OwnedUsagesCounter(this)
this._lastWorker = null
this._stableRef = StableRef.create(this)
safeAutoClose2(onError = { this._stableRef.dispose() }) {
this._heap = nullToWinApiErr { HeapCreate(HEAP_NO_SERIALIZE.toUInt(), sizeOfUL<ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC>(), 0u) }
safeAutoClose2(onError = { HeapDestroy(this._heap) }) {
this._sync = nullToWinApiErr { HeapAlloc(this._heap, 0u, sizeOfUL<CRITICAL_SECTION>()) }.reinterpret()
InitializeCriticalSection(this._sync)
}
}
}
protected fun _register(handle: HANDLE, wrapper: COpaquePointer) = this._state.withUse {
EnterCriticalSection(this._sync)
safeAutoClose1(finally = { LeaveCriticalSection(this._sync) }) {
var worker = this._lastWorker ?: this._createNewWorker()
while (ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage_tryAdd(worker.handlesStorage, handle, wrapper)) {
worker = this._createNewWorker()
}
zeroToWinApiErr { SetEvent(worker.notifier) }
}
}
private fun _createNewWorker(): WorkerData {
val notifier = nullToWinApiErr { CreateEventA(null, FALSE, FALSE, null) }
safeAutoClose2(onError = { CloseHandle(notifier) }) {
val handlesStorage = HeapAlloc(this._heap, 0u, sizeOfUL<ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage>())
.nullToWinApiErr().reinterpret<ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage>()
safeAutoClose2(onError = { HeapFree(this._heap, 0u, handlesStorage) }) {
ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage_init(handlesStorage, notifier)
val dataObj = WorkerData(this._lastWorker, handlesStorage, notifier)
this._lastWorker = dataObj
dataObj.threadController = Thread.create(this._generateThreadName(), true, this.ThreadRoutineImpl(dataObj))
dataObj.threadController.start()
return@_createNewWorker dataObj
}
}
}
@OptIn(ManualStateManipulation::class)
open fun destroy() {
this._state.close()
while (true) {
val worker = this._lastWorker ?: break
EnterCriticalSection(this._sync)
safeAutoClose1(finally = { LeaveCriticalSection(this._sync) }) {
worker.interrupted = true
zeroToWinApiErr { SetEvent(worker.notifier) }
}
worker.threadController.join()
worker.threadController.releaseResources()
this._lastWorker = worker.next
zeroToWinApiErr { HeapFree(this._heap, 0u, worker.handlesStorage) }
zeroToWinApiErr { CloseHandle(worker.notifier) }
}
zeroToWinApiErr { HeapFree(this._heap, 0u, this._sync) }
zeroToWinApiErr { HeapDestroy(this._heap) }
}
class WorkerData(
val next: WorkerData?,
val handlesStorage: CPointer<ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage>,
val notifier: HANDLE,
) {
private var _threadController: ThreadController? = null
var threadController: ThreadController
get() = this._threadController ?: throw IllegalStateException("Not initialized")
set(value) {
if (this._threadController != null)
throw IllegalStateException("Already initialized")
this._threadController = value
}
var interrupted = false
set(value) {
if (field && !value)
throw IllegalStateException("Can't cancel interruption")
field = value
}
}
private inner class ThreadRoutineImpl(
private val _data: WorkerData,
) : ThreadRoutine {
override fun runThread(thread: Thread) {
try {
var usedSlotsCountCached = 1uL
mainloop@ while (true) {
when (val pos = ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage_wait(this._data.handlesStorage, usedSlotsCountCached)) {
WAIT_FAILED -> WindowsApiException.throwFromLastWindowsErr()
WAIT_TIMEOUT.toUInt() -> continue
in WAIT_ABANDONED_0..<(WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS.toUInt()) -> TODO()
in WAIT_OBJECT_0..<(WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS.toUInt()) -> {
EnterCriticalSection(this@HandleAndPointerBackgroundGarbageCollector._sync)
safeAutoClose1(finally = { LeaveCriticalSection(this@HandleAndPointerBackgroundGarbageCollector._sync) }) {
if (pos != WAIT_OBJECT_0) {
val wrapper = nullToWinApiErr {
ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage_removeSlotAndCloseHandle(
this._data.handlesStorage, (pos - WAIT_OBJECT_0).toULong()
)
}
this@HandleAndPointerBackgroundGarbageCollector._destroyPtr(wrapper)
}
usedSlotsCountCached = ru_landgrafhomyak_multitasking_0_HandleAndPointerBackgroundGC_HandlesStorage_shitToNextWorker(
this._data.handlesStorage,
this._data.next?.handlesStorage
)
this._data.next?.notifier?.let { n -> SetEvent(n) }?.zeroToWinApiErr()
if (this._data.interrupted) return@runThread
continue@mainloop
}
}
}
}
} catch (e: Throwable) {
e.printStackTrace()
}
}
}
}

View File

@ -0,0 +1,101 @@
package ru.landgrafhomyak.multitasking_0.threads
import kotlinx.cinterop.COpaquePointer
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.StableRef
import kotlinx.cinterop.asStableRef
import platform.windows.CloseHandle
import platform.windows.GetCurrentThread
import platform.windows.HANDLE
import platform.windows.SwitchToThread
import ru.landgrafhomyak.multitasking_0.HandleAndPointerBackgroundGarbageCollector
import ru.landgrafhomyak.multitasking_0.WrongCallerThreadException
import ru.landgrafhomyak.multitasking_0._c.ru_landgrafhomyak_multitasking_0_duplicateHandle
import ru.landgrafhomyak.multitasking_0.threads.sync.ResumeThreadCallback
import ru.landgrafhomyak.utility.highlevel_try_finally.safeAutoClose2
import ru.landgrafhomyak.utility.kotlin_native_interop_stdlib_0.windows.nullToWinApiErr
import ru.landgrafhomyak.utility.kotlin_native_interop_stdlib_0.windows.zeroToWinApiErr
//@Suppress("AMBIGUOUS_EXPECTS", "ACTUAL_WITHOUT_EXPECT")
@OptIn(ExperimentalForeignApi::class)
public actual class Thread(
internal val _nativeThread: HANDLE,
public actual val name: String,
internal val isExternal: Boolean,
) {
internal val _stableRef: StableRef<Thread> = StableRef.create(this)
private val _threadLocalMethods = ThreadLocalMethodsImpl(this)
actual override fun toString(): String = "<platform native thread name='${this.name}'>"
private object ExternThreadsGC : HandleAndPointerBackgroundGarbageCollector() {
override fun _destroyPtr(ptr: COpaquePointer) {
ptr.asStableRef<Thread>().dispose()
}
override fun _generateThreadName(): String {
TODO("Not yet implemented")
}
fun registerCurrentThread(): Thread {
val handle = nullToWinApiErr { ru_landgrafhomyak_multitasking_0_duplicateHandle(GetCurrentThread()) }
val wrapper: Thread
safeAutoClose2(onError = { zeroToWinApiErr { CloseHandle(handle) } }) {
wrapper = Thread(handle, ""/*todo GetThreadDescription starting from win10*/, isExternal = true)
safeAutoClose2(onError = { wrapper._stableRef.dispose() }) {
this._register(handle, wrapper._stableRef.asCPointer())
}
}
return wrapper
}
}
internal class CurrentThreadVariable {
private val _tls = ThreadLocalVariable()
fun get(): Thread? = this._tls.get()?.asStableRef<Thread>()?.get()
fun set(t: Thread?) {
this._tls.set(t?._stableRef?.asCPointer())
}
}
public actual companion object {
internal val _tl_currentThread = CurrentThreadVariable()
public actual
val current: ThreadLocalMethods
get() {
val existing = this._tl_currentThread.get()
if (existing != null) return existing._threadLocalMethods
val created = ExternThreadsGC.registerCurrentThread()
this._tl_currentThread.set(created)
return created._threadLocalMethods
}
public actual fun create(name: String, isDaemon: Boolean, routine: ThreadRoutine): ThreadController =
ThreadControllerImpl(name, isDaemon, routine)
}
private class ThreadLocalMethodsImpl(thread: Thread) : _CommonThreadLocalMethods(thread), ThreadLocalMethods {
override fun _assertCurrentThread() {
if (Thread._tl_currentThread.get() != this.thread)
throw WrongCallerThreadException("Object returned by 'Thread.current' must be used only in thread where it was produced")
}
override fun yield() {
this._assertCurrentThread()
SwitchToThread()
}
override fun suspendThread(store: (ResumeThreadCallback) -> Unit) {
TODO()
}
override fun suspendThread(timeoutMillis: UInt, timeoutHandler: ResumeThreadCallback.TimeoutHandler, store: (ResumeThreadCallback) -> Unit) {
TODO()
}
}
}

View File

@ -0,0 +1,160 @@
package ru.landgrafhomyak.multitasking_0.threads
import kotlin.concurrent.atomics.AtomicReference
import kotlin.concurrent.atomics.ExperimentalAtomicApi
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.StableRef
import kotlinx.cinterop.asStableRef
import kotlinx.cinterop.staticCFunction
import platform.windows.CREATE_SUSPENDED
import platform.windows.CloseHandle
import platform.windows.CreateThread
import platform.windows.INFINITE
import platform.windows.ResumeThread
import platform.windows.WAIT_FAILED
import platform.windows.WAIT_OBJECT_0
import platform.windows.WAIT_TIMEOUT
import platform.windows.WaitForSingleObject
import ru.landgrafhomyak.utility.closeable_state_1.CloseableState
import ru.landgrafhomyak.utility.closeable_state_1.Destructor
import ru.landgrafhomyak.utility.closeable_state_1.OwnedErrorOnConcurrentAccessState
import ru.landgrafhomyak.utility.closeable_state_1.withUse
import ru.landgrafhomyak.utility.closeable_state_1.withUseIfNotClosed
import ru.landgrafhomyak.utility.closeable_state_1.withUseThenClose
import ru.landgrafhomyak.utility.highlevel_try_finally.safeAutoClose2
import ru.landgrafhomyak.utility.kotlin_native_interop_stdlib_0.windows.WindowsApiException
import ru.landgrafhomyak.utility.kotlin_native_interop_stdlib_0.windows.nullToWinApiErr
import ru.landgrafhomyak.utility.kotlin_native_interop_stdlib_0.windows.zeroToWinApiErr
@Suppress("JoinDeclarationAndAssignment")
@OptIn(ExperimentalForeignApi::class, ExperimentalAtomicApi::class)
public class ThreadControllerImpl : ThreadController {
public override val thread: Thread
private val _guard: CloseableState.ExternallySynchronized
private var _uncaughtException: AtomicReference<Throwable?>
private var _isStarted: Boolean
internal constructor(name: String, isDaemon: Boolean, routine: ThreadRoutine) {
this._uncaughtException = AtomicReference(null)
this._guard = OwnedErrorOnConcurrentAccessState(this)
this._isStarted = false
val kernel = this.Kernel(routine)
val kernelRef = StableRef.create(kernel)
safeAutoClose2(onError = { kernelRef.dispose() }) {
val handle = nullToWinApiErr {
CreateThread(
null,
0uL,
staticCFunction { kernelPtr ->
val kernelRef = kernelPtr!!.asStableRef<Kernel>()
val kernel = kernelRef.get()
kernelRef.dispose()
kernel.run()
return@staticCFunction 0u
},
kernelRef.asCPointer(),
CREATE_SUSPENDED.toUInt(),
null
)
}
safeAutoClose2(onError = { zeroToWinApiErr { CloseHandle(handle) } }) {
this.thread = Thread(handle, name, false)
}
}
}
private inner class Kernel(private val _routine: ThreadRoutine) {
fun run() {
try {
Thread._tl_currentThread.set(this@ThreadControllerImpl.thread)
this._routine.runThread(this@ThreadControllerImpl.thread)
} catch (t: Throwable) {
this@ThreadControllerImpl._uncaughtException.store(t)
}
}
}
override fun toString(): String = "<controller of platform native thread '${this.thread.name}'>"
public override val isDaemon: Boolean
get() {
TODO()
}
private fun _testFinished(): Boolean {
when (WaitForSingleObject(this.thread._nativeThread, 0u)) {
WAIT_FAILED -> WindowsApiException.throwFromLastWindowsErr()
WAIT_TIMEOUT.toUInt() -> return false
WAIT_OBJECT_0 -> return true
else -> throw RuntimeException("Unexpected return value of 'WaitForSingleObject'")
}
}
public override val state: ThreadController.State
get() = this._guard.withUseIfNotClosed(ThreadController.State.DESTROYED) {
if (!this._isStarted) return@withUseIfNotClosed ThreadController.State.NEW
if (!this._testFinished())
return@withUseIfNotClosed ThreadController.State.STARTED
else {
if (this._uncaughtException.load() != null)
return@withUseIfNotClosed ThreadController.State.FINISHED_WITH_ERROR
else
return@withUseIfNotClosed ThreadController.State.FINISHED_SUCCESSFULLY
}
}
public override val uncaughtException: Throwable
get() = this._guard.withUse {
val e = this._uncaughtException.load()
if (e != null)
return e
if (!this._isStarted)
return@withUse throw IllegalStateException("Thread not finished yet")
if (this._testFinished())
throw IllegalStateException("Thread finished without uncaught exceptions")
else
throw IllegalStateException("Thread not finished yet")
}
public override fun start(): Unit = this._guard.withUse {
if (this._isStarted) {
if (this._testFinished())
throw IllegalStateException("Thread already was started and finished")
else
throw IllegalStateException("Thread already was started")
}
if (0u > ResumeThread(this.thread._nativeThread)) WindowsApiException.throwFromLastWindowsErr()
this._isStarted = true
}
override fun join(): Unit = this._guard.withUse {
if (!this._isStarted)
throw IllegalStateException("Can't join on thread that isn't started")
waitloop@ while (true) {
when (WaitForSingleObject(this.thread._nativeThread, INFINITE)) {
WAIT_FAILED -> WindowsApiException.throwFromLastWindowsErr()
WAIT_TIMEOUT.toUInt() -> continue@waitloop
WAIT_OBJECT_0 -> break@waitloop
else -> throw RuntimeException("Unexpected return value of 'WaitForSingleObject'")
}
}
}
@Destructor
public override fun releaseResources(): Unit = this._guard.withUseThenClose {
if (this._isStarted) {
if (!this._testFinished())
throw IllegalStateException("Can't destroy thread because it not finished yet")
}
zeroToWinApiErr { CloseHandle(this.thread._nativeThread) }
return@withUseThenClose true
}
}

View File

@ -0,0 +1,259 @@
package ru.landgrafhomyak.multitasking_0._tests
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.time.Clock
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.ExperimentalTime
import kotlinx.cinterop.COpaquePointer
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.alloc
import kotlinx.cinterop.invoke
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.ptr
import kotlinx.cinterop.reinterpret
import kotlinx.cinterop.toCPointer
import kotlinx.cinterop.toLong
import platform.posix.printf
import platform.windows.CONDITION_VARIABLE
import platform.windows.CRITICAL_SECTION
import platform.windows.CloseHandle
import platform.windows.CreateEventA
import platform.windows.DeleteCriticalSection
import platform.windows.ERROR_INVALID_HANDLE
import platform.windows.EnterCriticalSection
import platform.windows.FALSE
import platform.windows.GetLastError
import platform.windows.GetModuleHandle
import platform.windows.GetModuleHandleA
import platform.windows.GetProcAddress
import platform.windows.HANDLE
import platform.windows.INFINITE
import platform.windows.InitializeConditionVariable
import platform.windows.InitializeCriticalSection
import platform.windows.LeaveCriticalSection
import platform.windows.SetEvent
import platform.windows.Sleep
import platform.windows.SleepConditionVariableCS
import platform.windows.TRUE
import platform.windows.WINBOOL
import platform.windows.WakeAllConditionVariable
import ru.landgrafhomyak.multitasking_0.HandleAndPointerBackgroundGarbageCollector
import ru.landgrafhomyak.multitasking_0._c.ru_landgrafhomyak_multitasking_0_duplicateHandle
import ru.landgrafhomyak.utility.highlevel_try_finally.safeAutoClose1
import ru.landgrafhomyak.utility.kotlin_native_interop_stdlib_0.windows.WindowsApiException
import ru.landgrafhomyak.utility.kotlin_native_interop_stdlib_0.windows.nullToWinApiErr
import ru.landgrafhomyak.utility.kotlin_native_interop_stdlib_0.windows.zeroToWinApiErr
@OptIn(ExperimentalForeignApi::class)
public class HandleAndPointerBackgroundGarbageCollectorTest {
private val CompareObjectHandles = GetProcAddress(
nullToWinApiErr { GetModuleHandleA("KernelBase.dll") },
"CompareObjectHandles"
).nullToWinApiErr().reinterpret<kotlinx.cinterop.CFunction<(HANDLE, HANDLE) -> WINBOOL>>()
private class TestableGarbageCollector(
private val _sync: CPointer<CRITICAL_SECTION>,
private val _notify: CPointer<CONDITION_VARIABLE>,
private val _dst: BooleanArray,
) : HandleAndPointerBackgroundGarbageCollector() {
override fun _destroyPtr(ptr: COpaquePointer) {
val i = ptr.toLong().toInt() - 1
EnterCriticalSection(this._sync)
this._dst[i] = true
WakeAllConditionVariable(this._notify)
LeaveCriticalSection(this._sync)
}
override fun _generateThreadName(): String = "abc"
public fun register(handle: HANDLE, wrapper: COpaquePointer) =
this._register(handle, wrapper)
}
@OptIn(ExperimentalContracts::class)
private fun _createEventsRec(countToAllocate: Int, scope: (Array<HANDLE>, Array<() -> Boolean>) -> Unit) {
contract {
callsInPlace(scope, InvocationKind.EXACTLY_ONCE)
}
val marker = nullToWinApiErr { CreateEventA(null, FALSE, FALSE, null) }
safeAutoClose1(finally = { zeroToWinApiErr { CloseHandle(marker) } }) {
val event = nullToWinApiErr { ru_landgrafhomyak_multitasking_0_duplicateHandle(marker) }
safeAutoClose1(
action = {
val checkClosed = { CompareObjectHandles(marker, event) == FALSE }
if (countToAllocate > 1) {
this._createEventsRec(countToAllocate - 1) { arr, isc -> scope(arrayOf(event) + arr, arrayOf(checkClosed) + isc) }
} else {
scope(arrayOf(event), arrayOf(checkClosed))
}
},
finally = {
if (CompareObjectHandles(marker, event) == TRUE) {
zeroToWinApiErr { CloseHandle(event) }
}
}
)
}
}
@OptIn(ExperimentalTime::class)
private fun _runTest(size: Int, body: (ctx: TestContext) -> Unit) {
val flags = BooleanArray(size) { false }
val isExpected = BooleanArray(flags.size) { false }
memScoped {
val cs = alloc<CRITICAL_SECTION>().ptr
val cv = alloc<CONDITION_VARIABLE>().ptr
InitializeCriticalSection(cs)
InitializeConditionVariable(cv)
safeAutoClose1(
action = {
this@HandleAndPointerBackgroundGarbageCollectorTest._createEventsRec(size) { events, isEventClosed ->
val gc = TestableGarbageCollector(cs, cv, flags)
safeAutoClose1(finally = { gc.destroy() }) {
body(TestContext(cs, cv, events, gc, isExpected))
EnterCriticalSection(cs)
var i = 0
for ((h, fe) in ((isEventClosed) zip (flags zip isExpected))) {
val (f, e) = fe
val c = h()
assertFalse(!e && f && c, "Resource $i not expected to be destructed, but both handle and wrapper are")
assertFalse(!e && f && !c, "Resource $i not expected to be destructed, but wrapper is")
assertFalse(!e && !f && c, "Resource $i not expected to be destructed, but handle is")
assertFalse(e && !f && !c, "Resource $i expected to be destructed, but both handle and wrapper are not")
assertFalse(e && f && !c, "Resource $i expected to be destructed, but handle isn't")
assertFalse(e && !f && c, "Resource $i expected to be destructed, but wrapper isn't")
i++
}
LeaveCriticalSection(cs)
}
}
},
finally = {
DeleteCriticalSection(cs)
}
)
}
}
private class TestContext(
private val _sync: CPointer<CRITICAL_SECTION>,
private val _notify: CPointer<CONDITION_VARIABLE>,
private val _events: Array<HANDLE>,
private val _dst: TestableGarbageCollector,
private val _isExpected: BooleanArray,
) {
private val _isAdded = BooleanArray(this._events.size) { false }
fun add(i: Int) {
if (this._isAdded[i]) throw RuntimeException("Bad test: duplicated add($i)")
this._dst.register(this._events[i], (i + 1).toLong().toCPointer()!!)
}
fun release(i: Int) {
if (this._isExpected[i]) throw RuntimeException("Bad test: duplicated release($i)")
EnterCriticalSection(this._sync)
zeroToWinApiErr { SetEvent(this._events[i]) }
SleepConditionVariableCS(this._notify, this._sync, INFINITE)
this._isExpected[i] = true
LeaveCriticalSection(this._sync)
}
fun sleep(d: Duration) {
Sleep(d.inWholeMilliseconds.toUInt())
}
}
@Test
fun `test empty`() = this._runTest(0) { ctx -> }
@Test
fun `_test events allocation 1`() = this._runTest(1) { ctx -> }
@Test
fun `_test events allocation 2`() = this._runTest(1) { ctx -> }
@Test
fun `_test events allocation 3`() = this._runTest(1) { ctx -> }
@Test
fun `test 1 alive`() = this._runTest(1) { ctx ->
ctx.add(0)
}
@Test
fun `test 1 destruct instantly`() = this._runTest(1) { ctx ->
ctx.add(0)
ctx.release(0)
}
@Test
fun `test 1 destructed after delay`() = this._runTest(1) { ctx ->
ctx.add(0)
ctx.sleep(400.milliseconds)
ctx.release(0)
}
@Test
fun `test 2 destruct 1-st`() = this._runTest(2) { ctx ->
ctx.add(0)
ctx.add(1)
ctx.release(0)
}
@Test
fun `test 2 destruct 2-st`() = this._runTest(2) { ctx ->
ctx.add(0)
ctx.add(1)
ctx.release(1)
}
@Test
fun `test 2 destruct 12`() = this._runTest(2) { ctx ->
ctx.add(0)
ctx.add(1)
ctx.release(0)
ctx.release(1)
}
@Test
fun `test 2 destruct 21`() = this._runTest(2) { ctx ->
ctx.add(0)
ctx.add(1)
ctx.release(1)
ctx.release(0)
}
@Test
fun `test 80 alive`() = this._runTest(80) { ctx ->
(0..<80).forEach { i -> ctx.add(i) }
}
@Test
fun `test 80 shitting`() = this._runTest(80) { ctx ->
(0..<80).forEach { i -> ctx.add(i) }
(0..<20).forEach { i -> ctx.release(i) }
ctx.release(79)
}
@Test
fun `test 80 destruct all`() = this._runTest(80) { ctx ->
(0..<80).forEach { i -> ctx.add(i) }
(0..<80).forEach { i -> ctx.release(i) }
}
@Test
fun `test 80 destruct all reversed`() = this._runTest(80) { ctx ->
(0..<80).forEach { i -> ctx.add(i) }
(79 downTo 0).forEach { i -> ctx.release(i) }
}
}