diff --git a/build.gradle.kts b/build.gradle.kts index 1524c94..81e7806 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,7 +13,7 @@ buildscript { } plugins { - kotlin("multiplatform") version "2.2.20-Beta1" + kotlin("multiplatform") version "2.2.20" `maven-publish` } @@ -63,6 +63,13 @@ kotlin { } val commonTest by getting + this@kotlin.jvm().compilations.getByName("main").defaultSourceSet { + dependencies { + compileOnly("org.jetbrains.kotlin:kotlin-annotations-jvm:${this@kotlin.coreLibrariesVersion}") + } + } + + val notJvmMain by creating { dependsOn(commonMain) } val notJvmTest by creating { dependsOn(commonTest) } diff --git a/src/commonMain/kotlin/ru/landgrafhomyak/multitasking_0/WrongCallerThreadException.kt b/src/commonMain/kotlin/ru/landgrafhomyak/multitasking_0/WrongCallerThreadException.kt index b180d34..497020c 100644 --- a/src/commonMain/kotlin/ru/landgrafhomyak/multitasking_0/WrongCallerThreadException.kt +++ b/src/commonMain/kotlin/ru/landgrafhomyak/multitasking_0/WrongCallerThreadException.kt @@ -2,9 +2,9 @@ package ru.landgrafhomyak.multitasking_0 import kotlin.RuntimeException -public class WrongCallerThreadException : RuntimeException { - public constructor() : super() - public constructor(message: String?) : super(message) - public constructor(message: String?, cause: Throwable?) : super(message, cause) - public constructor(cause: Throwable?) : super(cause) +public expect class WrongCallerThreadException : RuntimeException { + public constructor() + public constructor(message: String?) + public constructor(message: String?, cause: Throwable?) + public constructor(cause: Throwable?) } \ No newline at end of file diff --git a/src/jvmMain/java/ru/landgrafhomyak/multitasking_0/threads/sync/SpinLockThreadMutex.java b/src/jvmMain/java/ru/landgrafhomyak/multitasking_0/threads/sync/SpinLockThreadMutex.java new file mode 100644 index 0000000..d726f79 --- /dev/null +++ b/src/jvmMain/java/ru/landgrafhomyak/multitasking_0/threads/sync/SpinLockThreadMutex.java @@ -0,0 +1,158 @@ +package ru.landgrafhomyak.multitasking_0.threads.sync; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import org.jetbrains.annotations.NotNull; +import kotlin.annotations.jvm.KotlinActual; + +@KotlinActual +public class SpinLockThreadMutex implements ThreadMutex { + private static final long CLOSED_USAGES_COUNT = -0x8000_0000_0000_0000L; + + private final AtomicLong _usagesCount; + private final AtomicReference _currentOwner; + + @KotlinActual + public SpinLockThreadMutex() { + this._usagesCount = new AtomicLong(); + this._currentOwner = new AtomicReference<>(null); + } + + @KotlinActual + @Override + public void lock() { + if (this._usagesCount.getAndIncrement() < 0) { + this._usagesCount.getAndDecrement(); + throw new IllegalStateException("Mutex was destroyed"); + } + final Thread thisOwner = Thread.currentThread(); + while (!this._currentOwner.compareAndSet(null, thisOwner)) + Thread.onSpinWait(); + } + + @KotlinActual + @Override + public void unlock() { + if (!this._currentOwner.compareAndSet(Thread.currentThread(), null)) { + if (this._usagesCount.get() < 0) + throw new IllegalStateException("Mutex was destroyed"); + throw new WrongThreadException("Thread doesn't hold this mutex"); + } + + this._usagesCount.getAndDecrement(); + } + + + @KotlinActual + @Override + public void destroy() { + final long cachedUsagesCount = this._usagesCount.compareAndExchange(0, CLOSED_USAGES_COUNT); + if (cachedUsagesCount < 0) + throw new IllegalStateException("Mutex was destroyed"); + if (cachedUsagesCount > 0) + throw new IllegalStateException("There are thread owning or waiting on this mutex"); + } + + @KotlinActual + @Override + @NotNull + public ThreadCondition newAssociatedCondition() { + return this.new ConditionImpl(); + } + + private class ConditionImpl implements ThreadCondition { + private static class QueueNode { + public QueueNode next; + public final AtomicBoolean isWaiting; + + public QueueNode() { + this.next = null; + this.isWaiting = new AtomicBoolean(true); + } + } + + private final AtomicReference _next; + private final AtomicLong _usagesCount; + + public ConditionImpl() { + if (SpinLockThreadMutex.this._usagesCount.getAndIncrement() < 0) { + SpinLockThreadMutex.this._usagesCount.getAndDecrement(); + throw new IllegalStateException("Mutex was destroyed"); + } + this._next = new AtomicReference<>(null); + this._usagesCount = new AtomicLong(0); + } + + private void _throwDestroyed() { + if (SpinLockThreadMutex.this._usagesCount.get() < 0) + throw new IllegalStateException("Condition and associated mutex were destroyed"); + throw new IllegalStateException("Condition was destroyed"); + } + + @Override + public void await() { + if (this._usagesCount.getAndIncrement() < 0) { + SpinLockThreadMutex.this._usagesCount.getAndDecrement(); + this._throwDestroyed(); + } + final Thread currentThread = Thread.currentThread(); + if (SpinLockThreadMutex.this._currentOwner.get() != currentThread) + throw new WrongThreadException("Thread doesn't hold this mutex"); + + final QueueNode node = new QueueNode(); + do { + node.next = this._next.get(); + } while (!this._next.compareAndSet(node.next, node)); + + SpinLockThreadMutex.this._currentOwner.set(null); + + while (node.isWaiting.get()) + Thread.onSpinWait(); + + while (!SpinLockThreadMutex.this._currentOwner.compareAndSet(null, currentThread)) + Thread.onSpinWait(); + + this._usagesCount.getAndDecrement(); + } + + private boolean _wakeOne() { + while (true) { + final QueueNode node = this._next.get(); + if (node == null) return false; + if (!this._next.compareAndSet(node, node.next)) + continue; + node.isWaiting.set(false); + return true; + } + } + + @Override + public void wakeOne() { + if (this._usagesCount.get() < 0) + this._throwDestroyed(); + + this._wakeOne(); + } + + @SuppressWarnings("StatementWithEmptyBody") + @Override + public void wakeAll() { + if (this._usagesCount.get() < 0) + this._throwDestroyed(); + + while (this._wakeOne()) { + } + } + + @Override + public void destroy() { + final long cachedUsagesCount = this._usagesCount.compareAndExchange(0, SpinLockThreadMutex.CLOSED_USAGES_COUNT); + if (cachedUsagesCount < 0) + throw new IllegalStateException("Condition already was destroyed"); + if (cachedUsagesCount > 0) + throw new IllegalStateException("There are thread waiting on this condition"); + } + } +} diff --git a/src/jvmMain/kotlin/ru/landgrafhomyak/multitasking_0/WrongCallerThreadException.kt b/src/jvmMain/kotlin/ru/landgrafhomyak/multitasking_0/WrongCallerThreadException.kt new file mode 100644 index 0000000..31c2082 --- /dev/null +++ b/src/jvmMain/kotlin/ru/landgrafhomyak/multitasking_0/WrongCallerThreadException.kt @@ -0,0 +1,3 @@ +package ru.landgrafhomyak.multitasking_0 + +public actual typealias WrongCallerThreadException = java.lang.WrongThreadException \ No newline at end of file diff --git a/src/multithreadPlatformMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/sync/SpinLockThreadMutex.kt b/src/multithreadPlatformMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/sync/SpinLockThreadMutex.kt new file mode 100644 index 0000000..a5c9f6c --- /dev/null +++ b/src/multithreadPlatformMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/sync/SpinLockThreadMutex.kt @@ -0,0 +1,10 @@ +package ru.landgrafhomyak.multitasking_0.threads.sync + +public expect class SpinLockThreadMutex : ThreadMutex { + public constructor() + + override fun lock() + override fun unlock() + override fun newAssociatedCondition(): ThreadCondition + override fun destroy() +} \ No newline at end of file diff --git a/src/multithreadPlatformMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/sync/ThreadCondition.kt b/src/multithreadPlatformMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/sync/ThreadCondition.kt new file mode 100644 index 0000000..f58617f --- /dev/null +++ b/src/multithreadPlatformMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/sync/ThreadCondition.kt @@ -0,0 +1,8 @@ +package ru.landgrafhomyak.multitasking_0.threads.sync + +public interface ThreadCondition { + public fun await() + public fun wakeOne() + public fun wakeAll() + public fun destroy() +} \ No newline at end of file diff --git a/src/multithreadPlatformMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/sync/ThreadMutex.kt b/src/multithreadPlatformMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/sync/ThreadMutex.kt new file mode 100644 index 0000000..8456fd8 --- /dev/null +++ b/src/multithreadPlatformMain/kotlin/ru/landgrafhomyak/multitasking_0/threads/sync/ThreadMutex.kt @@ -0,0 +1,8 @@ +package ru.landgrafhomyak.multitasking_0.threads.sync + +public interface ThreadMutex { + public fun lock() + public fun unlock() + public fun newAssociatedCondition(): ThreadCondition + public fun destroy() +} \ No newline at end of file diff --git a/src/notJvmMain/kotlin/ru/landgrafhomyak/multitasking_0/WrongCallerThreadException.kt b/src/notJvmMain/kotlin/ru/landgrafhomyak/multitasking_0/WrongCallerThreadException.kt new file mode 100644 index 0000000..cc536fc --- /dev/null +++ b/src/notJvmMain/kotlin/ru/landgrafhomyak/multitasking_0/WrongCallerThreadException.kt @@ -0,0 +1,10 @@ +package ru.landgrafhomyak.multitasking_0 + +import kotlin.RuntimeException + +public actual class WrongCallerThreadException : RuntimeException { + public actual constructor() : super() + public actual constructor(message: String?) : super(message) + public actual constructor(message: String?, cause: Throwable?) : super(message, cause) + public actual constructor(cause: Throwable?) : super(cause) +} \ No newline at end of file