Browse Source

Add AtomicLazy

Him188 4 years ago
parent
commit
48564056df

+ 1 - 1
mirai-core-utils/build.gradle.kts

@@ -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")

+ 51 - 0
mirai-core-utils/src/commonMain/kotlin/AtomicLazy.kt

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

+ 20 - 0
mirai-core-utils/src/commonMain/kotlin/CoroutineUtils.kt

@@ -41,4 +41,24 @@ public inline fun CoroutineScope.launchWithPermit(
     return launch(coroutineContext) {
         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))
 }

+ 21 - 0
mirai-core-utils/src/commonMain/kotlin/Symbol.kt

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

+ 103 - 0
mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/LateinitMutablePropertyTest.kt

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