From 092750e2159f45558c62598ef365515842a51af4 Mon Sep 17 00:00:00 2001 From: Alexander Shabalin Date: Mon, 28 Jun 2021 15:03:46 +0300 Subject: [PATCH] Fix race in Lazy for the new MM --- .../backend.native/tests/build.gradle | 5 ++ .../tests/runtime/workers/lazy4.kt | 65 +++++++++++++++++++ .../kotlin/kotlin/native/concurrent/Lazy.kt | 7 +- 3 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 kotlin-native/backend.native/tests/runtime/workers/lazy4.kt diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index 6720d71b425..5d8f2f716ce 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -1064,6 +1064,11 @@ standaloneTest("lazy3") { source = "runtime/workers/lazy3.kt" } +task lazy4(type: KonanLocalTest) { + enabled = (project.testTarget != 'wasm32') // Workers need pthreads. Need exceptions + source = "runtime/workers/lazy4.kt" +} + task mutableData1(type: KonanLocalTest) { enabled = (project.testTarget != 'wasm32') // Workers need pthreads. Need exceptions source = "runtime/workers/mutableData1.kt" diff --git a/kotlin-native/backend.native/tests/runtime/workers/lazy4.kt b/kotlin-native/backend.native/tests/runtime/workers/lazy4.kt new file mode 100644 index 00000000000..3a565de67b1 --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/workers/lazy4.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +package runtime.workers.lazy4 + +import kotlin.test.* + +import kotlin.native.concurrent.* + +const val WORKERS_COUNT = 20 + +class C(private val initializer: () -> Int) { + val data by lazy { initializer() } +} + +fun concurrentLazyAccess(freeze: Boolean) { + val initializerCallCount = AtomicInt(0) + + val c = C { + initializerCallCount.increment() + 42 + } + if (freeze) { + c.freeze() + } + + val workers = Array(WORKERS_COUNT, { Worker.start() }) + val inited = AtomicInt(0) + val canStart = AtomicInt(0) + val futures = Array(workers.size) { i -> + workers[i].execute(TransferMode.SAFE, { Triple(inited, canStart, c) }) { (inited, canStart, c) -> + inited.increment() + while (canStart.value != 1) {} + c.data + } + } + + while (inited.value < workers.size) {} + canStart.value = 1 + + futures.forEach { + assertEquals(42, it.result) + } + workers.forEach { + it.requestTermination().result + } + + assertEquals(1, initializerCallCount.value) +} + +@Test +fun concurrentLazyAccessUnfrozen() { + if (Platform.memoryModel != MemoryModel.EXPERIMENTAL) { + return + } + concurrentLazyAccess(false) +} + +@Test +fun concurrentLazyAccessFrozen() { + concurrentLazyAccess(true) +} + diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Lazy.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Lazy.kt index e71e6ffc4b5..0e814614f64 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Lazy.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Lazy.kt @@ -9,6 +9,8 @@ import kotlin.native.internal.Frozen internal class FreezeAwareLazyImpl(initializer: () -> T) : Lazy { private val value_ = FreezableAtomicReference(UNINITIALIZED) + // This cannot be made atomic because of the legacy MM. See https://github.com/JetBrains/kotlin-native/pull/3944 + // So it must be protected by the lock below. private var initializer_: (() -> T)? = initializer private val lock_ = Lock() @@ -46,9 +48,10 @@ internal class FreezeAwareLazyImpl(initializer: () -> T) : Lazy { override val value: T get() { - return if (isFrozen) { + return if (isShareable()) { + // TODO: This is probably a big performance problem for lazy with the new MM. Address it. locked(lock_) { - getOrInit(true) + getOrInit(isFrozen) } } else { getOrInit(false)