From 9ad7b006130e998e7c04404d0f5a8638331e5e87 Mon Sep 17 00:00:00 2001 From: Alexander Shabalin Date: Mon, 30 Oct 2023 13:15:20 +0100 Subject: [PATCH] [K/N] Migrate some runtime/workers tests to new testing infra ^KT-61259 --- .../backend.native/tests/build.gradle | 74 --- .../tests/runtime/workers/lazy0.kt | 89 ---- .../tests/runtime/workers/lazy0.out | 1 - .../tests/runtime/workers/lazy1.kt | 103 ----- .../tests/runtime/workers/lazy1.out | 1 - .../tests/runtime/workers/lazy4.kt | 77 ---- .../tests/runtime/workers/worker0.kt | 26 -- .../tests/runtime/workers/worker0.out | 2 - .../tests/runtime/workers/worker1.kt | 36 -- .../tests/runtime/workers/worker1.out | 1 - .../tests/runtime/workers/worker11.kt | 118 ----- .../tests/runtime/workers/worker11.out | 1 - .../tests/runtime/workers/worker2.kt | 46 -- .../tests/runtime/workers/worker2.out | 1 - .../tests/runtime/workers/worker3.kt | 37 -- .../tests/runtime/workers/worker3.out | 1 - .../tests/runtime/workers/worker4.kt | 185 -------- .../tests/runtime/workers/worker4.out | 2 - .../tests/runtime/workers/worker5.kt | 35 +- .../tests/runtime/workers/worker5.out | 2 - .../tests/runtime/workers/worker6.kt | 44 -- .../tests/runtime/workers/worker6.out | 2 - .../tests/runtime/workers/worker7.kt | 29 -- .../tests/runtime/workers/worker7.out | 3 - .../tests/runtime/workers/worker8.kt | 33 -- .../tests/runtime/workers/worker8.out | 3 - .../runtime/workers/worker9_experimentalMM.kt | 92 ---- .../workers/worker9_experimentalMM.out | 7 - .../workers/worker_exception_messages.kt | 104 ----- .../runtime/workers/worker_list_workers.kt | 38 -- .../test/native/concurrent/WorkerTest.kt | 429 ++++++++++++++++++ .../runtime/test/utils/LazyNativeTest.kt | 112 +++++ libraries/stdlib/build.gradle.kts | 1 + .../kotlin/konan/test/blackbox/StdlibTest.kt | 5 +- 34 files changed, 547 insertions(+), 1193 deletions(-) delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/lazy0.kt delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/lazy0.out delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/lazy1.kt delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/lazy1.out delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/lazy4.kt delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker0.kt delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker0.out delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker1.kt delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker1.out delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker11.kt delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker11.out delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker2.kt delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker2.out delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker3.kt delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker3.out delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker4.kt delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker4.out delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker6.kt delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker6.out delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker7.kt delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker7.out delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker8.kt delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker8.out delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker9_experimentalMM.kt delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker9_experimentalMM.out delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker_exception_messages.kt delete mode 100644 kotlin-native/backend.native/tests/runtime/workers/worker_list_workers.kt create mode 100644 kotlin-native/runtime/test/native/concurrent/WorkerTest.kt create mode 100644 kotlin-native/runtime/test/utils/LazyNativeTest.kt diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index f14078fbc99..744084bf201 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -845,73 +845,18 @@ standaloneTest("worker_bound_reference0") { } } -tasks.register("worker0", KonanLocalTest) { - useGoldenData = true - source = "runtime/workers/worker0.kt" -} - -tasks.register("worker1", KonanLocalTest) { - useGoldenData = true - source = "runtime/workers/worker1.kt" -} - -tasks.register("worker2", KonanLocalTest) { - useGoldenData = true - source = "runtime/workers/worker2.kt" -} - -tasks.register("worker3", KonanLocalTest) { - useGoldenData = true - source = "runtime/workers/worker3.kt" -} - -tasks.register("worker4", KonanLocalTest) { - useGoldenData = true - source = "runtime/workers/worker4.kt" -} - // This tests changes main thread worker queue state, so better be executed alone. standaloneTest("worker5") { useGoldenData = true source = "runtime/workers/worker5.kt" } -tasks.register("worker6", KonanLocalTest) { - useGoldenData = true - source = "runtime/workers/worker6.kt" -} - -tasks.register("worker7", KonanLocalTest) { - useGoldenData = true - source = "runtime/workers/worker7.kt" -} - -tasks.register("worker8", KonanLocalTest) { - useGoldenData = true - source = "runtime/workers/worker8.kt" -} - -tasks.register("worker9_experimentalMM", KonanLocalTest) { - useGoldenData = true - source = "runtime/workers/worker9_experimentalMM.kt" -} - tasks.register("worker10", KonanLocalTest) { enabled = !isNoopGC useGoldenData = true source = "runtime/workers/worker10.kt" } -tasks.register("worker11", KonanLocalTest) { - enabled = !isAggressiveGC // TODO: Investigate why too slow - useGoldenData = true - source = "runtime/workers/worker11.kt" -} - -tasks.register("worker_exception_messages", KonanLocalTest) { - source = "runtime/workers/worker_exception_messages.kt" -} - standaloneTest("worker_exceptions") { flags = ["-tr", "-Xworker-exception-handling=use-hook"] outputChecker = { @@ -998,10 +943,6 @@ standaloneTest("worker_threadlocal_no_leak") { source = "runtime/workers/worker_threadlocal_no_leak.kt" } -tasks.register("worker_list_workers", KonanLocalTest) { - source = "runtime/workers/worker_list_workers.kt" -} - standaloneTest("freeze_disabled") { enabled = !isNoopGC flags = ["-tr"] @@ -1009,16 +950,6 @@ standaloneTest("freeze_disabled") { testLogger = KonanTest.Logger.SILENT } -tasks.register("lazy0", KonanLocalTest) { - useGoldenData = true - source = "runtime/workers/lazy0.kt" -} - -tasks.register("lazy1", KonanLocalTest) { - useGoldenData = true - source = "runtime/workers/lazy1.kt" -} - standaloneTest("lazy2") { useGoldenData = true source = "runtime/workers/lazy2.kt" @@ -1029,11 +960,6 @@ standaloneTest("lazy3") { source = "runtime/workers/lazy3.kt" } -tasks.register("lazy4", KonanLocalTest) { - enabled = !isAggressiveGC // TODO: Investigate why too slow - source = "runtime/workers/lazy4.kt" -} - tasks.register("mutableData1", KonanLocalTest) { source = "runtime/workers/mutableData1.kt" } diff --git a/kotlin-native/backend.native/tests/runtime/workers/lazy0.kt b/kotlin-native/backend.native/tests/runtime/workers/lazy0.kt deleted file mode 100644 index 33ffc2bc957..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/lazy0.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2010-2018 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. - */ - -@file:OptIn(FreezingIsDeprecated::class, ObsoleteWorkersApi::class) -package runtime.workers.lazy0 - -import kotlin.test.* - -import kotlin.native.concurrent.* - -data class Data(val x: Int, val y: String) - -object Immutable { - val x by atomicLazy { - 42 - } -} - -object Immutable2 { - val y by atomicLazy { - Data(239, "Kotlin") - } -} - -object Immutable3 { - val x by lazy { - var result = 0 - for (i in 1 .. 1000) - result += i - result - } -} - -fun testSingleData(workers: Array) { - val set = mutableSetOf() - for (attempt in 1 .. 3) { - val futures = Array(workers.size, { workerIndex -> - workers[workerIndex].execute(TransferMode.SAFE, { "" }) { _ -> Immutable2.y } - }) - futures.forEach { set += it.result } - } - assertEquals(set.size, 1) - assertEquals(set.single(), Immutable2.y) -} - -fun testFrozenLazy(workers: Array) { - // To make sure it is always frozen, and we don't race in relaxed mode. - Immutable3.freeze() - val set = mutableSetOf() - for (attempt in 1 .. 3) { - val futures = Array(workers.size, { workerIndex -> - workers[workerIndex].execute(TransferMode.SAFE, { "" }) { _ -> Immutable3.x } - }) - futures.forEach { set += it.result } - } - assertEquals(1, set.size) - assertEquals(Immutable3.x, set.single()) - assertEquals(1001 * 500, set.single()) -} - -fun testLiquidLazy() { - class L { - val value by lazy { - 17 - } - } - val l1 = L() - for (i in 1 .. 100) - assertEquals(l1.value, 17) - - val l2 = L() - l2.freeze() - for (i in 1 .. 100) - assertEquals(l2.value, 17) -} - -@Test fun runTest() { - assertEquals(42, Immutable.x) - - val COUNT = 5 - val workers = Array(COUNT, { _ -> Worker.start()}) - testSingleData(workers) - testFrozenLazy(workers) - testLiquidLazy() - - println("OK") -} diff --git a/kotlin-native/backend.native/tests/runtime/workers/lazy0.out b/kotlin-native/backend.native/tests/runtime/workers/lazy0.out deleted file mode 100644 index d86bac9de59..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/lazy0.out +++ /dev/null @@ -1 +0,0 @@ -OK diff --git a/kotlin-native/backend.native/tests/runtime/workers/lazy1.kt b/kotlin-native/backend.native/tests/runtime/workers/lazy1.kt deleted file mode 100644 index 88a6da66c19..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/lazy1.kt +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2010-2018 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. - */ - -@file:OptIn(kotlin.experimental.ExperimentalNativeApi::class, FreezingIsDeprecated::class) -package runtime.workers.lazy1 - -import kotlin.test.* -import kotlin.native.concurrent.* - -private var y = 20 - -class Lazy(mode: LazyThreadSafetyMode) { - val x = 17 - val self by lazy(mode) { this } - val recursion: Int by lazy(mode) { - if (x < 17) 42 else recursion - } - val finiteRecursion : Int by lazy(mode) { - if (y < 17) 42 else { - y -= 1 - finiteRecursion + 1 - } - } - val freezer: Int by lazy(mode) { - freeze() - 42 - } - val thrower: String by lazy(mode) { - if (x < 100) throw IllegalArgumentException() - "FAIL" - } -} - -private val checkedLazyModes = - if (Platform.memoryModel != MemoryModel.EXPERIMENTAL) - listOf(LazyThreadSafetyMode.PUBLICATION) - else - listOf(LazyThreadSafetyMode.SYNCHRONIZED, LazyThreadSafetyMode.PUBLICATION) - -@Test fun runTest1() { - for (mode in checkedLazyModes) { - // We decided to synchonaize behaviour to be consistent with jvm version. - // Anyway, it's doesn't looks like well-defined case - if (Platform.memoryModel == MemoryModel.EXPERIMENTAL) { - val expected = if (mode == LazyThreadSafetyMode.SYNCHRONIZED) 46 else 42 - y = 20 - assertEquals(Lazy(mode).finiteRecursion, expected) - y = 20 - assertEquals(Lazy(mode).freeze().finiteRecursion, expected) - } else { - assertFailsWith { - println(Lazy(mode).recursion) - } - assertFailsWith { - println(Lazy(mode).freeze().recursion) - } - y = 20 - assertFailsWith { - println(Lazy(mode).finiteRecursion) - } - y = 20 - assertFailsWith { - println(Lazy(mode).freeze().finiteRecursion) - } - } - } -} - -@Test fun runTest2() { - for (mode in checkedLazyModes) { - var sum = 0 - for (i in 1..100) { - val self = Lazy(mode).freeze() - assertEquals(self, self.self) - sum += self.self.hashCode() - } - } - println("OK") -} - - -@Test fun runTest3() { - if (Platform.isFreezingEnabled) { - for (mode in checkedLazyModes) { - assertFailsWith { - println(Lazy(mode).freezer) - } - } - } -} - -@Test fun runTest4() { - for (mode in checkedLazyModes) { - val self = Lazy(mode) - repeat(10) { - assertFailsWith { - println(self.thrower) - } - } - } -} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/workers/lazy1.out b/kotlin-native/backend.native/tests/runtime/workers/lazy1.out deleted file mode 100644 index d86bac9de59..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/lazy1.out +++ /dev/null @@ -1 +0,0 @@ -OK diff --git a/kotlin-native/backend.native/tests/runtime/workers/lazy4.kt b/kotlin-native/backend.native/tests/runtime/workers/lazy4.kt deleted file mode 100644 index 4a2fc9b3aa5..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/lazy4.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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. - */ - -@file:OptIn(kotlin.experimental.ExperimentalNativeApi::class, FreezingIsDeprecated::class, ObsoleteWorkersApi::class) -package runtime.workers.lazy4 - -import kotlin.test.* -import kotlin.concurrent.AtomicInt -import kotlin.concurrent.* -import kotlin.native.concurrent.* - -const val WORKERS_COUNT = 20 - -class IntHolder(val value:Int) - -class C(mode: LazyThreadSafetyMode, private val initializer: () -> IntHolder) { - val data by lazy(mode) { initializer() } -} - -fun concurrentLazyAccess(freeze: Boolean, mode: LazyThreadSafetyMode) { - // in old mm PUBLICATION is in fact SYNCHRONIZED, while SYNCHRONIZED is not supported - val argumentMode = if (Platform.memoryModel == MemoryModel.EXPERIMENTAL) mode else LazyThreadSafetyMode.PUBLICATION - val initializerCallCount = AtomicInt(0) - - val c = C(argumentMode) { - initializerCallCount.incrementAndGet() - IntHolder(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.incrementAndGet() - while (canStart.value != 1) {} - c.data - } - } - - while (inited.value < workers.size) {} - canStart.value = 1 - - val results = futures.map { it.result } - results.forEach { - assertEquals(42, it.value) - assertSame(results[0], it) - } - workers.forEach { - it.requestTermination().result - } - - if (mode == LazyThreadSafetyMode.SYNCHRONIZED) { - assertEquals(1, initializerCallCount.value) - } -} - -@Test -fun concurrentLazyAccessUnfrozen() { - if (Platform.memoryModel != MemoryModel.EXPERIMENTAL) { - return - } - concurrentLazyAccess(false, LazyThreadSafetyMode.SYNCHRONIZED) - concurrentLazyAccess(false, LazyThreadSafetyMode.PUBLICATION) -} - -@Test -fun concurrentLazyAccessFrozen() { - concurrentLazyAccess(true, LazyThreadSafetyMode.SYNCHRONIZED) - concurrentLazyAccess(true, LazyThreadSafetyMode.PUBLICATION) -} - diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker0.kt b/kotlin-native/backend.native/tests/runtime/workers/worker0.kt deleted file mode 100644 index 544391da85e..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker0.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2010-2018 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. - */ - -@file:OptIn(ObsoleteWorkersApi::class) -package runtime.workers.worker0 - -import kotlin.test.* - -import kotlin.native.concurrent.* - -@Test fun runTest() { - val worker = Worker.start() - val future = worker.execute(TransferMode.SAFE, { "Input" }) { - input -> - assertEquals(1, 1) - assertFailsWith { assertEquals(1, 2) } - input + " processed" - } - future.consume { - result -> println("Got $result") - } - worker.requestTermination().result - println("OK") -} diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker0.out b/kotlin-native/backend.native/tests/runtime/workers/worker0.out deleted file mode 100644 index 1276792036f..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker0.out +++ /dev/null @@ -1,2 +0,0 @@ -Got Input processed -OK diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker1.kt b/kotlin-native/backend.native/tests/runtime/workers/worker1.kt deleted file mode 100644 index 9be59a9fa66..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker1.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2010-2018 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. - */ - -@file:OptIn(ObsoleteWorkersApi::class) -package runtime.workers.worker1 - -import kotlin.test.* - -import kotlin.native.concurrent.* - -@Test fun runTest() { - val COUNT = 5 - val workers = Array(COUNT, { _ -> Worker.start()}) - - for (attempt in 1 .. 3) { - val futures = Array(workers.size, - { i -> workers[i].execute(TransferMode.SAFE, { "$attempt: Input $i" }) - { input -> input + " processed" } - }) - futures.forEachIndexed { index, future -> - future.consume { - result -> - if ("$attempt: Input $index processed" != result) { - println("Got unexpected $result") - throw Error(result) - } - } - } - } - workers.forEach { - it.requestTermination().result - } - println("OK") -} diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker1.out b/kotlin-native/backend.native/tests/runtime/workers/worker1.out deleted file mode 100644 index d86bac9de59..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker1.out +++ /dev/null @@ -1 +0,0 @@ -OK diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker11.kt b/kotlin-native/backend.native/tests/runtime/workers/worker11.kt deleted file mode 100644 index b5527c552f0..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker11.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2010-2018 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. - */ - -@file:OptIn(kotlin.experimental.ExperimentalNativeApi::class, FreezingIsDeprecated::class, ObsoleteWorkersApi::class) -package runtime.workers.worker11 - -import kotlin.test.* -import kotlin.concurrent.AtomicInt -import kotlin.concurrent.* -import kotlin.native.concurrent.* -import kotlinx.cinterop.convert - -data class Job(val index: Int, var input: Int, var counter: Int) - -fun initJobs(count: Int) = Array(count) { i -> Job(i, i * 2, i)} - -@Test fun runTest0() { - val workers = Array(100, { _ -> Worker.start() }) - val jobs = initJobs(workers.size) - val futures = Array(workers.size, { workerIndex -> - workers[workerIndex].execute(TransferMode.SAFE, { - val job = jobs[workerIndex] - jobs[workerIndex] = null - job!! - }) { job -> - job.counter += job.input - job - } - }) - val futureSet = futures.toSet() - var consumed = 0 - while (consumed < futureSet.size) { - val ready = waitForMultipleFutures(futureSet, 10000) - ready.forEach { - it.consume { job -> - assertEquals(job.index * 3, job.counter) - jobs[job.index] = job - } - consumed++ - } - } - assertEquals(consumed, workers.size) - workers.forEach { - it.requestTermination().result - } - println("OK") -} - -val COUNT = 2 - -val counters = Array(COUNT) { AtomicInt(0) } - -@Test fun runTest1() { - val workers = Array(COUNT) { Worker.start() } - // Ensure processQueue() can only be called on current Worker. - workers.forEach { - assertFailsWith { - it.processQueue() - } - } - val futures = Array(workers.size) { workerIndex -> - workers[workerIndex].execute(TransferMode.SAFE, { - workerIndex - }) { - index -> - assertEquals(0, counters[index].value) - // Process following request. - while (!Worker.current!!.processQueue()) {} - // Ensure it has an effect. - assertEquals(1, counters[index].value) - // No more non-terminating tasks in this worker queue. - assertEquals(false, Worker.current!!.processQueue()) - } - } - val futures2 = Array(workers.size) { workerIndex -> - workers[workerIndex].execute(TransferMode.SAFE, { - workerIndex - }) { index -> - assertEquals(0, counters[index].value) - counters[index].incrementAndGet() - } - } - futures2.forEach { it.result } - futures.forEach { it.result } - workers.forEach { - it.requestTermination().result - } - - // Ensure terminated workers are no longer there. - workers.forEach { - assertFailsWith { it.execute(TransferMode.SAFE, { Unit }) { println("ERROR") } } - } -} - -@Test fun runTest2() { - val workers = Array(COUNT) { Worker.start() } - val futures = Array(workers.size) { workerIndex -> - workers[workerIndex].execute(TransferMode.SAFE, { null }) { - // Here we processed termination request. - assertEquals(false, Worker.current.processQueue()) - } - } - workers.forEach { - it.executeAfter(1000L*1000*1000, { - println("DELAY EXECUTED") - assert(false) - }.freeze()) - } - workers.forEach { - it.requestTermination(processScheduledJobs = false).result - } - // Process futures, ignoring possible cancelled ones. - futures.forEach { - try { it.result } catch (e: IllegalStateException) {} - } -} diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker11.out b/kotlin-native/backend.native/tests/runtime/workers/worker11.out deleted file mode 100644 index d86bac9de59..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker11.out +++ /dev/null @@ -1 +0,0 @@ -OK diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker2.kt b/kotlin-native/backend.native/tests/runtime/workers/worker2.kt deleted file mode 100644 index a09f070721e..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker2.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2010-2018 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. - */ - -@file:OptIn(ObsoleteWorkersApi::class) -package runtime.workers.worker2 - -import kotlin.test.* - -import kotlin.native.concurrent.* - -data class WorkerArgument(val intParam: Int, val stringParam: String) -data class WorkerResult(val intResult: Int, val stringResult: String) - -@Test fun runTest() { - val COUNT = 5 - val workers = Array(COUNT, { _ -> Worker.start()}) - - for (attempt in 1 .. 3) { - val futures = Array(workers.size, { workerIndex -> workers[workerIndex].execute(TransferMode.SAFE, { - WorkerArgument(workerIndex, "attempt $attempt") }) { input -> - var sum = 0 - for (i in 0..input.intParam * 1000) { - sum += i - } - WorkerResult(sum, input.stringParam + " result") - } - }) - val futureSet = futures.toSet() - var consumed = 0 - while (consumed < futureSet.size) { - val ready = waitForMultipleFutures(futureSet, 10000) - ready.forEach { - it.consume { result -> - if (result.stringResult != "attempt $attempt result") throw Error("Unexpected $result") - consumed++ - } - } - } - } - workers.forEach { - it.requestTermination().result - } - println("OK") -} diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker2.out b/kotlin-native/backend.native/tests/runtime/workers/worker2.out deleted file mode 100644 index d86bac9de59..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker2.out +++ /dev/null @@ -1 +0,0 @@ -OK diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker3.kt b/kotlin-native/backend.native/tests/runtime/workers/worker3.kt deleted file mode 100644 index a9a1d8fe747..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker3.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2010-2018 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. - */ - -@file:OptIn(kotlin.experimental.ExperimentalNativeApi::class, ObsoleteWorkersApi::class) -package runtime.workers.worker3 - -import kotlin.test.* - -import kotlin.native.concurrent.* - -data class DataParam(var int: Int) -data class WorkerArgument(val intParam: Int, val dataParam: DataParam) -data class WorkerResult(val intResult: Int, val stringResult: String) - -@Test fun runTest() { - main(emptyArray()) -} - -fun main(args: Array) { - val worker = Worker.start() - val dataParam = DataParam(17) - val future = try { - worker.execute(TransferMode.SAFE, - { WorkerArgument(42, dataParam) }) { - input -> WorkerResult(input.intParam, input.dataParam.toString() + " result") - } - } catch (e: IllegalStateException) { - null - } - if (future != null && Platform.memoryModel == MemoryModel.STRICT) - println("Fail 1") - if (dataParam.int != 17) println("Fail 2") - worker.requestTermination().result - println("OK") -} diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker3.out b/kotlin-native/backend.native/tests/runtime/workers/worker3.out deleted file mode 100644 index d86bac9de59..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker3.out +++ /dev/null @@ -1 +0,0 @@ -OK diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker4.kt b/kotlin-native/backend.native/tests/runtime/workers/worker4.kt deleted file mode 100644 index e095a32f84c..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker4.kt +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright 2010-2018 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. - */ - -@file:OptIn(FreezingIsDeprecated::class, ObsoleteWorkersApi::class) -package runtime.workers.worker4 - -import kotlin.test.* -import kotlin.concurrent.AtomicInt -import kotlin.native.concurrent.* - -@Test fun runTest1() { - withWorker { - val future = execute(TransferMode.SAFE, { 41 }) { input -> - input + 1 - } - future.consume { result -> - println("Got $result") - } - } - println("OK") -} - -@Test fun runTest2() { - withWorker { - val counter = AtomicInt(0) - - executeAfter(0, { - assertTrue(Worker.current.park(10_000_000, false)) - assertEquals(counter.value, 0) - assertTrue(Worker.current.processQueue()) - assertEquals(1, counter.value) - // Let main proceed. - counter.incrementAndGet() // counter becomes 2 here. - assertTrue(Worker.current.park(10_000_000, true)) - assertEquals(3, counter.value) - }.freeze()) - - executeAfter(0, { - counter.incrementAndGet() - Unit - }.freeze()) - - while (counter.value < 2) { - Worker.current.park(1_000) - } - - executeAfter(0, { - counter.incrementAndGet() - Unit - }.freeze()) - - while (counter.value == 2) { - Worker.current.park(1_000) - } - } -} - -@Test fun runTest3() { - val worker = Worker.start(name = "Lumberjack") - val counter = AtomicInt(0) - worker.executeAfter(0, { - assertEquals("Lumberjack", Worker.current.name) - counter.incrementAndGet() - Unit - }.freeze()) - - while (counter.value == 0) { - Worker.current.park(1_000) - } - assertEquals("Lumberjack", worker.name) - worker.requestTermination().result - assertFailsWith { - println(worker.name) - } -} - -@Test fun runTest4() { - val counter = AtomicInt(0) - Worker.current.executeAfter(10_000, { - counter.incrementAndGet() - Unit - }.freeze()) - assertTrue(Worker.current.park(1_000_000, process = true)) - assertEquals(1, counter.value) -} - -@Test fun runTest5() { - val main = Worker.current - val counter = AtomicInt(0) - withWorker { - executeAfter(1000, { - main.executeAfter(1, { - counter.incrementAndGet() - Unit - }.freeze()) - }.freeze()) - assertTrue(main.park(1000L * 1000 * 1000, process = true)) - assertEquals(1, counter.value) - } -} - -@Test fun runTest6() { - // Ensure zero timeout works properly. - Worker.current.park(0, process = true) -} - -@Test fun runTest7() { - val counter = AtomicInt(0) - withWorker { - val f1 = execute(TransferMode.SAFE, { counter }) { counter -> - Worker.current.park(Long.MAX_VALUE / 1000L, process = true) - counter.incrementAndGet() - } - // wait a bit - Worker.current.park(10_000L) - // submit a task - val f2 = execute(TransferMode.SAFE, { counter }) { counter -> - counter.incrementAndGet() - } - f1.consume {} - f2.consume {} - assertEquals(2, counter.value) - } -} - -// This test checks that when multiple `executeAfter` jobs are submitted to `targetWorker` and have the -// same scheduled execution time (in micros since an epoch), nether of them gets lost. -@Test fun testExecuteAfterScheduledTimeClash() = withWorker { - val targetWorker = this - val mainWorker = Worker.current - - // Configuration of the test. - val numberOfSubmitters = 2 - val numberOfTasks = 100 - val delayInMicroseconds = 100L - - val submitters = Array(numberOfSubmitters) { Worker.start() } - try { - val readySubmittersCounter = AtomicInt(0) - val executedTasksCounter = AtomicInt(0) - val finishedBatchesCounter = AtomicInt(0) - - submitters.forEach { - it.executeAfter(0L, { - readySubmittersCounter.incrementAndGet() - // Wait for other submitters, to make them all start at the same time: - while (readySubmittersCounter.value != numberOfSubmitters) {} - - // Concurrently submit tasks with matching scheduled execution time: - repeat(numberOfTasks) { - targetWorker.executeAfter(delayInMicroseconds, { - executedTasksCounter.incrementAndGet() - Unit - }.freeze()) - } - - // Use larger delay for the task below, to make sure it gets executed after - // the tasks above submitted by the same worker. - // If the order is wrong, the test will fail as well. - // NOTE: the code below was affected by the same problem with clashing times, so despite all the effort - // the test still might hang without a fix. - targetWorker.executeAfter(delayInMicroseconds + 1, { - mainWorker.executeAfter(0L, { - finishedBatchesCounter.incrementAndGet() - Unit - }.freeze()) - }.freeze()) - }.freeze()) - } - - while (finishedBatchesCounter.value != numberOfSubmitters) { - // Wait and allow processing the `finishedBatchesCounter.increment()` tasks above: - Worker.current.park(delayInMicroseconds, process = true) - } - - // Note: we could have just waited for the condition above to become true, - // but this would mean that the test would hang in case of failure, which is not quite convenient. - - assertEquals(numberOfSubmitters * numberOfTasks, executedTasksCounter.value) - } finally { - submitters.forEach { it.requestTermination().result } - } -} diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker4.out b/kotlin-native/backend.native/tests/runtime/workers/worker4.out deleted file mode 100644 index e40dd2ee66e..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker4.out +++ /dev/null @@ -1,2 +0,0 @@ -Got 42 -OK diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker5.kt b/kotlin-native/backend.native/tests/runtime/workers/worker5.kt index 649c9880b47..93f59a12048 100644 --- a/kotlin-native/backend.native/tests/runtime/workers/worker5.kt +++ b/kotlin-native/backend.native/tests/runtime/workers/worker5.kt @@ -8,33 +8,8 @@ import kotlin.test.* import kotlin.native.concurrent.* -fun runTest0() { - val worker = Worker.start() - val future = worker.execute(TransferMode.SAFE, { "zzz" }) { - input -> input.length - } - future.consume { - result -> println("Got $result") - } - worker.requestTermination().result - println("OK") -} - -var done = false - -fun runTest1() { - val worker = Worker.current - done = false - // Here we request execution of the operation on the current worker. - worker.executeAfter(0, { - done = true - }.freeze()) - while (!done) - worker.processQueue() -} - // Ensure that termination of current worker on main thread doesn't lead to problems. -fun runTest2() { +fun main() { val worker = Worker.current val future = worker.requestTermination(false) worker.processQueue() @@ -43,11 +18,5 @@ fun runTest2() { // After termination request this worker is no longer addressable. assertFailsWith { worker.executeAfter(0, { println("BUG!") - }.freeze()) } -} - -fun main() { - runTest0() - runTest1() - runTest2() + })} } diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker5.out b/kotlin-native/backend.native/tests/runtime/workers/worker5.out index ee5b59aacf2..e69de29bb2d 100644 --- a/kotlin-native/backend.native/tests/runtime/workers/worker5.out +++ b/kotlin-native/backend.native/tests/runtime/workers/worker5.out @@ -1,2 +0,0 @@ -Got 3 -OK diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker6.kt b/kotlin-native/backend.native/tests/runtime/workers/worker6.kt deleted file mode 100644 index 146488da499..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker6.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2010-2018 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. - */ - -@file:OptIn(kotlin.experimental.ExperimentalNativeApi::class, FreezingIsDeprecated::class, ObsoleteWorkersApi::class) -package runtime.workers.worker6 - -import kotlin.test.* - -import kotlin.native.concurrent.* - -@Test fun runTest1() { - withWorker { - val future = execute(TransferMode.SAFE, { 42 }) { input -> - input.toString() - } - future.consume { result -> - println("Got $result") - } - } - println("OK") -} - -var int1 = 1 -val int2 = 77 - -@Test fun runTest2() { - int1++ - withWorker { - executeAfter(0, { - if (kotlin.native.Platform.memoryModel == kotlin.native.MemoryModel.EXPERIMENTAL) { - int1++ - assertEquals(3, int1) - } else { - assertFailsWith { - int1++ - } - assertEquals(2, int1) - } - assertEquals(77, int2) - }.freeze()) - } -} diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker6.out b/kotlin-native/backend.native/tests/runtime/workers/worker6.out deleted file mode 100644 index e40dd2ee66e..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker6.out +++ /dev/null @@ -1,2 +0,0 @@ -Got 42 -OK diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker7.kt b/kotlin-native/backend.native/tests/runtime/workers/worker7.kt deleted file mode 100644 index c038858b98d..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker7.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2010-2018 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. - */ - -@file:OptIn(ObsoleteWorkersApi::class) -package runtime.workers.worker7 - -import kotlin.test.* - -import kotlin.native.concurrent.* - -@Test fun runTest() { - val worker = Worker.start(false) - val future = worker.execute(TransferMode.SAFE, { "Input" }) { - input -> println(input) - } - future.consume { - result -> println("Got $result") - } - - assertFailsWith { - println(worker.execute(TransferMode.SAFE, { null }, { _ -> throw Error("An error") }).result) - } - - worker.requestTermination().result - - println("OK") -} diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker7.out b/kotlin-native/backend.native/tests/runtime/workers/worker7.out deleted file mode 100644 index 7d6a8aafdfd..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker7.out +++ /dev/null @@ -1,3 +0,0 @@ -Input -Got kotlin.Unit -OK diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker8.kt b/kotlin-native/backend.native/tests/runtime/workers/worker8.kt deleted file mode 100644 index b9b17d89151..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker8.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2010-2018 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. - */ -@file:OptIn(FreezingIsDeprecated::class, ObsoleteWorkersApi::class, kotlinx.cinterop.ExperimentalForeignApi::class) - -package runtime.workers.worker8 - -import kotlin.test.* - -import kotlin.native.concurrent.* - -data class SharedDataMember(val double: Double) - -data class SharedData(val string: String, val int: Int, val member: SharedDataMember) - -@Test fun runTest() { - val worker = Worker.start() - // Here we do rather strange thing. To test object detach API we detach object graph, - // pass detached graph to a worker, where we manually reattached passed value. - val future = worker.execute(TransferMode.SAFE, { - DetachedObjectGraph { SharedData("Hello", 10, SharedDataMember(0.1)) }.asCPointer() - }) { - inputDetached -> - val input = DetachedObjectGraph(inputDetached).attach() - println(input) - } - future.consume { - result -> println("Got $result") - } - worker.requestTermination().result - println("OK") -} diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker8.out b/kotlin-native/backend.native/tests/runtime/workers/worker8.out deleted file mode 100644 index 6b172c1a83d..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker8.out +++ /dev/null @@ -1,3 +0,0 @@ -SharedData(string=Hello, int=10, member=SharedDataMember(double=0.1)) -Got kotlin.Unit -OK diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker9_experimentalMM.kt b/kotlin-native/backend.native/tests/runtime/workers/worker9_experimentalMM.kt deleted file mode 100644 index db57a385d33..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker9_experimentalMM.kt +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2010-2018 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. - */ - -@file:OptIn(kotlin.experimental.ExperimentalNativeApi::class, FreezingIsDeprecated::class, ObsoleteWorkersApi::class) -package runtime.workers.worker9_experimentalMM - -import kotlin.test.* - -import kotlin.native.concurrent.* - -@Test fun runTest1() { - withLock { println("zzz") } - val worker = Worker.start() - val future = worker.execute(TransferMode.SAFE, {}) { - withLock { - println("42") - } - } - future.result - worker.requestTermination().result - println("OK") -} - -fun withLock(op: () -> Unit) { - op() -} - -@Test fun runTest2() { - val worker = Worker.start() - val future = worker.execute(TransferMode.SAFE, {}) { - val me = Worker.current - var x = 1 - me.executeAfter (20000) { - println("second ${++x}") - } - me.executeAfter(10000) { - println("first ${++x}") - } - } - worker.requestTermination().result -} - -@Test fun runTest3() { - val worker = Worker.start() - if (Platform.memoryModel == MemoryModel.EXPERIMENTAL) { - worker.executeAfter { - println("unfrozen OK") - } - } else { - assertFailsWith { - worker.executeAfter { - println("shall not happen") - } - } - } - assertFailsWith { - worker.executeAfter(-1, { - println("shall not happen") - }.freeze()) - } - - worker.executeAfter(0, { - println("frozen OK") - }.freeze()) - - worker.requestTermination().result -} - -class Node(var node: Node?, var outher: Node?) - -fun makeCyclic(): Node { - val inner = Node(null, null) - inner.node = inner - val outer = Node(null, null) - inner.outher = outer - return outer -} - -@OptIn(kotlin.native.runtime.NativeRuntimeApi::class) -@Test fun runTest4() { - val worker = Worker.start() - - val future = worker.execute(TransferMode.SAFE, { }) { - makeCyclic().also { - kotlin.native.runtime.GC.collect() - } - } - assert(future.result != null) - worker.requestTermination().result -} diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker9_experimentalMM.out b/kotlin-native/backend.native/tests/runtime/workers/worker9_experimentalMM.out deleted file mode 100644 index fc6ed94363d..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker9_experimentalMM.out +++ /dev/null @@ -1,7 +0,0 @@ -zzz -42 -OK -first 2 -second 3 -unfrozen OK -frozen OK diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker_exception_messages.kt b/kotlin-native/backend.native/tests/runtime/workers/worker_exception_messages.kt deleted file mode 100644 index be402e353c5..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker_exception_messages.kt +++ /dev/null @@ -1,104 +0,0 @@ -@file:OptIn(kotlin.experimental.ExperimentalNativeApi::class, FreezingIsDeprecated::class, ObsoleteWorkersApi::class) -package runtime.workers.worker_exception_messages - -import kotlin.test.* - -import kotlin.native.concurrent.* - -@Test -fun checkArgumentTransferFailed(): Unit = withWorker { - if (Platform.memoryModel == MemoryModel.EXPERIMENTAL) return // Transfer is no-op in this case. - - val argument = Any() - val exception = assertFailsWith { - execute(TransferMode.SAFE, { argument }) { - } - } - assertEquals("Unable to transfer object: it is still owned elsewhere", exception.message) -} - -@Test -fun checkDetachedObjectGraphTransferFailed() { - if (Platform.memoryModel == MemoryModel.EXPERIMENTAL) return // Transfer is no-op in this case. - - val obj = Any() - val exception = assertFailsWith { - DetachedObjectGraph { obj } - } - assertEquals("Unable to transfer object: it is still owned elsewhere", exception.message) -} - -@Test -fun checkProcessQueueOnWrongThread(): Unit = withWorker { - val exception = assertFailsWith { - processQueue() - } - assertEquals("Worker is not current or already terminated", exception.message) -} - -@Test -fun checkParkOnWrongThread(): Unit = withWorker { - val exception = assertFailsWith { - park(1L) - } - assertEquals("Worker is not current or already terminated", exception.message) -} - -@Test -fun checkFutureConsumedTwice(): Unit = withWorker { - val future = execute(TransferMode.SAFE, {}) { - 42 - } - assertEquals(42, future.result) - val exception = assertFailsWith { - future.result - } - assertEquals("Future is in an invalid state", exception.message) -} - -@Test -fun checkTerminatedWorkerName() { - val worker = Worker.start(name = "WorkerName") - assertEquals("WorkerName", worker.name) - worker.requestTermination().result - - val exception = assertFailsWith { - worker.name - } - assertEquals("Worker is already terminated", exception.message) -} - -@Test -fun checkTerminatedWorkerExecute() { - val worker = Worker.start() - worker.execute(TransferMode.SAFE, {}, {}).result - worker.requestTermination().result - - val exception = assertFailsWith { - worker.execute(TransferMode.SAFE, {}, {}).result - } - assertEquals("Worker is already terminated", exception.message) -} - -@Test -fun checkTerminatedWorkerExecuteAfter() { - val worker = Worker.start() - worker.executeAfter(0L, {}.freeze()) - worker.requestTermination().result - - val exception = assertFailsWith { - worker.executeAfter(0L, {}.freeze()) - } - assertEquals("Worker is already terminated", exception.message) -} - -@Test -fun checkTerminatedWorkerRequestTermination() { - val worker = Worker.start() - worker.requestTermination().result - - val exception = assertFailsWith { - worker.requestTermination() - } - assertEquals("Worker is already terminated", exception.message) -} diff --git a/kotlin-native/backend.native/tests/runtime/workers/worker_list_workers.kt b/kotlin-native/backend.native/tests/runtime/workers/worker_list_workers.kt deleted file mode 100644 index 52b437049be..00000000000 --- a/kotlin-native/backend.native/tests/runtime/workers/worker_list_workers.kt +++ /dev/null @@ -1,38 +0,0 @@ -@file:OptIn(kotlin.ExperimentalStdlibApi::class, ObsoleteWorkersApi::class) - -package runtime.workers.worker_list_workers - -import kotlin.native.concurrent.* -import kotlin.test.* - -const val WORKER_COUNT = 10 - -@Test -fun getAllWorkers() { - val workers = Array(WORKER_COUNT) { Worker.start() } - - val expectedWorkers = listOf(Worker.current) + workers - - assertEquals(expectedWorkers.toSet(), Worker.activeWorkers.toSet()) - - workers.forEach { - it.requestTermination().result - } -} - -@Test -fun getActiveWorkers() { - val workers = Array(WORKER_COUNT) { Worker.start() } - - val expectedWorkers = mutableListOf(Worker.current) - (0 until WORKER_COUNT step 2).forEach { i -> - workers[i].requestTermination().result - expectedWorkers.add(workers[i + 1]) - } - - assertEquals(expectedWorkers.toSet(), Worker.activeWorkers.toSet()) - - (0 until WORKER_COUNT step 2).forEach { i -> - workers[i + 1].requestTermination().result - } -} diff --git a/kotlin-native/runtime/test/native/concurrent/WorkerTest.kt b/kotlin-native/runtime/test/native/concurrent/WorkerTest.kt new file mode 100644 index 00000000000..a956b20b3e7 --- /dev/null +++ b/kotlin-native/runtime/test/native/concurrent/WorkerTest.kt @@ -0,0 +1,429 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package test.native.concurrent + +import kotlin.concurrent.AtomicInt +import kotlin.native.concurrent.* +import kotlin.test.* + +class WorkerTest { + @Test + fun execute() = withWorker { + val future = execute(TransferMode.SAFE, { "Input" }) { "$it processed" } + assertEquals("Input processed", future.result) + } + + class MyError : Exception("My error") + + @Test + fun executeWithException() = withWorker(errorReporting = false) { + val future = execute(TransferMode.SAFE, {}) { throw MyError() } + // Not `MyError`. + assertFailsWith { future.result } + assertEquals("Still working", execute(TransferMode.SAFE, { "Still" }) { "$it working" }.result) + } + + @OptIn(FreezingIsDeprecated::class) + @Test + fun executeWithDetachedObjectGraph() = withWorker { + data class SharedDataMember(val double: Double) + data class SharedData(val string: String, val int: Int, val member: SharedDataMember) + // Here we do rather strange thing. To test object detach API we detach object graph, + // pass detached graph to a worker, where we manually reattached passed value. + val future = execute(TransferMode.SAFE, { + DetachedObjectGraph { SharedData("Hello", 10, SharedDataMember(0.1)) }.asCPointer() + }) { + DetachedObjectGraph(it).attach() + } + assertEquals(SharedData("Hello", 10, SharedDataMember(double = 0.1)), future.result) + } + + @Test + fun executeOnWrongThread() { + val worker = Worker.start() + worker.requestTermination().result + + val exception = assertFailsWith { + worker.execute(TransferMode.SAFE, {}, {}).result + } + assertEquals("Worker is already terminated", exception.message) + } + + @Test + fun waitForMultipleFutures() { + val workers = Array(5) { Worker.start() } + + val futures = (1..3).flatMap { attempt -> + workers.mapIndexed { index, worker -> + worker.execute(TransferMode.SAFE, { "$attempt: Input $index" }) { "$it processed" } + } + } + + val actual = waitForMultipleFutures(futures, 10000) + + val expected = (1..3).flatMap { attempt -> + (0 until 5).map { index -> "$attempt: Input $index processed" } + }.toSet() + + // actual cannot be empty. + assertTrue(actual.isNotEmpty()) + // Everything in actual must be in expected. + // The reverse is not required to be true: waitForMultipleFutures may return when + // only some futures have completed. + actual.forEach { future -> + // Every actual future is also computed. + assertEquals(FutureState.COMPUTED, future.state) + assertTrue(expected.contains(future.result)) + } + + workers.forEach { + it.requestTermination().result + } + } + + @Test + fun executeWithConcurrentArrayModification() { + val workers = Array(100) { Worker.start() } + val array = Array(workers.size) { it } + val futures = workers.mapIndexed { index, worker -> + worker.execute(TransferMode.SAFE, { array to index }) { (array, index) -> + array[index] += index + } + } + + while (waitForMultipleFutures(futures, 10000).size < futures.size) { + } + + array.forEachIndexed { index, value -> + assertEquals(index * 2, value) + } + + workers.forEach { + it.requestTermination().result + } + } + + @Test + fun executeAfter() = withWorker { + val counter = AtomicInt(0) + + executeAfter(0) { + assertTrue(Worker.current.park(10_000_000, false)) + assertEquals(counter.value, 0) + assertTrue(Worker.current.processQueue()) + assertEquals(1, counter.value) + // Let main proceed. + counter.incrementAndGet() // counter becomes 2 here. + assertTrue(Worker.current.park(10_000_000, true)) + assertEquals(3, counter.value) + } + + executeAfter(0) { + counter.incrementAndGet() + Unit + } + + while (counter.value < 2) { + Worker.current.park(1_000) + } + + executeAfter(0) { + counter.incrementAndGet() + Unit + } + + while (counter.value == 2) { + Worker.current.park(1_000) + } + } + + @Test + fun executeAfterNegativeDelay() = withWorker { + assertFailsWith { executeAfter(-1) {} } + Unit + } + + @Test + fun executeAfterModify() = withWorker { + var v = 1 + val done = AtomicInt(0) + executeAfter(0) { + v++ + assertEquals(2, v) + done.value = 1 + } + while (done.value == 0) { + } + assertEquals(2, v) + } + + @Test + fun executeAfterOrdering() = withWorker { + val counter = AtomicInt(0) + val lastTask = AtomicInt(0) + executeAfter(20_000) { + lastTask.value = 1 + counter.incrementAndGet() + } + executeAfter(10_000) { + lastTask.value = 2 + counter.incrementAndGet() + } + // Wait for both tasks to complete. + while (counter.value != 2) { + } + // Task with id 1 was scheduled to execute later, so it has won. + assertEquals(1, lastTask.value) + } + + @Test + fun executeAfterCancelled() { + val worker = Worker.start() + + val future = worker.execute(TransferMode.SAFE, {}) { + // Here we processed termination request. + assertEquals(false, Worker.current.processQueue()) + } + + worker.executeAfter(1_000_000_000L) { error("FAILURE") } + + // Request worker to terminate and wait for the request to be processed. + worker.requestTermination(processScheduledJobs = false).result + // Now wait for the worker to complete termination, cleaning up after itself. + waitWorkerTermination(worker) + + // `future` is bound to terminated `worker` and so it's not available anymore. + assertFailsWith { future.result } + } + + @Test + fun executeAfterOnMain() { + var done = false + Worker.current.executeAfter(0) { + done = true + } + // Not executed immediately. + assertFalse(done) + // The current worker's queue may be filled with other tasks, so we must loop. + while (!done) { + Worker.current.processQueue() + } + } + + // This test checks that when multiple `executeAfter` jobs are submitted to `targetWorker` and have the + // same scheduled execution time (in micros since an epoch), nether of them gets lost. + @Test + fun executeAfterScheduledTimeClash() = withWorker { + val targetWorker = this + val mainWorker = Worker.current + + // Configuration of the test. + val numberOfSubmitters = 2 + val numberOfTasks = 100 + val delayInMicroseconds = 100L + + val submitters = Array(numberOfSubmitters) { Worker.start() } + try { + val readySubmittersCounter = AtomicInt(0) + val executedTasksCounter = AtomicInt(0) + val finishedBatchesCounter = AtomicInt(0) + + submitters.forEach { + it.executeAfter(0L) { + readySubmittersCounter.incrementAndGet() + // Wait for other submitters, to make them all start at the same time: + while (readySubmittersCounter.value != numberOfSubmitters) { + } + + // Concurrently submit tasks with matching scheduled execution time: + repeat(numberOfTasks) { + targetWorker.executeAfter(delayInMicroseconds) { + executedTasksCounter.incrementAndGet() + Unit + } + } + + // Use larger delay for the task below, to make sure it gets executed after + // the tasks above submitted by the same worker. + // If the order is wrong, the test will fail as well. + // NOTE: the code below was affected by the same problem with clashing times, so despite all the effort + // the test still might hang without a fix. + targetWorker.executeAfter(delayInMicroseconds + 1) { + mainWorker.executeAfter(0L) { + finishedBatchesCounter.incrementAndGet() + Unit + } + } + } + } + + while (finishedBatchesCounter.value != numberOfSubmitters) { + // Wait and allow processing the `finishedBatchesCounter.increment()` tasks above: + Worker.current.park(delayInMicroseconds, process = true) + } + + // Note: we could have just waited for the condition above to become true, + // but this would mean that the test would hang in case of failure, which is not quite convenient. + + assertEquals(numberOfSubmitters * numberOfTasks, executedTasksCounter.value) + } finally { + submitters.forEach { it.requestTermination().result } + } + } + + @Test + fun executeAfterOnWrongThread() { + val worker = Worker.start() + worker.requestTermination().result + + val exception = assertFailsWith { + worker.executeAfter(0L) {} + } + assertEquals("Worker is already terminated", exception.message) + } + + @Test + fun withName() = withWorker(name = "Lumberjack") { + execute(TransferMode.SAFE, {}) { + assertEquals("Lumberjack", Worker.current.name) + }.result + assertEquals("Lumberjack", name) + } + + @Test + fun nameOnWrongThread() { + val worker = Worker.start(name = "Lumberjack") + worker.requestTermination().result + val exception = assertFailsWith { + worker.name + } + assertEquals("Worker is already terminated", exception.message) + } + + @Test + fun park() = withWorker { + val counter = AtomicInt(0) + val f1 = execute(TransferMode.SAFE, { counter }) { counter -> + Worker.current.park(Long.MAX_VALUE / 1000L, process = true) + counter.incrementAndGet() + } + // wait a bit + Worker.current.park(10_000L) + // submit a task + val f2 = execute(TransferMode.SAFE, { counter }) { counter -> + counter.incrementAndGet() + } + f1.result + f2.result + assertEquals(2, counter.value) + } + + @Test + fun parkMain() = withWorker { + val main = Worker.current + val counter = AtomicInt(0) + executeAfter(1000) { + main.executeAfter(1) { + counter.incrementAndGet() + Unit + } + } + assertTrue(main.park(1_000_000_000L, process = true)) + assertEquals(1, counter.value) + } + + @Test + fun parkOnWrongThread() = withWorker { + val exception = assertFailsWith { + park(1L) + } + assertEquals("Worker is not current or already terminated", exception.message) + } + + @Test + fun parkZeroTimeout() { + Worker.current.park(0, process = true) + } + + @Test + fun processQueue() = withWorker { + val counter = AtomicInt(0) + val future1 = execute(TransferMode.SAFE, { counter }) { counter -> + assertEquals(0, counter.value) + // Process following request. + while (!Worker.current.processQueue()) { + } + // Ensure it has an effect. + assertEquals(1, counter.value) + // No more non-terminating tasks in this worker queue. + assertEquals(false, Worker.current.processQueue()) + } + val future2 = execute(TransferMode.SAFE, { counter }) { counter -> + counter.incrementAndGet() + } + future2.result + future1.result + } + + @Test + fun processQueueOnWrongThread() = withWorker { + val exception = assertFailsWith { + processQueue() + } + assertEquals("Worker is not current or already terminated", exception.message) + } + + @Test + fun requestTerminationOnWrongThread() { + val worker = Worker.start() + worker.requestTermination().result + + val exception = assertFailsWith { + worker.requestTermination() + } + assertEquals("Worker is already terminated", exception.message) + } + + @Test + fun activeWorkers() { + val workers = Array(10) { Worker.start() } + + val actualWorkers = Worker.activeWorkers.toSet() + + assertTrue(actualWorkers.size - workers.size == 1 || actualWorkers.size - workers.size == 2, + "actualWorkers.size = ${actualWorkers.size} workers.size = ${workers.size} actual size must be greater by 1 (main worker) or 2 (cleaners worker)") + workers.forEach { + actualWorkers.contains(it) + } + actualWorkers.contains(Worker.current) + + val terminatedWorkers = mutableSetOf() + (workers.indices step 2).forEach { + val worker = workers[it] + worker.requestTermination().result + terminatedWorkers.add(worker) + } + + val actualWorkersAfterTermination = Worker.activeWorkers.toSet() + assertEquals(terminatedWorkers, actualWorkers - actualWorkersAfterTermination) + + (workers.indices step 2).forEach { + workers[it + 1].requestTermination().result + } + } + + @Test + fun futureConsumedTwice(): Unit = withWorker { + val future = execute(TransferMode.SAFE, {}) { + 42 + } + assertEquals(42, future.result) + val exception = assertFailsWith { + future.result + } + assertEquals("Future is in an invalid state", exception.message) + } +} \ No newline at end of file diff --git a/kotlin-native/runtime/test/utils/LazyNativeTest.kt b/kotlin-native/runtime/test/utils/LazyNativeTest.kt new file mode 100644 index 00000000000..4f7be5f8406 --- /dev/null +++ b/kotlin-native/runtime/test/utils/LazyNativeTest.kt @@ -0,0 +1,112 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package test.utils + +import kotlin.concurrent.AtomicInt +import kotlin.native.concurrent.* +import kotlin.test.* + +abstract class AbstractExplicitModeLazyTest { + abstract val mode: LazyThreadSafetyMode + + @Test + fun finiteRecursion() { + var y = 20 + + class C { + val finiteRecursion: Int by lazy(mode) { + if (y < 17) 42 else { + y -= 1 + finiteRecursion + 1 + } + } + } + assertEquals(if (mode == LazyThreadSafetyMode.SYNCHRONIZED) 46 else 42, C().finiteRecursion) + } + + @Test + fun captureThis() { + class C { + val self by lazy(mode) { this } + } + + val self = C() + assertEquals(self, self.self) + } + + @Test + fun throwException() { + class C { + val thrower by lazy(mode) { + error("failure") + } + } + + val self = C() + repeat(10) { + assertFailsWith { + self.thrower + } + } + } + + @Test + fun multiThreadedInit() { + val initializerCallCount = AtomicInt(0) + + class C { + val data by lazy(mode) { + initializerCallCount.getAndIncrement() + Any() + } + } + + val self = C() + + val workers = Array(20) { Worker.start() } + val initialized = AtomicInt(0) + val canStart = AtomicInt(0) + val futures = workers.map { + it.execute(TransferMode.SAFE, { Triple(initialized, canStart, self) }) { (initialized, canStart, self) -> + initialized.incrementAndGet() + while (canStart.value != 1) { + } + self.data + } + } + + while (initialized.value < workers.size) { + } + canStart.value = 1 + + val results = mutableSetOf() + futures.forEach { + results += it.result + } + + if (mode == LazyThreadSafetyMode.SYNCHRONIZED) { + assertEquals(1, initializerCallCount.value) + } else { + assertTrue(initializerCallCount.value > 0) + assertTrue(initializerCallCount.value <= workers.size) + } + assertSame(self.data, results.single()) + + workers.forEach { + it.requestTermination().result + } + } +} + +class SynchronizedExplicitModeLazyTest : AbstractExplicitModeLazyTest() { + override val mode: LazyThreadSafetyMode + get() = LazyThreadSafetyMode.SYNCHRONIZED +} + +class PublicationExplicitModeLazyTest : AbstractExplicitModeLazyTest() { + override val mode: LazyThreadSafetyMode + get() = LazyThreadSafetyMode.PUBLICATION +} \ No newline at end of file diff --git a/libraries/stdlib/build.gradle.kts b/libraries/stdlib/build.gradle.kts index c030c96604c..b01ca6bee07 100644 --- a/libraries/stdlib/build.gradle.kts +++ b/libraries/stdlib/build.gradle.kts @@ -547,6 +547,7 @@ kotlin { optIn("kotlin.native.runtime.NativeRuntimeApi") optIn("kotlin.native.internal.InternalForKotlinNative") optIn("kotlinx.cinterop.ExperimentalForeignApi") + optIn("kotlin.native.concurrent.ObsoleteWorkersApi") } } } diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/test/blackbox/StdlibTest.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/test/blackbox/StdlibTest.kt index 15885c469d1..43902d1195b 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/test/blackbox/StdlibTest.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/test/blackbox/StdlibTest.kt @@ -27,7 +27,7 @@ import org.jetbrains.kotlin.konan.test.blackbox.support.group.PredefinedTestCase freeCompilerArgs = [ ENABLE_MPP, STDLIB_IS_A_FRIEND, ENABLE_X_STDLIB_API, ENABLE_X_ENCODING_API, ENABLE_RANGE_UNTIL, ENABLE_X_FOREIGN_API, ENABLE_X_NATIVE_API, ENABLE_OBSOLETE_NATIVE_API, ENABLE_NATIVE_RUNTIME_API, - ENABLE_INTERNAL_FOR_KOTLIN_NATIVE], + ENABLE_OBSOLETE_WORKERS_API, ENABLE_INTERNAL_FOR_KOTLIN_NATIVE], sourceLocations = [ "libraries/stdlib/test/**.kt", "libraries/stdlib/common/test/**.kt", @@ -53,7 +53,7 @@ class StdlibTest : AbstractNativeBlackBoxTest() { freeCompilerArgs = [ ENABLE_MPP, STDLIB_IS_A_FRIEND, ENABLE_X_STDLIB_API, ENABLE_X_ENCODING_API, ENABLE_RANGE_UNTIL, ENABLE_X_FOREIGN_API, ENABLE_X_NATIVE_API, ENABLE_OBSOLETE_NATIVE_API, ENABLE_NATIVE_RUNTIME_API, - ENABLE_INTERNAL_FOR_KOTLIN_NATIVE, + ENABLE_OBSOLETE_WORKERS_API, ENABLE_INTERNAL_FOR_KOTLIN_NATIVE, "-Xcommon-sources=libraries/stdlib/common/test/jsCollectionFactories.kt", "-Xcommon-sources=libraries/stdlib/common/test/testUtils.kt", "-Xcommon-sources=libraries/stdlib/test/testUtils.kt", @@ -84,6 +84,7 @@ private const val ENABLE_X_FOREIGN_API = "-opt-in=kotlinx.cinterop.ExperimentalF private const val ENABLE_X_NATIVE_API = "-opt-in=kotlin.experimental.ExperimentalNativeApi" private const val ENABLE_OBSOLETE_NATIVE_API = "-opt-in=kotlin.native.ObsoleteNativeApi" private const val ENABLE_NATIVE_RUNTIME_API = "-opt-in=kotlin.native.runtime.NativeRuntimeApi" +private const val ENABLE_OBSOLETE_WORKERS_API = "-opt-in=kotlin.native.concurrent.ObsoleteWorkersApi" private const val ENABLE_INTERNAL_FOR_KOTLIN_NATIVE = "-opt-in=kotlin.native.internal.InternalForKotlinNative" private const val ENABLE_RANGE_UNTIL = "-XXLanguage:+RangeUntilOperator" // keep until 1.8 private const val DISABLED_STDLIB_TEST = "test.collections.CollectionTest.abstractCollectionToArray" \ No newline at end of file