From 298e8dad116ea6c14186c2a961fc45022769062b Mon Sep 17 00:00:00 2001 From: Andrew Golovashevich Date: Sat, 22 Mar 2025 20:58:11 +0300 Subject: [PATCH] Extracted to separate library; debug version of counter --- .gitignore | 9 ++ .gitmodules | 3 + build.gradle.kts | 47 ++++++ gradle.properties | 1 + highlevel-try-finally | 1 + settings.gradle.kts | 3 + .../CloseableReferenceCounter.kt | 34 ++--- .../CloseableReferenceCounter_Debug.kt | 135 ++++++++++++++++++ .../_CloseableReferenceCounter_LowLevel.kt | 16 +++ 9 files changed, 230 insertions(+), 19 deletions(-) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 build.gradle.kts create mode 100644 gradle.properties create mode 160000 highlevel-try-finally create mode 100644 settings.gradle.kts create mode 100644 src/commonMain/kotlin/ru/landrafhomyak/utility/reference_counter/CloseableReferenceCounter_Debug.kt create mode 100644 src/commonMain/kotlin/ru/landrafhomyak/utility/reference_counter/_CloseableReferenceCounter_LowLevel.kt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f2e4e0d --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/.idea/ +gradle/ +.gradle/ +build/ +*.class +*.jar +/out/ +/gradlew* +.kotlin/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..1d5629a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "highlevel-try-finally"] + path = highlevel-try-finally + url = https://git.landgrafhomyak.ru/xomrk/highlevel-try-finally.kt diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..f388709 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,47 @@ +import org.jetbrains.kotlin.gradle.dsl.KotlinVersion +import ru.landgrafhomyak.kotlin.kmp_gradle_build_helper.* +import ru.landgrafhomyak.kotlin.kmp_gradle_build_helper.plugin.xomrk + +buildscript { + repositories { + mavenCentral() + maven("https://maven.landgrafhomyak.ru/") + } + + dependencies { + classpath("ru.landgrafhomyak.kotlin:kotlin-mpp-gradle-build:v0.3k2.1.10") + } +} + +group = "ru.landgrafhomyak.utility" +version = "0.1" + +repositories { + mavenCentral() + maven("https://maven.landgrafhomyak.ru/") +} + +xomrk { + kotlin { + setCompatibilityWithKotlin(KotlinVersion.KOTLIN_2_0) + optInContracts() + explicitApi() + + defineAllMultiplatformTargets() + + sourceSets { + commonMain { + dependencies { + implementation("org.jetbrains.kotlinx:atomicfu:0.27.0") + implementation("ru.landgrafhomyak.utility:highlevel-try-finally:0.4") + } + } + } + } + + publishing { + repositories { + defineXomrkGiteaMavenRepo() + } + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..7fc6f1f --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official diff --git a/highlevel-try-finally b/highlevel-try-finally new file mode 160000 index 0000000..88c8da2 --- /dev/null +++ b/highlevel-try-finally @@ -0,0 +1 @@ +Subproject commit 88c8da21e4ec11c9755b6770565685bdc29acbca diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..ddaca88 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,3 @@ +rootProject.name = "reference-counter" + +includeBuild("./highlevel-try-finally") \ No newline at end of file diff --git a/src/commonMain/kotlin/ru/landrafhomyak/utility/reference_counter/CloseableReferenceCounter.kt b/src/commonMain/kotlin/ru/landrafhomyak/utility/reference_counter/CloseableReferenceCounter.kt index f2d037c..1402ad0 100644 --- a/src/commonMain/kotlin/ru/landrafhomyak/utility/reference_counter/CloseableReferenceCounter.kt +++ b/src/commonMain/kotlin/ru/landrafhomyak/utility/reference_counter/CloseableReferenceCounter.kt @@ -1,5 +1,8 @@ +@file:OptIn(ExperimentalContracts::class) + package ru.landrafhomyak.utility.reference_counter +import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlinx.atomicfu.AtomicLong @@ -8,21 +11,21 @@ import kotlinx.atomicfu.update import ru.landgrafhomyak.utility.highlevel_try_finally.safeAutoClose1 import ru.landgrafhomyak.utility.highlevel_try_finally.safeAutoClose2 -internal class CloseableReferenceCounter(private val _errMessage: String) { +public class CloseableReferenceCounter(private val _errMessage: String) { private val _value: AtomicLong = atomic(0L) - fun throwClosed() { + public fun throwClosed() { throw IllegalStateException(this._errMessage) } - fun incref() { + public fun incref() { this._value.update { o -> if (o < 0) this.throwClosed() return@update o + 1 } } - inline fun tryIncref(block: () -> R): R { + public inline fun tryIncref(block: () -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } @@ -30,48 +33,41 @@ internal class CloseableReferenceCounter(private val _errMessage: String) { return safeAutoClose2(onError = this::decref, action = block) } - fun checkNotClosed() { + public fun assertNotClosed() { if (this._value.value < 0) this.throwClosed() } - fun decref() { + public fun decref() { this._value.update(Long::dec) } - inline fun tryDecref(block: () -> R): R { + public inline fun tryDecref(block: () -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - this.checkNotClosed() + this.assertNotClosed() return safeAutoClose2(onSuccess = this::decref, action = block) } - fun close(errExistRefs: String) { - val state = this._value.compareAndExchange(0, -1) + public fun close(errExistRefs: String) { + val state = _CloseableReferenceCounter_LowLevel.compareAndExchange(this._value, 0, _CloseableReferenceCounter_LowLevel.CLOSED_STATE_VALUE) when { state > 0 -> throw IllegalStateException(errExistRefs) state < 0 -> this.throwClosed() } } - inline fun withRef(protected: () -> R): R { + public inline fun withRef(protected: () -> R): R { this.incref() return safeAutoClose1(finally = this::decref, action = protected) } override fun toString(): String { val refcntCached = this._value.value + @Suppress("LiftReturnOrAssignment") if (refcntCached < 0) return "" else return "" } - - private fun AtomicLong.compareAndExchange(expected: Long, newValue: Long): Long { - while (true) { - val old = this@compareAndExchange.value - if (old != expected) return old - if (this@compareAndExchange.compareAndSet(old, newValue)) return old - } - } } \ No newline at end of file diff --git a/src/commonMain/kotlin/ru/landrafhomyak/utility/reference_counter/CloseableReferenceCounter_Debug.kt b/src/commonMain/kotlin/ru/landrafhomyak/utility/reference_counter/CloseableReferenceCounter_Debug.kt new file mode 100644 index 0000000..298f3ec --- /dev/null +++ b/src/commonMain/kotlin/ru/landrafhomyak/utility/reference_counter/CloseableReferenceCounter_Debug.kt @@ -0,0 +1,135 @@ +@file:OptIn(ExperimentalContracts::class) + +package ru.landrafhomyak.utility.reference_counter + +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract +import kotlinx.atomicfu.AtomicLong +import kotlinx.atomicfu.atomic +import kotlinx.atomicfu.getAndUpdate +import kotlinx.atomicfu.update +import ru.landgrafhomyak.utility.highlevel_try_finally.safeAutoClose1 +import ru.landgrafhomyak.utility.highlevel_try_finally.safeAutoClose2 + +@CloseableReferenceCounter_Debug.RequiresExplicitDebug +public class CloseableReferenceCounter_Debug( + private val _dbgName: String, + private val _errMessage: String, + private val _logger: Observer? = null, +) { + public fun interface Observer { + public fun observeState(instance: CloseableReferenceCounter_Debug, actions: String) + } + + @RequiresOptIn(level = RequiresOptIn.Level.WARNING) + @Retention(AnnotationRetention.BINARY) + public annotation class RequiresExplicitDebug + + + private val _value: AtomicLong = atomic(0L) + + @Suppress("RemoveRedundantQualifierName") + private val _id = CloseableReferenceCounter_Debug._nextId.getAndUpdate(ULong::inc) + + @Suppress("NOTHING_TO_INLINE") + private inline fun _throwErrors(valueToCheck: Long) { + when { + valueToCheck >= 0 || valueToCheck == _CloseableReferenceCounter_LowLevel.CLOSED_STATE_VALUE -> {} + valueToCheck < _CloseableReferenceCounter_LowLevel.CLOSED_STATE_VALUE -> throw RuntimeException("Too many references") + valueToCheck > _CloseableReferenceCounter_LowLevel.CLOSED_STATE_VALUE -> throw RuntimeException(".decref called more times than .incref") + } + } + + @RequiresExplicitDebug + public fun throwErrors() { + this._throwErrors(this._value.value) + } + + public fun throwClosed() { + throw IllegalStateException(this._errMessage) + } + + public fun incref() { + this._value.update { o -> + if (o < 0) { + this._throwErrors(o) + this.throwClosed() + this._throwErrors(o + 1) + } + return@update o + 1 + } + this._logger?.observeState(this, "incref") + } + + public inline fun tryIncref(block: () -> R): R { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + this.incref() + return safeAutoClose2(onError = this::decref, action = block) + } + + public fun assertNotClosed() { + if (this._value.value < 0) this.throwClosed() + } + + public fun decref() { + this._value.update { o -> + if (o < 0) { + this._throwErrors(o) + this.throwClosed() + this._throwErrors(o - 1) + } + return@update o - 1 + } + this._logger?.observeState(this, "decref") + } + + public inline fun tryDecref(block: () -> R): R { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + this.assertNotClosed() + return safeAutoClose2(onSuccess = this::decref, action = block) + } + + public fun close(errExistRefs: String) { + val state = _CloseableReferenceCounter_LowLevel.compareAndExchange(this._value, 0, _CloseableReferenceCounter_LowLevel.CLOSED_STATE_VALUE) + this._throwErrors(state) + when { + state > 0 -> throw IllegalStateException(errExistRefs) + state < 0 -> this.throwClosed() + } + this._logger?.observeState(this, "closed") + } + + public inline fun withRef(protected: () -> R): R { + this.incref() + return safeAutoClose1(finally = this::decref, action = protected) + } + + override fun toString(): String { + val refcntCached = this._value.value + val stateRepr: String + @Suppress("LiftReturnOrAssignment") + when { + refcntCached >= 0 -> stateRepr = refcntCached.toString() + refcntCached == _CloseableReferenceCounter_LowLevel.CLOSED_STATE_VALUE -> stateRepr = "closed" + refcntCached < _CloseableReferenceCounter_LowLevel.CLOSED_STATE_VALUE -> stateRepr = "overflow" + refcntCached > _CloseableReferenceCounter_LowLevel.CLOSED_STATE_VALUE -> stateRepr = "underflow" + else -> throw Error("Unreachable") + } + return "" + } + + public companion object { + private val _nextId = atomic(0uL) + } + + public object ObserveToStdout : Observer { + override fun observeState(instance: CloseableReferenceCounter_Debug, actions: String) { + print("${instance} ${actions}\n") + } + } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/ru/landrafhomyak/utility/reference_counter/_CloseableReferenceCounter_LowLevel.kt b/src/commonMain/kotlin/ru/landrafhomyak/utility/reference_counter/_CloseableReferenceCounter_LowLevel.kt new file mode 100644 index 0000000..3ca6a20 --- /dev/null +++ b/src/commonMain/kotlin/ru/landrafhomyak/utility/reference_counter/_CloseableReferenceCounter_LowLevel.kt @@ -0,0 +1,16 @@ +package ru.landrafhomyak.utility.reference_counter + +import kotlinx.atomicfu.AtomicLong + +@Suppress("ClassName") +internal object _CloseableReferenceCounter_LowLevel { + internal fun compareAndExchange(atomic: AtomicLong, expected: Long, newValue: Long): Long { + while (true) { + val old = atomic.value + if (old != expected) return old + if (atomic.compareAndSet(old, newValue)) return old + } + } + + internal const val CLOSED_STATE_VALUE = -0x4000_0000_0000_0000L +} \ No newline at end of file