Extracted to separate library; debug version of counter

This commit is contained in:
Andrew Golovashevich 2025-03-22 20:58:11 +03:00
parent 5a49c43a4f
commit 298e8dad11
9 changed files with 230 additions and 19 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
/.idea/
gradle/
.gradle/
build/
*.class
*.jar
/out/
/gradlew*
.kotlin/

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "highlevel-try-finally"]
path = highlevel-try-finally
url = https://git.landgrafhomyak.ru/xomrk/highlevel-try-finally.kt

47
build.gradle.kts Normal file
View File

@ -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()
}
}
}

1
gradle.properties Normal file
View File

@ -0,0 +1 @@
kotlin.code.style=official

1
highlevel-try-finally Submodule

@ -0,0 +1 @@
Subproject commit 88c8da21e4ec11c9755b6770565685bdc29acbca

3
settings.gradle.kts Normal file
View File

@ -0,0 +1,3 @@
rootProject.name = "reference-counter"
includeBuild("./highlevel-try-finally")

View File

@ -1,5 +1,8 @@
@file:OptIn(ExperimentalContracts::class)
package ru.landrafhomyak.utility.reference_counter package ru.landrafhomyak.utility.reference_counter
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
import kotlin.contracts.contract import kotlin.contracts.contract
import kotlinx.atomicfu.AtomicLong 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.safeAutoClose1
import ru.landgrafhomyak.utility.highlevel_try_finally.safeAutoClose2 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) private val _value: AtomicLong = atomic(0L)
fun throwClosed() { public fun throwClosed() {
throw IllegalStateException(this._errMessage) throw IllegalStateException(this._errMessage)
} }
fun incref() { public fun incref() {
this._value.update { o -> this._value.update { o ->
if (o < 0) this.throwClosed() if (o < 0) this.throwClosed()
return@update o + 1 return@update o + 1
} }
} }
inline fun <R> tryIncref(block: () -> R): R { public inline fun <R> tryIncref(block: () -> R): R {
contract { contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE) callsInPlace(block, InvocationKind.EXACTLY_ONCE)
} }
@ -30,48 +33,41 @@ internal class CloseableReferenceCounter(private val _errMessage: String) {
return safeAutoClose2(onError = this::decref, action = block) return safeAutoClose2(onError = this::decref, action = block)
} }
fun checkNotClosed() { public fun assertNotClosed() {
if (this._value.value < 0) this.throwClosed() if (this._value.value < 0) this.throwClosed()
} }
fun decref() { public fun decref() {
this._value.update(Long::dec) this._value.update(Long::dec)
} }
inline fun <R> tryDecref(block: () -> R): R { public inline fun <R> tryDecref(block: () -> R): R {
contract { contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE) callsInPlace(block, InvocationKind.EXACTLY_ONCE)
} }
this.checkNotClosed() this.assertNotClosed()
return safeAutoClose2(onSuccess = this::decref, action = block) return safeAutoClose2(onSuccess = this::decref, action = block)
} }
fun close(errExistRefs: String) { public fun close(errExistRefs: String) {
val state = this._value.compareAndExchange(0, -1) val state = _CloseableReferenceCounter_LowLevel.compareAndExchange(this._value, 0, _CloseableReferenceCounter_LowLevel.CLOSED_STATE_VALUE)
when { when {
state > 0 -> throw IllegalStateException(errExistRefs) state > 0 -> throw IllegalStateException(errExistRefs)
state < 0 -> this.throwClosed() state < 0 -> this.throwClosed()
} }
} }
inline fun <R> withRef(protected: () -> R): R { public inline fun <R> withRef(protected: () -> R): R {
this.incref() this.incref()
return safeAutoClose1(finally = this::decref, action = protected) return safeAutoClose1(finally = this::decref, action = protected)
} }
override fun toString(): String { override fun toString(): String {
val refcntCached = this._value.value val refcntCached = this._value.value
@Suppress("LiftReturnOrAssignment")
if (refcntCached < 0) if (refcntCached < 0)
return "<ref counter [closed]>" return "<ref counter [closed]>"
else else
return "<ref counter [${refcntCached}]>" return "<ref counter [${refcntCached}]>"
} }
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
}
}
} }

View File

@ -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 <R> 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 <R> 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 <R> 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 "<ref counter \"${this._dbgName}@${this._id}\" [${stateRepr}]>"
}
public companion object {
private val _nextId = atomic(0uL)
}
public object ObserveToStdout : Observer {
override fun observeState(instance: CloseableReferenceCounter_Debug, actions: String) {
print("${instance} ${actions}\n")
}
}
}

View File

@ -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
}