[K/N] Migrate some runtime/workers tests to new testing infra ^KT-61259
This commit is contained in:
committed by
Space Team
parent
fd1579186f
commit
9ad7b00613
@@ -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.
|
// This tests changes main thread worker queue state, so better be executed alone.
|
||||||
standaloneTest("worker5") {
|
standaloneTest("worker5") {
|
||||||
useGoldenData = true
|
useGoldenData = true
|
||||||
source = "runtime/workers/worker5.kt"
|
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) {
|
tasks.register("worker10", KonanLocalTest) {
|
||||||
enabled = !isNoopGC
|
enabled = !isNoopGC
|
||||||
useGoldenData = true
|
useGoldenData = true
|
||||||
source = "runtime/workers/worker10.kt"
|
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") {
|
standaloneTest("worker_exceptions") {
|
||||||
flags = ["-tr", "-Xworker-exception-handling=use-hook"]
|
flags = ["-tr", "-Xworker-exception-handling=use-hook"]
|
||||||
outputChecker = {
|
outputChecker = {
|
||||||
@@ -998,10 +943,6 @@ standaloneTest("worker_threadlocal_no_leak") {
|
|||||||
source = "runtime/workers/worker_threadlocal_no_leak.kt"
|
source = "runtime/workers/worker_threadlocal_no_leak.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register("worker_list_workers", KonanLocalTest) {
|
|
||||||
source = "runtime/workers/worker_list_workers.kt"
|
|
||||||
}
|
|
||||||
|
|
||||||
standaloneTest("freeze_disabled") {
|
standaloneTest("freeze_disabled") {
|
||||||
enabled = !isNoopGC
|
enabled = !isNoopGC
|
||||||
flags = ["-tr"]
|
flags = ["-tr"]
|
||||||
@@ -1009,16 +950,6 @@ standaloneTest("freeze_disabled") {
|
|||||||
testLogger = KonanTest.Logger.SILENT
|
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") {
|
standaloneTest("lazy2") {
|
||||||
useGoldenData = true
|
useGoldenData = true
|
||||||
source = "runtime/workers/lazy2.kt"
|
source = "runtime/workers/lazy2.kt"
|
||||||
@@ -1029,11 +960,6 @@ standaloneTest("lazy3") {
|
|||||||
source = "runtime/workers/lazy3.kt"
|
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) {
|
tasks.register("mutableData1", KonanLocalTest) {
|
||||||
source = "runtime/workers/mutableData1.kt"
|
source = "runtime/workers/mutableData1.kt"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<Worker>) {
|
|
||||||
val set = mutableSetOf<Any?>()
|
|
||||||
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<Worker>) {
|
|
||||||
// To make sure it is always frozen, and we don't race in relaxed mode.
|
|
||||||
Immutable3.freeze()
|
|
||||||
val set = mutableSetOf<Int>()
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
OK
|
|
||||||
@@ -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<IllegalStateException> {
|
|
||||||
println(Lazy(mode).recursion)
|
|
||||||
}
|
|
||||||
assertFailsWith<IllegalStateException> {
|
|
||||||
println(Lazy(mode).freeze().recursion)
|
|
||||||
}
|
|
||||||
y = 20
|
|
||||||
assertFailsWith<IllegalStateException> {
|
|
||||||
println(Lazy(mode).finiteRecursion)
|
|
||||||
}
|
|
||||||
y = 20
|
|
||||||
assertFailsWith<IllegalStateException> {
|
|
||||||
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<InvalidMutabilityException> {
|
|
||||||
println(Lazy(mode).freezer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test fun runTest4() {
|
|
||||||
for (mode in checkedLazyModes) {
|
|
||||||
val self = Lazy(mode)
|
|
||||||
repeat(10) {
|
|
||||||
assertFailsWith<IllegalArgumentException> {
|
|
||||||
println(self.thrower)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
OK
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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<AssertionError> { assertEquals(1, 2) }
|
|
||||||
input + " processed"
|
|
||||||
}
|
|
||||||
future.consume {
|
|
||||||
result -> println("Got $result")
|
|
||||||
}
|
|
||||||
worker.requestTermination().result
|
|
||||||
println("OK")
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
Got Input processed
|
|
||||||
OK
|
|
||||||
@@ -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")
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
OK
|
|
||||||
@@ -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<Job?>(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<IllegalStateException> {
|
|
||||||
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<IllegalStateException> { 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) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
OK
|
|
||||||
@@ -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")
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
OK
|
|
||||||
@@ -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<String>) {
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
OK
|
|
||||||
@@ -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<IllegalStateException> {
|
|
||||||
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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
Got 42
|
|
||||||
OK
|
|
||||||
@@ -8,33 +8,8 @@ import kotlin.test.*
|
|||||||
|
|
||||||
import kotlin.native.concurrent.*
|
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.
|
// Ensure that termination of current worker on main thread doesn't lead to problems.
|
||||||
fun runTest2() {
|
fun main() {
|
||||||
val worker = Worker.current
|
val worker = Worker.current
|
||||||
val future = worker.requestTermination(false)
|
val future = worker.requestTermination(false)
|
||||||
worker.processQueue()
|
worker.processQueue()
|
||||||
@@ -43,11 +18,5 @@ fun runTest2() {
|
|||||||
// After termination request this worker is no longer addressable.
|
// After termination request this worker is no longer addressable.
|
||||||
assertFailsWith<IllegalStateException> { worker.executeAfter(0, {
|
assertFailsWith<IllegalStateException> { worker.executeAfter(0, {
|
||||||
println("BUG!")
|
println("BUG!")
|
||||||
}.freeze()) }
|
})}
|
||||||
}
|
|
||||||
|
|
||||||
fun main() {
|
|
||||||
runTest0()
|
|
||||||
runTest1()
|
|
||||||
runTest2()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
Got 3
|
|
||||||
OK
|
|
||||||
|
|||||||
@@ -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<IncorrectDereferenceException> {
|
|
||||||
int1++
|
|
||||||
}
|
|
||||||
assertEquals(2, int1)
|
|
||||||
}
|
|
||||||
assertEquals(77, int2)
|
|
||||||
}.freeze())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
Got 42
|
|
||||||
OK
|
|
||||||
@@ -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<IllegalStateException> {
|
|
||||||
println(worker.execute(TransferMode.SAFE, { null }, { _ -> throw Error("An error") }).result)
|
|
||||||
}
|
|
||||||
|
|
||||||
worker.requestTermination().result
|
|
||||||
|
|
||||||
println("OK")
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
Input
|
|
||||||
Got kotlin.Unit
|
|
||||||
OK
|
|
||||||
@@ -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<SharedData>(inputDetached).attach()
|
|
||||||
println(input)
|
|
||||||
}
|
|
||||||
future.consume {
|
|
||||||
result -> println("Got $result")
|
|
||||||
}
|
|
||||||
worker.requestTermination().result
|
|
||||||
println("OK")
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
SharedData(string=Hello, int=10, member=SharedDataMember(double=0.1))
|
|
||||||
Got kotlin.Unit
|
|
||||||
OK
|
|
||||||
@@ -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<IllegalStateException> {
|
|
||||||
worker.executeAfter {
|
|
||||||
println("shall not happen")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assertFailsWith<IllegalArgumentException> {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
zzz
|
|
||||||
42
|
|
||||||
OK
|
|
||||||
first 2
|
|
||||||
second 3
|
|
||||||
unfrozen OK
|
|
||||||
frozen OK
|
|
||||||
@@ -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<IllegalStateException> {
|
|
||||||
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<IllegalStateException> {
|
|
||||||
DetachedObjectGraph { obj }
|
|
||||||
}
|
|
||||||
assertEquals("Unable to transfer object: it is still owned elsewhere", exception.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun checkProcessQueueOnWrongThread(): Unit = withWorker {
|
|
||||||
val exception = assertFailsWith<IllegalStateException> {
|
|
||||||
processQueue()
|
|
||||||
}
|
|
||||||
assertEquals("Worker is not current or already terminated", exception.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun checkParkOnWrongThread(): Unit = withWorker {
|
|
||||||
val exception = assertFailsWith<IllegalStateException> {
|
|
||||||
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<IllegalStateException> {
|
|
||||||
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<IllegalStateException> {
|
|
||||||
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<IllegalStateException> {
|
|
||||||
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<IllegalStateException> {
|
|
||||||
worker.executeAfter(0L, {}.freeze())
|
|
||||||
}
|
|
||||||
assertEquals("Worker is already terminated", exception.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun checkTerminatedWorkerRequestTermination() {
|
|
||||||
val worker = Worker.start()
|
|
||||||
worker.requestTermination().result
|
|
||||||
|
|
||||||
val exception = assertFailsWith<IllegalStateException> {
|
|
||||||
worker.requestTermination()
|
|
||||||
}
|
|
||||||
assertEquals("Worker is already terminated", exception.message)
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<IllegalStateException> { 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<SharedData>(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<IllegalStateException> {
|
||||||
|
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<IllegalArgumentException> { 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<IllegalStateException> { 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<IllegalStateException> {
|
||||||
|
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<IllegalStateException> {
|
||||||
|
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<IllegalStateException> {
|
||||||
|
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<IllegalStateException> {
|
||||||
|
processQueue()
|
||||||
|
}
|
||||||
|
assertEquals("Worker is not current or already terminated", exception.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun requestTerminationOnWrongThread() {
|
||||||
|
val worker = Worker.start()
|
||||||
|
worker.requestTermination().result
|
||||||
|
|
||||||
|
val exception = assertFailsWith<IllegalStateException> {
|
||||||
|
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<Worker>()
|
||||||
|
(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<IllegalStateException> {
|
||||||
|
future.result
|
||||||
|
}
|
||||||
|
assertEquals("Future is in an invalid state", exception.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String>(mode) {
|
||||||
|
error("failure")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val self = C()
|
||||||
|
repeat(10) {
|
||||||
|
assertFailsWith<IllegalStateException> {
|
||||||
|
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<Any>()
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -547,6 +547,7 @@ kotlin {
|
|||||||
optIn("kotlin.native.runtime.NativeRuntimeApi")
|
optIn("kotlin.native.runtime.NativeRuntimeApi")
|
||||||
optIn("kotlin.native.internal.InternalForKotlinNative")
|
optIn("kotlin.native.internal.InternalForKotlinNative")
|
||||||
optIn("kotlinx.cinterop.ExperimentalForeignApi")
|
optIn("kotlinx.cinterop.ExperimentalForeignApi")
|
||||||
|
optIn("kotlin.native.concurrent.ObsoleteWorkersApi")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import org.jetbrains.kotlin.konan.test.blackbox.support.group.PredefinedTestCase
|
|||||||
freeCompilerArgs = [
|
freeCompilerArgs = [
|
||||||
ENABLE_MPP, STDLIB_IS_A_FRIEND, ENABLE_X_STDLIB_API, ENABLE_X_ENCODING_API, ENABLE_RANGE_UNTIL,
|
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_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 = [
|
sourceLocations = [
|
||||||
"libraries/stdlib/test/**.kt",
|
"libraries/stdlib/test/**.kt",
|
||||||
"libraries/stdlib/common/test/**.kt",
|
"libraries/stdlib/common/test/**.kt",
|
||||||
@@ -53,7 +53,7 @@ class StdlibTest : AbstractNativeBlackBoxTest() {
|
|||||||
freeCompilerArgs = [
|
freeCompilerArgs = [
|
||||||
ENABLE_MPP, STDLIB_IS_A_FRIEND, ENABLE_X_STDLIB_API, ENABLE_X_ENCODING_API, ENABLE_RANGE_UNTIL,
|
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_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/jsCollectionFactories.kt",
|
||||||
"-Xcommon-sources=libraries/stdlib/common/test/testUtils.kt",
|
"-Xcommon-sources=libraries/stdlib/common/test/testUtils.kt",
|
||||||
"-Xcommon-sources=libraries/stdlib/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_X_NATIVE_API = "-opt-in=kotlin.experimental.ExperimentalNativeApi"
|
||||||
private const val ENABLE_OBSOLETE_NATIVE_API = "-opt-in=kotlin.native.ObsoleteNativeApi"
|
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_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_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 ENABLE_RANGE_UNTIL = "-XXLanguage:+RangeUntilOperator" // keep until 1.8
|
||||||
private const val DISABLED_STDLIB_TEST = "test.collections.CollectionTest.abstractCollectionToArray"
|
private const val DISABLED_STDLIB_TEST = "test.collections.CollectionTest.abstractCollectionToArray"
|
||||||
Reference in New Issue
Block a user