Add AtomicLazy

This commit is contained in:
Him188 2021-04-14 12:51:44 +08:00
parent 2014c34102
commit 48564056df
5 changed files with 196 additions and 1 deletions

View File

@ -15,7 +15,7 @@ plugins {
kotlin("multiplatform")
kotlin("plugin.serialization")
//id("kotlinx-atomicfu")
id("kotlinx-atomicfu")
id("net.mamoe.kotlin-jvm-blocking-bridge")
`maven-publish`
id("com.jfrog.bintray")

View File

@ -0,0 +1,51 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.utils
import kotlinx.atomicfu.atomic
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
private val UNINITIALIZED: Any? = Symbol("UNINITIALIZED")
/**
* - [initializer] is supported to be called at most once, however multiple invocations may happen if executed by multiple coroutines in single thread.
* - [ReadWriteProperty.setValue] prevails on competition with [initializer].
*/
public fun <T> lateinitMutableProperty(initializer: () -> T): ReadWriteProperty<Any?, T> =
LateinitMutableProperty(initializer)
private class LateinitMutableProperty<T>(
initializer: () -> T
) : ReadWriteProperty<Any?, T> {
private val value = atomic(UNINITIALIZED)
private var initializer: (() -> T)? = initializer
@Suppress("UNCHECKED_CAST")
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return when (val v = this.value.value) {
UNINITIALIZED -> synchronized(this) {
val initializer = initializer
if (initializer != null && this.value.value === UNINITIALIZED) {
val value = initializer()
this.value.compareAndSet(UNINITIALIZED, value) // setValue prevails
this.initializer = null
value
} else v as T
}
else -> v as T
}
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value.value = value
}
}

View File

@ -42,3 +42,23 @@ public inline fun CoroutineScope.launchWithPermit(
semaphore.withPermit { block() }
}
}
/**
* Creates a child scope of the receiver scope.
*/
public fun CoroutineScope.childScope(
coroutineContext: CoroutineContext = EmptyCoroutineContext,
): CoroutineScope {
val ctx = this.coroutineContext + coroutineContext
return CoroutineScope(ctx + SupervisorJob(ctx.job))
}
/**
* Creates a child scope of the receiver context scope.
*/
public fun CoroutineContext.childScope(
coroutineContext: CoroutineContext = EmptyCoroutineContext,
): CoroutineScope {
val ctx = this + coroutineContext
return CoroutineScope(ctx + SupervisorJob(ctx.job))
}

View File

@ -0,0 +1,21 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.utils
public class Symbol private constructor(name: String) {
private val str = "Symbol($name)"
override fun toString(): String = str
public companion object {
@Suppress("RedundantNullableReturnType")
@JvmName("create")
public operator fun invoke(name: String): Any? = Symbol(name) // calls constructor
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.utils
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.util.concurrent.CompletableFuture
import java.util.concurrent.atomic.AtomicInteger
import kotlin.concurrent.thread
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertSame
internal class LateinitMutablePropertyTest {
@Test
fun canInitialize() {
val value = Symbol("expected")
val prop by lateinitMutableProperty { value }
assertSame(value, prop)
}
@Test
fun canOverride() {
val value = Symbol("expected")
val overrode = Symbol("override")
var prop by lateinitMutableProperty { value }
prop = overrode
assertSame(overrode, prop)
}
@Test
fun initializerCalledOnce() {
val value = Symbol("expected")
val counter = AtomicInteger(0)
val prop by lateinitMutableProperty {
counter.incrementAndGet()
value
}
assertSame(value, prop)
assertSame(value, prop)
assertEquals(1, counter.get())
}
@Test
fun initializerCalledOnceConcurrent() = runBlocking {
val value = Symbol("expected")
val counter = AtomicInteger(0)
val verySlowInitializer = CompletableFuture<Unit>()
val prop by lateinitMutableProperty {
counter.incrementAndGet()
verySlowInitializer.join() // do not use coroutine: coroutines run in same thread so `synchronized` doesnt work.
value
}
val lock = CompletableDeferred<Unit>()
repeat(10) {
launch {
lock.join()
@Suppress("UNUSED_EXPRESSION")
prop
}
}
lock.complete(Unit) // resume callers
verySlowInitializer.complete(Unit)
assertSame(value, prop)
assertEquals(1, counter.get())
}
@Test
fun setValuePrevailsOnCompetitionWithInitializer() {
val verySlowInitializer = CompletableFuture<Unit>()
val override = Symbol("override")
val initializer = Symbol("initializer")
var prop by lateinitMutableProperty {
verySlowInitializer.join()
initializer
}
thread { println(prop) }
prop = override
verySlowInitializer.complete(Unit)
assertSame(override, prop)
}
}