Extracted to separate library; debug version of counter
This commit is contained in:
parent
5a49c43a4f
commit
298e8dad11
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/.idea/
|
||||||
|
gradle/
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
*.class
|
||||||
|
*.jar
|
||||||
|
/out/
|
||||||
|
/gradlew*
|
||||||
|
.kotlin/
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal 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
47
build.gradle.kts
Normal 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
1
gradle.properties
Normal file
@ -0,0 +1 @@
|
|||||||
|
kotlin.code.style=official
|
1
highlevel-try-finally
Submodule
1
highlevel-try-finally
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 88c8da21e4ec11c9755b6770565685bdc29acbca
|
3
settings.gradle.kts
Normal file
3
settings.gradle.kts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
rootProject.name = "reference-counter"
|
||||||
|
|
||||||
|
includeBuild("./highlevel-try-finally")
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user