diff --git a/build.gradle.kts b/build.gradle.kts index f131717..55306d0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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) } } diff --git a/gradle.properties b/gradle.properties index 10a9db6..0fc59ed 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,5 @@ kotlin.native.ignoreDisabledTargets=true kotlin.mpp.applyDefaultHierarchyTemplate=false kotlin.native.enableKlibsCrossCompilation=true -kotlin.stdlib.default.dependency=false \ No newline at end of file +kotlin.stdlib.default.dependency=false +kotlin.mpp.enableCInteropCommonization=true \ No newline at end of file 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 5a2f73e..c9cc8a5 100644 --- a/src/jvmMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/Thread.kt +++ b/src/jvmMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/Thread.kt @@ -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() { diff --git a/src/windowsMain/c/Include/ru/landgrafhomyak/multitasking_0/handle_and_pointer_background_garbage_collector.h b/src/windowsMain/c/Include/ru/landgrafhomyak/multitasking_0/handle_and_pointer_background_garbage_collector.h new file mode 100644 index 0000000..1b00c94 --- /dev/null +++ b/src/windowsMain/c/Include/ru/landgrafhomyak/multitasking_0/handle_and_pointer_background_garbage_collector.h @@ -0,0 +1,117 @@ +#pragma once + +#include + +#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; +} \ No newline at end of file diff --git a/src/windowsMain/c/Include/ru/landgrafhomyak/multitasking_0/utility.h b/src/windowsMain/c/Include/ru/landgrafhomyak/multitasking_0/utility.h new file mode 100644 index 0000000..7778bbf --- /dev/null +++ b/src/windowsMain/c/Include/ru/landgrafhomyak/multitasking_0/utility.h @@ -0,0 +1,14 @@ +#pragma once + + +#include + +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; +} \ No newline at end of file diff --git a/src/windowsMain/c/windows.c2kt_def b/src/windowsMain/c/windows.c2kt_def new file mode 100644 index 0000000..4f108bf --- /dev/null +++ b/src/windowsMain/c/windows.c2kt_def @@ -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 \ No newline at end of file diff --git a/src/windowsMain/kotlin/ru/landgrafhomyak/multitasking_0/HandleAndPointerBackgroundGarbageCollector.kt b/src/windowsMain/kotlin/ru/landgrafhomyak/multitasking_0/HandleAndPointerBackgroundGarbageCollector.kt new file mode 100644 index 0000000..3046347 --- /dev/null +++ b/src/windowsMain/kotlin/ru/landgrafhomyak/multitasking_0/HandleAndPointerBackgroundGarbageCollector.kt @@ -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 + private val _stableRef: StableRef + 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(), 0u) } + safeAutoClose2(onError = { HeapDestroy(this._heap) }) { + this._sync = nullToWinApiErr { HeapAlloc(this._heap, 0u, sizeOfUL()) }.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()) + .nullToWinApiErr().reinterpret() + 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, + 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() + } + } + } +} \ No newline at end of file diff --git a/src/windowsMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/Thread.kt b/src/windowsMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/Thread.kt new file mode 100644 index 0000000..7cd19b2 --- /dev/null +++ b/src/windowsMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/Thread.kt @@ -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 = StableRef.create(this) + private val _threadLocalMethods = ThreadLocalMethodsImpl(this) + + actual override fun toString(): String = "" + + private object ExternThreadsGC : HandleAndPointerBackgroundGarbageCollector() { + override fun _destroyPtr(ptr: COpaquePointer) { + ptr.asStableRef().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()?.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() + } + } +} diff --git a/src/windowsMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/ThreadControllerImpl.kt b/src/windowsMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/ThreadControllerImpl.kt new file mode 100644 index 0000000..5f43695 --- /dev/null +++ b/src/windowsMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/ThreadControllerImpl.kt @@ -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 + 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() + 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 = "" + + 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 + } +} \ No newline at end of file diff --git a/src/windowsTest/kotlin/ru/landgrafhomyak/multitasking_0/_tests/HandleAndPointerBackgroundGarbageCollectorTest.kt b/src/windowsTest/kotlin/ru/landgrafhomyak/multitasking_0/_tests/HandleAndPointerBackgroundGarbageCollectorTest.kt new file mode 100644 index 0000000..11c4852 --- /dev/null +++ b/src/windowsTest/kotlin/ru/landgrafhomyak/multitasking_0/_tests/HandleAndPointerBackgroundGarbageCollectorTest.kt @@ -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 WINBOOL>>() + + + private class TestableGarbageCollector( + private val _sync: CPointer, + private val _notify: CPointer, + 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, 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().ptr + val cv = alloc().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, + private val _notify: CPointer, + private val _events: Array, + 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) } + } +} \ No newline at end of file