[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.
|
||||
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"
|
||||
}
|
||||
|
||||
@@ -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.*
|
||||
|
||||
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<IllegalStateException> { worker.executeAfter(0, {
|
||||
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.internal.InternalForKotlinNative")
|
||||
optIn("kotlinx.cinterop.ExperimentalForeignApi")
|
||||
optIn("kotlin.native.concurrent.ObsoleteWorkersApi")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user