Concurrent API update. (#1949)
This commit is contained in:
+8
-9
@@ -20,11 +20,11 @@
|
|||||||
each mutable object is owned by the single worker, but ownership could be transferred.
|
each mutable object is owned by the single worker, but ownership could be transferred.
|
||||||
See section [Object transfer and freezing](#transfer).
|
See section [Object transfer and freezing](#transfer).
|
||||||
|
|
||||||
Once worker is started with `startWorker` function call, it can be uniquely addressed with an integer
|
Once worker is started with `Worker.start` function call, it can be uniquely addressed with an integer
|
||||||
worker id. Other workers, or non-worker concurrency primitives, such as OS threads, could send a message
|
worker id. Other workers, or non-worker concurrency primitives, such as OS threads, could send a message
|
||||||
to the worker with `schedule` call.
|
to the worker with `execute` call.
|
||||||
```kotlin
|
```kotlin
|
||||||
val future = schedule(TransferMode.CHECKED, { SomeDataForWorker() }) {
|
val future = execute(TransferMode.SAFE, { SomeDataForWorker() }) {
|
||||||
// data returned by the second function argument comes to the
|
// data returned by the second function argument comes to the
|
||||||
// worker routine as 'input' parameter.
|
// worker routine as 'input' parameter.
|
||||||
input ->
|
input ->
|
||||||
@@ -36,19 +36,18 @@
|
|||||||
// Here we see result returned from routine above. Note that future object or
|
// Here we see result returned from routine above. Note that future object or
|
||||||
// id could be transferred to another worker, so we don't have to consume future
|
// id could be transferred to another worker, so we don't have to consume future
|
||||||
// in same execution context it was obtained.
|
// in same execution context it was obtained.
|
||||||
result ->
|
result -> println("result is $result")
|
||||||
println("result is $result")
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
The call to `schedule` uses function passed as its second parameter to produce an object subgraph
|
The call to `execute` uses function passed as its second parameter to produce an object subgraph
|
||||||
(i.e. set of mutually referring objects) which is passed as the whole to that worker, and no longer
|
(i.e. set of mutually referring objects) which is passed as the whole to that worker, and no longer
|
||||||
available to the thread that initiated the request. This property is checked if the first parameter
|
available to the thread that initiated the request. This property is checked if the first parameter
|
||||||
is `TransferMode.CHECKED` by graph traversal and just assumed to be true, if it is `TransferMode.UNCHECKED`.
|
is `TransferMode.SAFE` by graph traversal and just assumed to be true, if it is `TransferMode.UNCHECKED`.
|
||||||
Last parameter to schedule is a special Kotlin lambda, which is not allowed to capture any state,
|
Last parameter to `execute` is a special Kotlin lambda, which is not allowed to capture any state,
|
||||||
and is actually invoked in target worker's context. Once processed, result is transferred to whoever consumes
|
and is actually invoked in target worker's context. Once processed, result is transferred to whoever consumes
|
||||||
the future, and is attached to object graph of that worker/thread.
|
the future, and is attached to object graph of that worker/thread.
|
||||||
|
|
||||||
If an object is transferred in `UNCHECKED` mode and is still accessible from multiple concurrent executors,
|
If an object is transferred in `UNSAFE` mode and is still accessible from multiple concurrent executors,
|
||||||
program will likely crash unexpectedly, so consider that last resort in optimizing, not a general purpose
|
program will likely crash unexpectedly, so consider that last resort in optimizing, not a general purpose
|
||||||
mechanism.
|
mechanism.
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -82,10 +82,10 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns, vararg konanPrimitives:
|
|||||||
|
|
||||||
val concurrentPackageScope = builtIns.builtInsModule.getPackage(FqName("kotlin.native.concurrent")).memberScope
|
val concurrentPackageScope = builtIns.builtInsModule.getPackage(FqName("kotlin.native.concurrent")).memberScope
|
||||||
|
|
||||||
val scheduleFunction = concurrentPackageScope.getContributedClass("Worker")
|
val executeFunction = concurrentPackageScope.getContributedClass("Worker")
|
||||||
.unsubstitutedMemberScope.getContributedFunctions("schedule").single()
|
.unsubstitutedMemberScope.getContributedFunctions("execute").single()
|
||||||
|
|
||||||
val scheduleImplFunction = concurrentPackageScope.getContributedFunctions("scheduleImpl").single()
|
val executeImplFunction = concurrentPackageScope.getContributedFunctions("executeImpl").single()
|
||||||
|
|
||||||
val signExtend = packageScope.getContributedFunctions("signExtend").single()
|
val signExtend = packageScope.getContributedFunctions("signExtend").single()
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -234,7 +234,7 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val
|
|||||||
) as ClassDescriptor
|
) as ClassDescriptor
|
||||||
)
|
)
|
||||||
|
|
||||||
val scheduleImpl = symbolTable.referenceSimpleFunction(context.interopBuiltIns.scheduleImplFunction)
|
val executeImpl = symbolTable.referenceSimpleFunction(context.interopBuiltIns.executeImplFunction)
|
||||||
|
|
||||||
val areEqualByValue = context.getInternalFunctions("areEqualByValue").map {
|
val areEqualByValue = context.getInternalFunctions("areEqualByValue").map {
|
||||||
symbolTable.referenceSimpleFunction(it)
|
symbolTable.referenceSimpleFunction(it)
|
||||||
|
|||||||
+1
-2
@@ -27,7 +27,6 @@ import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
|
|||||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||||
import org.jetbrains.kotlin.ir.types.IrType
|
import org.jetbrains.kotlin.ir.types.IrType
|
||||||
import org.jetbrains.kotlin.ir.types.isNullableAny
|
import org.jetbrains.kotlin.ir.types.isNullableAny
|
||||||
import org.jetbrains.kotlin.ir.types.isUnit
|
|
||||||
import org.jetbrains.kotlin.ir.util.simpleFunctions
|
import org.jetbrains.kotlin.ir.util.simpleFunctions
|
||||||
import org.jetbrains.kotlin.ir.util.transformFlat
|
import org.jetbrains.kotlin.ir.util.transformFlat
|
||||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||||
@@ -59,7 +58,7 @@ internal class WorkersBridgesBuilding(val context: Context) : DeclarationContain
|
|||||||
expression.transformChildrenVoid(this)
|
expression.transformChildrenVoid(this)
|
||||||
|
|
||||||
val descriptor = expression.descriptor.original
|
val descriptor = expression.descriptor.original
|
||||||
if (descriptor != interop.scheduleImplFunction)
|
if (descriptor != interop.executeImplFunction)
|
||||||
return expression
|
return expression
|
||||||
|
|
||||||
val job = expression.getValueArgument(3) as IrFunctionReference
|
val job = expression.getValueArgument(3) as IrFunctionReference
|
||||||
|
|||||||
+3
-3
@@ -810,7 +810,7 @@ private class InteropTransformer(val context: Context, val irFile: IrFile) : IrB
|
|||||||
typeArgumentsCount = 0)
|
typeArgumentsCount = 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
interop.scheduleFunction -> {
|
interop.executeFunction -> {
|
||||||
val irCallableReference = unwrapStaticFunctionArgument(expression.getValueArgument(2)!!)
|
val irCallableReference = unwrapStaticFunctionArgument(expression.getValueArgument(2)!!)
|
||||||
|
|
||||||
if (irCallableReference == null || irCallableReference.getArguments().isNotEmpty()) {
|
if (irCallableReference == null || irCallableReference.getArguments().isNotEmpty()) {
|
||||||
@@ -824,11 +824,11 @@ private class InteropTransformer(val context: Context, val irFile: IrFile) : IrB
|
|||||||
val target = targetSymbol.descriptor
|
val target = targetSymbol.descriptor
|
||||||
val jobPointer = IrFunctionReferenceImpl(
|
val jobPointer = IrFunctionReferenceImpl(
|
||||||
builder.startOffset, builder.endOffset,
|
builder.startOffset, builder.endOffset,
|
||||||
symbols.scheduleImpl.owner.valueParameters[3].type,
|
symbols.executeImpl.owner.valueParameters[3].type,
|
||||||
targetSymbol, target,
|
targetSymbol, target,
|
||||||
typeArgumentsCount = 0)
|
typeArgumentsCount = 0)
|
||||||
|
|
||||||
builder.irCall(symbols.scheduleImpl).apply {
|
builder.irCall(symbols.executeImpl).apply {
|
||||||
putValueArgument(0, expression.dispatchReceiver)
|
putValueArgument(0, expression.dispatchReceiver)
|
||||||
putValueArgument(1, expression.getValueArgument(0))
|
putValueArgument(1, expression.getValueArgument(0))
|
||||||
putValueArgument(2, expression.getValueArgument(1))
|
putValueArgument(2, expression.getValueArgument(1))
|
||||||
|
|||||||
+7
-11
@@ -324,11 +324,11 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
|
|||||||
expressions += expression
|
expressions += expression
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expression is IrCall && expression.symbol == scheduleImplSymbol) {
|
if (expression is IrCall && expression.symbol == executeImplSymbol) {
|
||||||
// Producer and job of scheduleImpl are called externally, we need to reflect this somehow.
|
// Producer and job of executeImpl are called externally, we need to reflect this somehow.
|
||||||
val producerInvocation = IrCallImpl(expression.startOffset, expression.endOffset,
|
val producerInvocation = IrCallImpl(expression.startOffset, expression.endOffset,
|
||||||
scheduleImplProducerInvoke.returnType,
|
executeImplProducerInvoke.returnType,
|
||||||
scheduleImplProducerInvoke.symbol, scheduleImplProducerInvoke.descriptor)
|
executeImplProducerInvoke.symbol, executeImplProducerInvoke.descriptor)
|
||||||
producerInvocation.dispatchReceiver = expression.getValueArgument(2)
|
producerInvocation.dispatchReceiver = expression.getValueArgument(2)
|
||||||
val jobFunctionReference = expression.getValueArgument(3) as? IrFunctionReference
|
val jobFunctionReference = expression.getValueArgument(3) as? IrFunctionReference
|
||||||
?: error("A function reference expected")
|
?: error("A function reference expected")
|
||||||
@@ -403,13 +403,9 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
|
|||||||
private val arraySetSymbol = context.ir.symbols.arraySet
|
private val arraySetSymbol = context.ir.symbols.arraySet
|
||||||
private val createUninitializedInstanceSymbol = context.ir.symbols.createUninitializedInstance
|
private val createUninitializedInstanceSymbol = context.ir.symbols.createUninitializedInstance
|
||||||
private val initInstanceSymbol = context.ir.symbols.initInstance
|
private val initInstanceSymbol = context.ir.symbols.initInstance
|
||||||
private val scheduleImplSymbol = context.ir.symbols.scheduleImpl
|
private val executeImplSymbol = context.ir.symbols.executeImpl
|
||||||
private val scheduleImplProducerClassSymbol = context.ir.symbols.functions[0]
|
private val executeImplProducerClassSymbol = context.ir.symbols.functions[0]
|
||||||
private val scheduleImplProducerParam = scheduleImplSymbol.descriptor.valueParameters[2].also {
|
private val executeImplProducerInvoke = executeImplProducerClassSymbol.owner.simpleFunctions()
|
||||||
assert(it.name.asString() == "producer")
|
|
||||||
assert(it.type.constructor.declarationDescriptor == scheduleImplProducerClassSymbol.descriptor)
|
|
||||||
}
|
|
||||||
private val scheduleImplProducerInvoke = scheduleImplProducerClassSymbol.owner.simpleFunctions()
|
|
||||||
.single { it.name == OperatorNameConventions.INVOKE }
|
.single { it.name == OperatorNameConventions.INVOKE }
|
||||||
|
|
||||||
private inner class FunctionDFGBuilder(val expressionValuesExtractor: ExpressionValuesExtractor,
|
private inner class FunctionDFGBuilder(val expressionValuesExtractor: ExpressionValuesExtractor,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import kotlin.test.*
|
|||||||
@Test
|
@Test
|
||||||
fun testRandomWorkers() {
|
fun testRandomWorkers() {
|
||||||
val seed = getTimeMillis()
|
val seed = getTimeMillis()
|
||||||
val workers = Array(5, { _ -> startWorker()})
|
val workers = Array(5, { _ -> Worker.start() })
|
||||||
|
|
||||||
val attempts = 3
|
val attempts = 3
|
||||||
val results = Array(attempts, { ArrayList<Int>() } )
|
val results = Array(attempts, { ArrayList<Int>() } )
|
||||||
@@ -22,7 +22,8 @@ fun testRandomWorkers() {
|
|||||||
Random.seed = seed
|
Random.seed = seed
|
||||||
// Produce a list of random numbers in each worker
|
// Produce a list of random numbers in each worker
|
||||||
val futures = Array(workers.size, { workerIndex ->
|
val futures = Array(workers.size, { workerIndex ->
|
||||||
workers[workerIndex].schedule(TransferMode.CHECKED, { workerIndex }) { input ->
|
workers[workerIndex].execute(TransferMode.SAFE, { workerIndex }) {
|
||||||
|
input ->
|
||||||
Array(10, { Random.nextInt() }).toList()
|
Array(10, { Random.nextInt() }).toList()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -30,11 +31,11 @@ fun testRandomWorkers() {
|
|||||||
val futureSet = futures.toSet()
|
val futureSet = futures.toSet()
|
||||||
for (i in 0 until futureSet.size) {
|
for (i in 0 until futureSet.size) {
|
||||||
val ready = futureSet.waitForMultipleFutures(10000)
|
val ready = futureSet.waitForMultipleFutures(10000)
|
||||||
ready.forEach { results[attempt].addAll(it.result()) }
|
ready.forEach { results[attempt].addAll(it.result) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
workers.forEach {
|
workers.forEach {
|
||||||
it.requestTermination().consume { _ -> }
|
it.requestTermination().result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,54 +12,54 @@ import kotlin.native.concurrent.*
|
|||||||
fun test1(workers: Array<Worker>) {
|
fun test1(workers: Array<Worker>) {
|
||||||
val atomic = AtomicInt(15)
|
val atomic = AtomicInt(15)
|
||||||
val futures = Array(workers.size, { workerIndex ->
|
val futures = Array(workers.size, { workerIndex ->
|
||||||
workers[workerIndex].schedule(TransferMode.CHECKED, { atomic }) {
|
workers[workerIndex].execute(TransferMode.SAFE, { atomic }) {
|
||||||
input -> input.increment()
|
input -> input.addAndGet(1)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
futures.forEach {
|
futures.forEach {
|
||||||
it.result()
|
it.result
|
||||||
}
|
}
|
||||||
println(atomic.get())
|
println(atomic.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun test2(workers: Array<Worker>) {
|
fun test2(workers: Array<Worker>) {
|
||||||
val atomic = AtomicInt(1)
|
val atomic = AtomicInt(1)
|
||||||
val counter = AtomicInt(0)
|
val counter = AtomicInt(0)
|
||||||
val futures = Array(workers.size, { workerIndex ->
|
val futures = Array(workers.size, { workerIndex ->
|
||||||
workers[workerIndex].schedule(TransferMode.CHECKED, { Triple(atomic, workerIndex, counter) }) {
|
workers[workerIndex].execute(TransferMode.SAFE, { Triple(atomic, workerIndex, counter) }) {
|
||||||
(place, index, result) ->
|
(place, index, result) ->
|
||||||
// Here we simulate mutex using [place] location to store tag of the current worker.
|
// Here we simulate mutex using [place] location to store tag of the current worker.
|
||||||
// When it is negative - worker executes exclusively.
|
// When it is negative - worker executes exclusively.
|
||||||
val tag = index + 1
|
val tag = index + 1
|
||||||
while (place.compareAndSwap(tag, -tag) != tag) {}
|
while (place.compareAndSwap(tag, -tag) != tag) {}
|
||||||
val ok1 = result.increment() == index + 1
|
val ok1 = result.addAndGet(1) == index + 1
|
||||||
// Now, let the next worker run.
|
// Now, let the next worker run.
|
||||||
val ok2 = place.compareAndSwap(-tag, tag + 1) == -tag
|
val ok2 = place.compareAndSwap(-tag, tag + 1) == -tag
|
||||||
ok1 && ok2
|
ok1 && ok2
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
futures.forEach {
|
futures.forEach {
|
||||||
assertEquals(it.result(), true)
|
assertEquals(it.result, true)
|
||||||
}
|
}
|
||||||
println(counter.get())
|
println(counter.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Data(val value: Int)
|
data class Data(val value: Int)
|
||||||
|
|
||||||
fun test3(workers: Array<Worker>) {
|
fun test3(workers: Array<Worker>) {
|
||||||
val common = AtomicReference<Data>()
|
val common = AtomicReference<Data?>(null)
|
||||||
val futures = Array(workers.size, { workerIndex ->
|
val futures = Array(workers.size, { workerIndex ->
|
||||||
workers[workerIndex].schedule(TransferMode.CHECKED, { Pair(common, workerIndex) }) {
|
workers[workerIndex].execute(TransferMode.SAFE, { Pair(common, workerIndex) }) {
|
||||||
(place, index) ->
|
(place, index) ->
|
||||||
val mine = Data(index).freeze()
|
val mine = Data(index).freeze()
|
||||||
// Try to publish our own data, until successful, in a tight loop.
|
// Try to publish our own data, until successful, in a tight loop.
|
||||||
while (place.compareAndSwap(null, mine) != null) {}
|
while (!place.compareAndSet(null, mine)) {}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
val seen = mutableSetOf<Data>()
|
val seen = mutableSetOf<Data>()
|
||||||
for (i in 0 until workers.size) {
|
for (i in 0 until workers.size) {
|
||||||
do {
|
do {
|
||||||
val current = common.get()
|
val current = common.value
|
||||||
if (current != null && !seen.contains(current)) {
|
if (current != null && !seen.contains(current)) {
|
||||||
seen += current
|
seen += current
|
||||||
// Let others publish.
|
// Let others publish.
|
||||||
@@ -69,7 +69,7 @@ fun test3(workers: Array<Worker>) {
|
|||||||
} while (true)
|
} while (true)
|
||||||
}
|
}
|
||||||
futures.forEach {
|
futures.forEach {
|
||||||
it.result()
|
it.result
|
||||||
}
|
}
|
||||||
assertEquals(seen.size, workers.size)
|
assertEquals(seen.size, workers.size)
|
||||||
}
|
}
|
||||||
@@ -79,33 +79,33 @@ fun test4() {
|
|||||||
AtomicReference(Data(1))
|
AtomicReference(Data(1))
|
||||||
}
|
}
|
||||||
assertFailsWith<InvalidMutabilityException> {
|
assertFailsWith<InvalidMutabilityException> {
|
||||||
AtomicReference<Data>().compareAndSwap(null, Data(2))
|
AtomicReference<Data?>(null).compareAndSwap(null, Data(2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun test5() {
|
fun test5() {
|
||||||
assertFailsWith<InvalidMutabilityException> {
|
assertFailsWith<InvalidMutabilityException> {
|
||||||
AtomicReference<Data>().set(Data(2))
|
AtomicReference<Data?>(null).value = Data(2)
|
||||||
}
|
}
|
||||||
val ref = AtomicReference<Data>()
|
val ref = AtomicReference<Data?>(null)
|
||||||
val value = Data(3).freeze()
|
val value = Data(3).freeze()
|
||||||
assertEquals(null, ref.get())
|
assertEquals(null, ref.value)
|
||||||
ref.set(value)
|
ref.value = value
|
||||||
assertEquals(3, ref.get()!!.value)
|
assertEquals(3, ref.value!!.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun test6() {
|
fun test6() {
|
||||||
val int = AtomicInt()
|
val int = AtomicInt(0)
|
||||||
int.set(239)
|
int.value = 239
|
||||||
assertEquals(239, int.get())
|
assertEquals(239, int.value)
|
||||||
val long = AtomicLong()
|
val long = AtomicLong(0)
|
||||||
long.set(239L)
|
long.value = 239L
|
||||||
assertEquals(239L, long.get())
|
assertEquals(239L, long.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun runTest() {
|
@Test fun runTest() {
|
||||||
val COUNT = 20
|
val COUNT = 20
|
||||||
val workers = Array(COUNT, { _ -> startWorker()})
|
val workers = Array(COUNT, { _ -> Worker.start()})
|
||||||
|
|
||||||
test1(workers)
|
test1(workers)
|
||||||
test2(workers)
|
test2(workers)
|
||||||
@@ -115,7 +115,7 @@ fun test6() {
|
|||||||
test6()
|
test6()
|
||||||
|
|
||||||
workers.forEach {
|
workers.forEach {
|
||||||
it.requestTermination().consume { _ -> }
|
it.requestTermination().result
|
||||||
}
|
}
|
||||||
println("OK")
|
println("OK")
|
||||||
}
|
}
|
||||||
@@ -17,8 +17,8 @@ data class Foo(val kind: A)
|
|||||||
// Enums are shared between threads so identity should be kept.
|
// Enums are shared between threads so identity should be kept.
|
||||||
@Test
|
@Test
|
||||||
fun runTest() {
|
fun runTest() {
|
||||||
val result = startWorker().schedule(TransferMode.CHECKED, { Foo(A.B) }, { input ->
|
val result = Worker.start().execute(TransferMode.SAFE, { Foo(A.B) }, { input ->
|
||||||
input.kind == A.B
|
input.kind == A.B
|
||||||
}).result()
|
}).result
|
||||||
println(result)
|
println(result)
|
||||||
}
|
}
|
||||||
@@ -14,12 +14,12 @@ data class SharedDataMember(val double: Double)
|
|||||||
data class SharedData(val string: String, val int: Int, val member: SharedDataMember)
|
data class SharedData(val string: String, val int: Int, val member: SharedDataMember)
|
||||||
|
|
||||||
@Test fun runTest() {
|
@Test fun runTest() {
|
||||||
val worker = startWorker()
|
val worker = Worker.start()
|
||||||
// Create immutable shared data.
|
// Create immutable shared data.
|
||||||
val immutable = SharedData("Hello", 10, SharedDataMember(0.1)).freeze()
|
val immutable = SharedData("Hello", 10, SharedDataMember(0.1)).freeze()
|
||||||
println("frozen bit is ${immutable.isFrozen}")
|
println("frozen bit is ${immutable.isFrozen}")
|
||||||
|
|
||||||
val future = worker.schedule(TransferMode.CHECKED, { immutable } ) {
|
val future = worker.execute(TransferMode.SAFE, { immutable } ) {
|
||||||
input ->
|
input ->
|
||||||
println("Worker: $input")
|
println("Worker: $input")
|
||||||
input
|
input
|
||||||
@@ -27,6 +27,6 @@ data class SharedData(val string: String, val int: Int, val member: SharedDataMe
|
|||||||
future.consume {
|
future.consume {
|
||||||
result -> println("Main: $result")
|
result -> println("Main: $result")
|
||||||
}
|
}
|
||||||
worker.requestTermination().result()
|
worker.requestTermination().result
|
||||||
println("OK")
|
println("OK")
|
||||||
}
|
}
|
||||||
@@ -59,32 +59,32 @@ data class Data(var int: Int)
|
|||||||
assertFailsWith<InvalidMutabilityException> { a8[1] = 2.0 }
|
assertFailsWith<InvalidMutabilityException> { a8[1] = 2.0 }
|
||||||
|
|
||||||
// Ensure that String and integral boxes are frozen by default, by passing local to the worker.
|
// Ensure that String and integral boxes are frozen by default, by passing local to the worker.
|
||||||
val worker = startWorker()
|
val worker = Worker.start()
|
||||||
var data: Any = "Hello" + " " + "world"
|
var data: Any = "Hello" + " " + "world"
|
||||||
assert(data.isFrozen)
|
assert(data.isFrozen)
|
||||||
worker.schedule(TransferMode.CHECKED, { data } ) {
|
worker.execute(TransferMode.SAFE, { data } ) {
|
||||||
input -> println("Worker 1: $input")
|
input -> println("Worker 1: $input")
|
||||||
}.result()
|
}.result
|
||||||
|
|
||||||
data = 42
|
data = 42
|
||||||
assert(data.isFrozen)
|
assert(data.isFrozen)
|
||||||
worker.schedule(TransferMode.CHECKED, { data } ) {
|
worker.execute(TransferMode.SAFE, { data } ) {
|
||||||
input -> println("Worker2: $input")
|
input -> println("Worker2: $input")
|
||||||
}.result()
|
}.result
|
||||||
|
|
||||||
data = 239.0
|
data = 239.0
|
||||||
assert(data.isFrozen)
|
assert(data.isFrozen)
|
||||||
worker.schedule(TransferMode.CHECKED, { data } ) {
|
worker.execute(TransferMode.SAFE, { data } ) {
|
||||||
input -> println("Worker3: $input")
|
input -> println("Worker3: $input")
|
||||||
}.result()
|
}.result
|
||||||
|
|
||||||
data = 'a'
|
data = 'a'
|
||||||
assert(data.isFrozen)
|
assert(data.isFrozen)
|
||||||
worker.schedule(TransferMode.CHECKED, { data } ) {
|
worker.execute(TransferMode.SAFE, { data } ) {
|
||||||
input -> println("Worker4: $input")
|
input -> println("Worker4: $input")
|
||||||
}.result()
|
}.result
|
||||||
|
|
||||||
worker.requestTermination().result()
|
worker.requestTermination().result
|
||||||
|
|
||||||
println("OK")
|
println("OK")
|
||||||
}
|
}
|
||||||
@@ -36,9 +36,9 @@ fun testSingleData(workers: Array<Worker>) {
|
|||||||
val set = mutableSetOf<Any?>()
|
val set = mutableSetOf<Any?>()
|
||||||
for (attempt in 1 .. 3) {
|
for (attempt in 1 .. 3) {
|
||||||
val futures = Array(workers.size, { workerIndex ->
|
val futures = Array(workers.size, { workerIndex ->
|
||||||
workers[workerIndex].schedule(TransferMode.CHECKED, { "" }) { _ -> Immutable2.y }
|
workers[workerIndex].execute(TransferMode.SAFE, { "" }) { _ -> Immutable2.y }
|
||||||
})
|
})
|
||||||
futures.forEach { set += it.result() }
|
futures.forEach { set += it.result }
|
||||||
}
|
}
|
||||||
assertEquals(set.size, 1)
|
assertEquals(set.size, 1)
|
||||||
assertEquals(set.single(), Immutable2.y)
|
assertEquals(set.single(), Immutable2.y)
|
||||||
@@ -48,9 +48,9 @@ fun testFrozenLazy(workers: Array<Worker>) {
|
|||||||
val set = mutableSetOf<Int>()
|
val set = mutableSetOf<Int>()
|
||||||
for (attempt in 1 .. 3) {
|
for (attempt in 1 .. 3) {
|
||||||
val futures = Array(workers.size, { workerIndex ->
|
val futures = Array(workers.size, { workerIndex ->
|
||||||
workers[workerIndex].schedule(TransferMode.CHECKED, { "" }) { _ -> Immutable3.x }
|
workers[workerIndex].execute(TransferMode.SAFE, { "" }) { _ -> Immutable3.x }
|
||||||
})
|
})
|
||||||
futures.forEach { set += it.result() }
|
futures.forEach { set += it.result }
|
||||||
}
|
}
|
||||||
assertEquals(1, set.size)
|
assertEquals(1, set.size)
|
||||||
assertEquals(Immutable3.x, set.single())
|
assertEquals(Immutable3.x, set.single())
|
||||||
@@ -77,7 +77,7 @@ fun testLiquidLazy() {
|
|||||||
assertEquals(42, Immutable.x)
|
assertEquals(42, Immutable.x)
|
||||||
|
|
||||||
val COUNT = 5
|
val COUNT = 5
|
||||||
val workers = Array(COUNT, { _ -> startWorker()})
|
val workers = Array(COUNT, { _ -> Worker.start()})
|
||||||
testSingleData(workers)
|
testSingleData(workers)
|
||||||
testFrozenLazy(workers)
|
testFrozenLazy(workers)
|
||||||
testLiquidLazy()
|
testLiquidLazy()
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ import kotlin.test.*
|
|||||||
import kotlin.native.concurrent.*
|
import kotlin.native.concurrent.*
|
||||||
|
|
||||||
@Test fun runTest() {
|
@Test fun runTest() {
|
||||||
val worker = startWorker()
|
val worker = Worker.start()
|
||||||
val future = worker.schedule(TransferMode.CHECKED, { "Input" }) {
|
val future = worker.execute(TransferMode.SAFE, { "Input" }) {
|
||||||
input -> input + " processed"
|
input -> input + " processed"
|
||||||
}
|
}
|
||||||
future.consume {
|
future.consume {
|
||||||
result -> println("Got $result")
|
result -> println("Got $result")
|
||||||
}
|
}
|
||||||
worker.requestTermination().consume { _ -> }
|
worker.requestTermination().result
|
||||||
println("OK")
|
println("OK")
|
||||||
}
|
}
|
||||||
@@ -11,11 +11,11 @@ import kotlin.native.concurrent.*
|
|||||||
|
|
||||||
@Test fun runTest() {
|
@Test fun runTest() {
|
||||||
val COUNT = 5
|
val COUNT = 5
|
||||||
val workers = Array(COUNT, { _ -> startWorker()})
|
val workers = Array(COUNT, { _ -> Worker.start()})
|
||||||
|
|
||||||
for (attempt in 1 .. 3) {
|
for (attempt in 1 .. 3) {
|
||||||
val futures = Array(workers.size,
|
val futures = Array(workers.size,
|
||||||
{ i -> workers[i].schedule(TransferMode.CHECKED, { "$attempt: Input $i" })
|
{ i -> workers[i].execute(TransferMode.SAFE, { "$attempt: Input $i" })
|
||||||
{ input -> input + " processed" }
|
{ input -> input + " processed" }
|
||||||
})
|
})
|
||||||
futures.forEachIndexed { index, future ->
|
futures.forEachIndexed { index, future ->
|
||||||
@@ -29,7 +29,7 @@ import kotlin.native.concurrent.*
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
workers.forEach {
|
workers.forEach {
|
||||||
it.requestTermination().consume { _ -> }
|
it.requestTermination().result
|
||||||
}
|
}
|
||||||
println("OK")
|
println("OK")
|
||||||
}
|
}
|
||||||
@@ -14,10 +14,10 @@ data class WorkerResult(val intResult: Int, val stringResult: String)
|
|||||||
|
|
||||||
@Test fun runTest() {
|
@Test fun runTest() {
|
||||||
val COUNT = 5
|
val COUNT = 5
|
||||||
val workers = Array(COUNT, { _ -> startWorker()})
|
val workers = Array(COUNT, { _ -> Worker.start()})
|
||||||
|
|
||||||
for (attempt in 1 .. 3) {
|
for (attempt in 1 .. 3) {
|
||||||
val futures = Array(workers.size, { workerIndex -> workers[workerIndex].schedule(TransferMode.CHECKED, {
|
val futures = Array(workers.size, { workerIndex -> workers[workerIndex].execute(TransferMode.SAFE, {
|
||||||
WorkerArgument(workerIndex, "attempt $attempt") }) { input ->
|
WorkerArgument(workerIndex, "attempt $attempt") }) { input ->
|
||||||
var sum = 0
|
var sum = 0
|
||||||
for (i in 0..input.intParam * 1000) {
|
for (i in 0..input.intParam * 1000) {
|
||||||
@@ -33,12 +33,13 @@ data class WorkerResult(val intResult: Int, val stringResult: String)
|
|||||||
ready.forEach {
|
ready.forEach {
|
||||||
it.consume { result ->
|
it.consume { result ->
|
||||||
if (result.stringResult != "attempt $attempt result") throw Error("Unexpected $result")
|
if (result.stringResult != "attempt $attempt result") throw Error("Unexpected $result")
|
||||||
consumed++ }
|
consumed++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
workers.forEach {
|
workers.forEach {
|
||||||
it.requestTermination().consume { _ -> }
|
it.requestTermination().result
|
||||||
}
|
}
|
||||||
println("OK")
|
println("OK")
|
||||||
}
|
}
|
||||||
@@ -18,19 +18,19 @@ data class WorkerResult(val intResult: Int, val stringResult: String)
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
val worker = startWorker()
|
val worker = Worker.start()
|
||||||
val dataParam = DataParam(17)
|
val dataParam = DataParam(17)
|
||||||
val future = try {
|
val future = try {
|
||||||
worker.schedule(TransferMode.CHECKED,
|
worker.execute(TransferMode.SAFE,
|
||||||
{ WorkerArgument(42, dataParam) },
|
{ WorkerArgument(42, dataParam) }) {
|
||||||
{ input -> WorkerResult(input.intParam, input.dataParam.toString() + " result") }
|
input -> WorkerResult(input.intParam, input.dataParam.toString() + " result")
|
||||||
)
|
}
|
||||||
} catch (e: IllegalStateException) {
|
} catch (e: IllegalStateException) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
if (future != null)
|
if (future != null)
|
||||||
println("Fail 1")
|
println("Fail 1")
|
||||||
if (dataParam.int != 17) println("Fail 2")
|
if (dataParam.int != 17) println("Fail 2")
|
||||||
worker.requestTermination().consume { _ -> }
|
worker.requestTermination().result
|
||||||
println("OK")
|
println("OK")
|
||||||
}
|
}
|
||||||
@@ -10,13 +10,13 @@ import kotlin.test.*
|
|||||||
import kotlin.native.concurrent.*
|
import kotlin.native.concurrent.*
|
||||||
|
|
||||||
@Test fun runTest() {
|
@Test fun runTest() {
|
||||||
val worker = startWorker()
|
val worker = Worker.start()
|
||||||
val future = worker.schedule(TransferMode.CHECKED, { 41 }) {
|
val future = worker.execute(TransferMode.SAFE, { 41 }) {
|
||||||
input -> input + 1
|
input -> input + 1
|
||||||
}
|
}
|
||||||
future.consume {
|
future.consume {
|
||||||
result -> println("Got $result")
|
result -> println("Got $result")
|
||||||
}
|
}
|
||||||
worker.requestTermination().consume { _ -> }
|
worker.requestTermination().result
|
||||||
println("OK")
|
println("OK")
|
||||||
}
|
}
|
||||||
@@ -10,13 +10,13 @@ import kotlin.test.*
|
|||||||
import kotlin.native.concurrent.*
|
import kotlin.native.concurrent.*
|
||||||
|
|
||||||
@Test fun runTest() {
|
@Test fun runTest() {
|
||||||
val worker = startWorker()
|
val worker = Worker.start()
|
||||||
val future = worker.schedule(TransferMode.CHECKED, { "zzz" }) {
|
val future = worker.execute(TransferMode.SAFE, { "zzz" }) {
|
||||||
input -> input.length
|
input -> input.length
|
||||||
}
|
}
|
||||||
future.consume {
|
future.consume {
|
||||||
result -> println("Got $result")
|
result -> println("Got $result")
|
||||||
}
|
}
|
||||||
worker.requestTermination().consume { _ -> }
|
worker.requestTermination().result
|
||||||
println("OK")
|
println("OK")
|
||||||
}
|
}
|
||||||
@@ -10,13 +10,13 @@ import kotlin.test.*
|
|||||||
import kotlin.native.concurrent.*
|
import kotlin.native.concurrent.*
|
||||||
|
|
||||||
@Test fun runTest() {
|
@Test fun runTest() {
|
||||||
val worker = startWorker()
|
val worker = Worker.start()
|
||||||
val future = worker.schedule(TransferMode.CHECKED, { 42 }) {
|
val future = worker.execute(TransferMode.SAFE, { 42 }) {
|
||||||
input -> input.toString()
|
input -> input.toString()
|
||||||
}
|
}
|
||||||
future.consume {
|
future.consume {
|
||||||
result -> println("Got $result")
|
result -> println("Got $result")
|
||||||
}
|
}
|
||||||
worker.requestTermination().consume { _ -> }
|
worker.requestTermination().result
|
||||||
println("OK")
|
println("OK")
|
||||||
}
|
}
|
||||||
@@ -10,13 +10,13 @@ import kotlin.test.*
|
|||||||
import kotlin.native.concurrent.*
|
import kotlin.native.concurrent.*
|
||||||
|
|
||||||
@Test fun runTest() {
|
@Test fun runTest() {
|
||||||
val worker = startWorker()
|
val worker = Worker.start()
|
||||||
val future = worker.schedule(TransferMode.CHECKED, { "Input" }) {
|
val future = worker.execute(TransferMode.SAFE, { "Input" }) {
|
||||||
input -> println(input)
|
input -> println(input)
|
||||||
}
|
}
|
||||||
future.consume {
|
future.consume {
|
||||||
result -> println("Got $result")
|
result -> println("Got $result")
|
||||||
}
|
}
|
||||||
worker.requestTermination().consume { _ -> }
|
worker.requestTermination().result
|
||||||
println("OK")
|
println("OK")
|
||||||
}
|
}
|
||||||
@@ -14,19 +14,19 @@ data class SharedDataMember(val double: Double)
|
|||||||
data class SharedData(val string: String, val int: Int, val member: SharedDataMember)
|
data class SharedData(val string: String, val int: Int, val member: SharedDataMember)
|
||||||
|
|
||||||
@Test fun runTest() {
|
@Test fun runTest() {
|
||||||
val worker = startWorker()
|
val worker = Worker.start()
|
||||||
// Here we do rather strange thing. To test object detach API we detach object graph,
|
// Here we do rather strange thing. To test object detach API we detach object graph,
|
||||||
// pass C pointer as a value to worker, where we manually reattached passed value.
|
// pass detached graph to a worker, where we manually reattached passed value.
|
||||||
val future = worker.schedule(TransferMode.CHECKED, {
|
val future = worker.execute(TransferMode.SAFE, {
|
||||||
detachObjectGraph { SharedData("Hello", 10, SharedDataMember(0.1)) }
|
DetachedObjectGraph { SharedData("Hello", 10, SharedDataMember(0.1)) }.asCPointer()
|
||||||
} ) {
|
}) {
|
||||||
inputC ->
|
inputDetached ->
|
||||||
val input = attachObjectGraph<SharedData>(inputC)
|
val input = DetachedObjectGraph<SharedData>(inputDetached).attach()
|
||||||
println(input)
|
println(input)
|
||||||
}
|
}
|
||||||
future.consume {
|
future.consume {
|
||||||
result -> println("Got $result")
|
result -> println("Got $result")
|
||||||
}
|
}
|
||||||
worker.requestTermination().consume { _ -> }
|
worker.requestTermination().result
|
||||||
println("OK")
|
println("OK")
|
||||||
}
|
}
|
||||||
@@ -11,14 +11,14 @@ import kotlin.native.concurrent.*
|
|||||||
|
|
||||||
@Test fun runTest() {
|
@Test fun runTest() {
|
||||||
withLock { println("zzz") }
|
withLock { println("zzz") }
|
||||||
val worker = startWorker()
|
val worker = Worker.start()
|
||||||
val future = worker.schedule(TransferMode.CHECKED, {}) {
|
val future = worker.execute(TransferMode.SAFE, {}) {
|
||||||
withLock {
|
withLock {
|
||||||
println("42")
|
println("42")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
future.result()
|
future.result
|
||||||
worker.requestTermination().result()
|
worker.requestTermination().result
|
||||||
println("OK")
|
println("OK")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ template <typename T> void setImpl(KRef thiz, T value) {
|
|||||||
atomicSet(location, value);
|
atomicSet(location, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T> T getImpl(KRef thiz) {
|
||||||
|
volatile T* location = reinterpret_cast<volatile T*>(thiz + 1);
|
||||||
|
return atomicGet(location);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T> T addAndGetImpl(KRef thiz, T delta) {
|
template <typename T> T addAndGetImpl(KRef thiz, T delta) {
|
||||||
volatile T* location = reinterpret_cast<volatile T*>(thiz + 1);
|
volatile T* location = reinterpret_cast<volatile T*>(thiz + 1);
|
||||||
return atomicAdd(location, delta);
|
return atomicAdd(location, delta);
|
||||||
@@ -42,6 +47,11 @@ template <typename T> T compareAndSwapImpl(KRef thiz, T expectedValue, T newValu
|
|||||||
return compareAndSwap(location, expectedValue, newValue);
|
return compareAndSwap(location, expectedValue, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T> KBoolean compareAndSetImpl(KRef thiz, T expectedValue, T newValue) {
|
||||||
|
volatile T* location = reinterpret_cast<volatile T*>(thiz + 1);
|
||||||
|
return compareAndSet(location, expectedValue, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
inline AtomicReferenceLayout* asAtomicReference(KRef thiz) {
|
inline AtomicReferenceLayout* asAtomicReference(KRef thiz) {
|
||||||
return reinterpret_cast<AtomicReferenceLayout*>(thiz + 1);
|
return reinterpret_cast<AtomicReferenceLayout*>(thiz + 1);
|
||||||
}
|
}
|
||||||
@@ -58,54 +68,105 @@ KInt Kotlin_AtomicInt_compareAndSwap(KRef thiz, KInt expectedValue, KInt newValu
|
|||||||
return compareAndSwapImpl(thiz, expectedValue, newValue);
|
return compareAndSwapImpl(thiz, expectedValue, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KBoolean Kotlin_AtomicInt_compareAndSet(KRef thiz, KInt expectedValue, KInt newValue) {
|
||||||
|
return compareAndSetImpl(thiz, expectedValue, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
void Kotlin_AtomicInt_set(KRef thiz, KInt newValue) {
|
void Kotlin_AtomicInt_set(KRef thiz, KInt newValue) {
|
||||||
setImpl(thiz, newValue);
|
setImpl(thiz, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KInt Kotlin_AtomicInt_get(KRef thiz) {
|
||||||
|
return getImpl<KInt>(thiz);
|
||||||
|
}
|
||||||
|
|
||||||
KLong Kotlin_AtomicLong_addAndGet(KRef thiz, KLong delta) {
|
KLong Kotlin_AtomicLong_addAndGet(KRef thiz, KLong delta) {
|
||||||
return addAndGetImpl(thiz, delta);
|
return addAndGetImpl(thiz, delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef __mips
|
||||||
|
static int lock64 = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
KLong Kotlin_AtomicLong_compareAndSwap(KRef thiz, KLong expectedValue, KLong newValue) {
|
KLong Kotlin_AtomicLong_compareAndSwap(KRef thiz, KLong expectedValue, KLong newValue) {
|
||||||
#ifdef __mips
|
#ifdef __mips
|
||||||
// Potentially huge performance penalty, but correct.
|
// Potentially huge performance penalty, but correct.
|
||||||
// TODO: reconsider, once target MIPS can do proper 64-bit CAS.
|
// TODO: reconsider, once target MIPS can do proper 64-bit CAS.
|
||||||
static int lock = 0;
|
while (compareAndSwap(&lock64, 0, 1) != 0);
|
||||||
while (compareAndSwap(&lock, 0, 1) != 0);
|
|
||||||
KLong* address = reinterpret_cast<KLong*>(thiz + 1);
|
KLong* address = reinterpret_cast<KLong*>(thiz + 1);
|
||||||
KLong old = *address;
|
KLong old = *address;
|
||||||
if (old == expectedValue) {
|
if (old == expectedValue) {
|
||||||
*address = newValue;
|
*address = newValue;
|
||||||
}
|
}
|
||||||
compareAndSwap(&lock, 1, 0);
|
compareAndSwap(&lock64, 1, 0);
|
||||||
return old;
|
return old;
|
||||||
#else
|
#else
|
||||||
return compareAndSwapImpl(thiz, expectedValue, newValue);
|
return compareAndSwapImpl(thiz, expectedValue, newValue);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KBoolean Kotlin_AtomicLong_compareAndSet(KRef thiz, KLong expectedValue, KLong newValue) {
|
||||||
|
#ifdef __mips
|
||||||
|
// Potentially huge performance penalty, but correct.
|
||||||
|
// TODO: reconsider, once target MIPS can do proper 64-bit CAS.
|
||||||
|
KBoolean result = false;
|
||||||
|
while (compareAndSwap(&lock64, 0, 1) != 0);
|
||||||
|
KLong* address = reinterpret_cast<KLong*>(thiz + 1);
|
||||||
|
KLong old = *address;
|
||||||
|
if (old == expectedValue) {
|
||||||
|
result = true;
|
||||||
|
*address = newValue;
|
||||||
|
}
|
||||||
|
compareAndSwap(&lock64, 1, 0);
|
||||||
|
return result;
|
||||||
|
#else
|
||||||
|
return compareAndSetImpl(thiz, expectedValue, newValue);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void Kotlin_AtomicLong_set(KRef thiz, KLong newValue) {
|
void Kotlin_AtomicLong_set(KRef thiz, KLong newValue) {
|
||||||
#ifdef __mips
|
#ifdef __mips
|
||||||
// Potentially huge performance penalty, but correct.
|
// Potentially huge performance penalty, but correct.
|
||||||
// TODO: reconsider, once target MIPS can do proper 64-bit atomic store.
|
// TODO: reconsider, once target MIPS can do proper 64-bit atomic store.
|
||||||
static int lock = 0;
|
while (compareAndSwap(&lock64, 0, 1) != 0);
|
||||||
while (compareAndSwap(&lock, 0, 1) != 0);
|
|
||||||
KLong* address = reinterpret_cast<KLong*>(thiz + 1);
|
KLong* address = reinterpret_cast<KLong*>(thiz + 1);
|
||||||
*address = newValue;
|
*address = newValue;
|
||||||
compareAndSwap(&lock, 1, 0);
|
compareAndSwap(&lock64, 1, 0);
|
||||||
#else
|
#else
|
||||||
setImpl(thiz, newValue);
|
setImpl(thiz, newValue);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KLong Kotlin_AtomicLong_get(KRef thiz) {
|
||||||
|
#ifdef __mips
|
||||||
|
// Potentially huge performance penalty, but correct.
|
||||||
|
// TODO: reconsider, once target MIPS can do proper 64-bit atomic store.
|
||||||
|
while (compareAndSwap(&lock64, 0, 1) != 0);
|
||||||
|
KLong* address = reinterpret_cast<KLong*>(thiz + 1);
|
||||||
|
KLong value = *address;
|
||||||
|
compareAndSwap(&lock64, 1, 0);
|
||||||
|
return value;
|
||||||
|
#else
|
||||||
|
return getImpl<KLong>(thiz);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
KNativePtr Kotlin_AtomicNativePtr_compareAndSwap(KRef thiz, KNativePtr expectedValue, KNativePtr newValue) {
|
KNativePtr Kotlin_AtomicNativePtr_compareAndSwap(KRef thiz, KNativePtr expectedValue, KNativePtr newValue) {
|
||||||
return compareAndSwapImpl(thiz, expectedValue, newValue);
|
return compareAndSwapImpl(thiz, expectedValue, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KBoolean Kotlin_AtomicNativePtr_compareAndSet(KRef thiz, KNativePtr expectedValue, KNativePtr newValue) {
|
||||||
|
return compareAndSetImpl(thiz, expectedValue, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
void Kotlin_AtomicNativePtr_set(KRef thiz, KNativePtr newValue) {
|
void Kotlin_AtomicNativePtr_set(KRef thiz, KNativePtr newValue) {
|
||||||
setImpl(thiz, newValue);
|
setImpl(thiz, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KNativePtr Kotlin_AtomicNativePtr_get(KRef thiz) {
|
||||||
|
return getImpl<KNativePtr>(thiz);
|
||||||
|
}
|
||||||
|
|
||||||
void Kotlin_AtomicReference_checkIfFrozen(KRef value) {
|
void Kotlin_AtomicReference_checkIfFrozen(KRef value) {
|
||||||
if (value != nullptr && !value->container()->permanentOrFrozen()) {
|
if (value != nullptr && !value->container()->permanentOrFrozen()) {
|
||||||
ThrowInvalidMutabilityException(value);
|
ThrowInvalidMutabilityException(value);
|
||||||
@@ -119,6 +180,15 @@ OBJ_GETTER(Kotlin_AtomicReference_compareAndSwap, KRef thiz, KRef expectedValue,
|
|||||||
RETURN_RESULT_OF(SwapRefLocked, &ref->value_, expectedValue, newValue, &ref->lock_);
|
RETURN_RESULT_OF(SwapRefLocked, &ref->value_, expectedValue, newValue, &ref->lock_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KBoolean Kotlin_AtomicReference_compareAndSet(KRef thiz, KRef expectedValue, KRef newValue) {
|
||||||
|
Kotlin_AtomicReference_checkIfFrozen(newValue);
|
||||||
|
// See Kotlin_AtomicReference_get() for explanations, why locking is needed.
|
||||||
|
AtomicReferenceLayout* ref = asAtomicReference(thiz);
|
||||||
|
ObjHolder holder;
|
||||||
|
auto old = SwapRefLocked(&ref->value_, expectedValue, newValue, &ref->lock_, holder.slot());
|
||||||
|
return old == expectedValue;
|
||||||
|
}
|
||||||
|
|
||||||
void Kotlin_AtomicReference_set(KRef thiz, KRef newValue) {
|
void Kotlin_AtomicReference_set(KRef thiz, KRef newValue) {
|
||||||
Kotlin_AtomicReference_checkIfFrozen(newValue);
|
Kotlin_AtomicReference_checkIfFrozen(newValue);
|
||||||
AtomicReferenceLayout* ref = asAtomicReference(thiz);
|
AtomicReferenceLayout* ref = asAtomicReference(thiz);
|
||||||
|
|||||||
@@ -25,6 +25,21 @@ ALWAYS_INLINE inline T compareAndSwap(volatile T* where, T expectedValue, T newV
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
ALWAYS_INLINE inline bool compareAndSet(volatile T* where, T expectedValue, T newValue) {
|
||||||
|
#ifndef KONAN_NO_THREADS
|
||||||
|
return __sync_bool_compare_and_swap(where, expectedValue, newValue);
|
||||||
|
#else
|
||||||
|
T oldValue = *where;
|
||||||
|
if (oldValue == expectedValue) {
|
||||||
|
*where = newValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
ALWAYS_INLINE inline void atomicSet(volatile T* where, T what) {
|
ALWAYS_INLINE inline void atomicSet(volatile T* where, T what) {
|
||||||
#ifndef KONAN_NO_THREADS
|
#ifndef KONAN_NO_THREADS
|
||||||
@@ -34,4 +49,15 @@ ALWAYS_INLINE inline void atomicSet(volatile T* where, T what) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
ALWAYS_INLINE inline T atomicGet(volatile T* where) {
|
||||||
|
#ifndef KONAN_NO_THREADS
|
||||||
|
T what;
|
||||||
|
__atomic_load(where, &what, __ATOMIC_SEQ_CST);
|
||||||
|
return what;
|
||||||
|
#else
|
||||||
|
return *where;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#endif // RUNTIME_ATOMIC_H
|
#endif // RUNTIME_ATOMIC_H
|
||||||
@@ -512,7 +512,7 @@ KInt Kotlin_Worker_requestTerminationWorkerInternal(KInt id, KBoolean processSch
|
|||||||
return requestTermination(id, processScheduledJobs);
|
return requestTermination(id, processScheduledJobs);
|
||||||
}
|
}
|
||||||
|
|
||||||
KInt Kotlin_Worker_scheduleInternal(KInt id, KInt transferMode, KRef producer, KNativePtr job) {
|
KInt Kotlin_Worker_executeInternal(KInt id, KInt transferMode, KRef producer, KNativePtr job) {
|
||||||
return schedule(id, transferMode, producer, job);
|
return schedule(id, transferMode, producer, job);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,124 +10,166 @@ import kotlin.native.internal.NoReorderFields
|
|||||||
import kotlin.native.SymbolName
|
import kotlin.native.SymbolName
|
||||||
import kotlinx.cinterop.NativePtr
|
import kotlinx.cinterop.NativePtr
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atomic values and freezing: atomics [AtomicInt], [AtomicLong], [AtomicNativePtr] and [AtomicReference]
|
||||||
|
* are unique types with regard to freezing. Namely, they provide mutating operations, while can participate
|
||||||
|
* in frozen subgraphs. So shared frozen objects can have fields of atomic types.
|
||||||
|
*/
|
||||||
@Frozen
|
@Frozen
|
||||||
class AtomicInt(private var value: Int = 0) {
|
public class AtomicInt(private var value_: Int) {
|
||||||
|
|
||||||
|
public var value: Int
|
||||||
|
get() = getImpl()
|
||||||
|
set(new) = setImpl(new)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increments the value by [delta] and returns the new value.
|
* Increments the value by [delta] and returns the new value.
|
||||||
*/
|
*/
|
||||||
@SymbolName("Kotlin_AtomicInt_addAndGet")
|
@SymbolName("Kotlin_AtomicInt_addAndGet")
|
||||||
external fun addAndGet(delta: Int): Int
|
external public fun addAndGet(delta: Int): Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares value with [expected] and replaces it with [new] value if values matches.
|
* Compares value with [expected] and replaces it with [new] value if values matches.
|
||||||
* Returns the old value.
|
* Returns the old value.
|
||||||
*/
|
*/
|
||||||
@SymbolName("Kotlin_AtomicInt_compareAndSwap")
|
@SymbolName("Kotlin_AtomicInt_compareAndSwap")
|
||||||
external fun compareAndSwap(expected: Int, new: Int): Int
|
external public fun compareAndSwap(expected: Int, new: Int): Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the new atomic value.
|
* Compares value with [expected] and replaces it with [new] value if values matches.
|
||||||
|
* Returns true if successful.
|
||||||
*/
|
*/
|
||||||
@SymbolName("Kotlin_AtomicInt_set")
|
@SymbolName("Kotlin_AtomicInt_compareAndSet")
|
||||||
external fun set(new: Int): Unit
|
external public fun compareAndSet(expected: Int, new: Int): Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increments value by one.
|
* Increments value by one.
|
||||||
*/
|
*/
|
||||||
fun increment(): Int = addAndGet(1)
|
public fun increment(): Unit {
|
||||||
|
addAndGet(1)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrements value by one.
|
* Decrements value by one.
|
||||||
*/
|
*/
|
||||||
fun decrement(): Int = addAndGet(-1)
|
public fun decrement(): Unit {
|
||||||
|
addAndGet(-1)
|
||||||
/**
|
}
|
||||||
* Returns the current value.
|
|
||||||
*/
|
|
||||||
fun get(): Int = value
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the string representation of this object.
|
* Returns the string representation of this object.
|
||||||
*/
|
*/
|
||||||
public override fun toString(): String = "AtomicInt $value"
|
public override fun toString(): String = value.toString()
|
||||||
|
|
||||||
|
// Implementation details.
|
||||||
|
@SymbolName("Kotlin_AtomicInt_set")
|
||||||
|
private external fun setImpl(new: Int): Unit
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_AtomicInt_get")
|
||||||
|
private external fun getImpl(): Int
|
||||||
}
|
}
|
||||||
|
|
||||||
@Frozen
|
@Frozen
|
||||||
class AtomicLong(private var value: Long = 0) {
|
public class AtomicLong(private var value_: Long = 0) {
|
||||||
|
|
||||||
|
public var value: Long
|
||||||
|
get() = getImpl()
|
||||||
|
set(new) = setImpl(new)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increments the value by [delta] and returns the new value.
|
* Increments the value by [delta] and returns the new value.
|
||||||
*/
|
*/
|
||||||
@SymbolName("Kotlin_AtomicLong_addAndGet")
|
@SymbolName("Kotlin_AtomicLong_addAndGet")
|
||||||
external fun addAndGet(delta: Long): Long
|
external public fun addAndGet(delta: Long): Long
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increments the value by [delta] and returns the new value.
|
* Increments the value by [delta] and returns the new value.
|
||||||
*/
|
*/
|
||||||
fun addAndGet(delta: Int): Long = addAndGet(delta.toLong())
|
public fun addAndGet(delta: Int): Long = addAndGet(delta.toLong())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares value with [expected] and replaces it with [new] value if values matches.
|
* Compares value with [expected] and replaces it with [new] value if values matches.
|
||||||
* Returns the old value.
|
* Returns the old value.
|
||||||
*/
|
*/
|
||||||
@SymbolName("Kotlin_AtomicLong_compareAndSwap")
|
@SymbolName("Kotlin_AtomicLong_compareAndSwap")
|
||||||
external fun compareAndSwap(expected: Long, new: Long): Long
|
external public fun compareAndSwap(expected: Long, new: Long): Long
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the new atomic value.
|
* Compares value with [expected] and replaces it with [new] value if values matches.
|
||||||
|
* Returns true if successful.
|
||||||
*/
|
*/
|
||||||
@SymbolName("Kotlin_AtomicLong_set")
|
@SymbolName("Kotlin_AtomicLong_compareAndSet")
|
||||||
external fun set(new: Long): Unit
|
external public fun compareAndSet(expected: Long, new: Long): Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increments value by one.
|
* Increments value by one.
|
||||||
*/
|
*/
|
||||||
fun increment(): Long = addAndGet(1L)
|
public fun increment(): Unit {
|
||||||
|
addAndGet(1L)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrements value by one.
|
* Decrements value by one.
|
||||||
*/
|
*/
|
||||||
fun decrement(): Long = addAndGet(-1L)
|
fun decrement(): Unit {
|
||||||
|
addAndGet(-1L)
|
||||||
/**
|
}
|
||||||
* Returns the current value.
|
|
||||||
*/
|
|
||||||
fun get(): Long = value
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the string representation of this object.
|
* Returns the string representation of this object.
|
||||||
*/
|
*/
|
||||||
public override fun toString(): String = "AtomicLong $value"
|
public override fun toString(): String = value.toString()
|
||||||
|
|
||||||
|
// Implementation details.
|
||||||
|
@SymbolName("Kotlin_AtomicLong_set")
|
||||||
|
private external fun setImpl(new: Long): Unit
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_AtomicLong_get")
|
||||||
|
private external fun getImpl(): Long
|
||||||
}
|
}
|
||||||
|
|
||||||
@Frozen
|
@Frozen
|
||||||
class AtomicNativePtr(private var value: NativePtr) {
|
public class AtomicNativePtr(private var value_: NativePtr) {
|
||||||
|
|
||||||
|
public var value: NativePtr
|
||||||
|
get() = getImpl()
|
||||||
|
set(new) = setImpl(new)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares value with [expected] and replaces it with [new] value if values matches.
|
* Compares value with [expected] and replaces it with [new] value if values matches.
|
||||||
* Returns the old value.
|
* Returns the old value.
|
||||||
*/
|
*/
|
||||||
@SymbolName("Kotlin_AtomicNativePtr_compareAndSwap")
|
@SymbolName("Kotlin_AtomicNativePtr_compareAndSwap")
|
||||||
external fun compareAndSwap(expected: NativePtr, new: NativePtr): NativePtr
|
external public fun compareAndSwap(expected: NativePtr, new: NativePtr): NativePtr
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current value.
|
* Compares value with [expected] and replaces it with [new] value if values matches.
|
||||||
*/
|
*/
|
||||||
fun get(): NativePtr = value
|
@SymbolName("Kotlin_AtomicNativePtr_compareAndSet")
|
||||||
}
|
external public fun compareAndSet(expected: NativePtr, new: NativePtr): Boolean
|
||||||
|
|
||||||
@SymbolName("Kotlin_AtomicReference_checkIfFrozen")
|
/**
|
||||||
external private fun checkIfFrozen(ref: Any?)
|
* Returns the string representation of this object.
|
||||||
|
*/
|
||||||
|
public override fun toString(): String = value.toString()
|
||||||
|
|
||||||
|
// Implementation details.
|
||||||
|
@SymbolName("Kotlin_AtomicNativePtr_set")
|
||||||
|
private external fun setImpl(new: NativePtr): Unit
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_AtomicNativePtr_get")
|
||||||
|
private external fun getImpl(): NativePtr
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An atomic reference to a frozen Kotlin object. Can be used in concurrent scenarious
|
* An atomic reference to a frozen Kotlin object. Can be used in concurrent scenarious
|
||||||
* and must be zeroed out (with `compareAndSwap(get(), null)`) once no longer needed.
|
* but frequently shall be of nullable type and be zeroed out (with `compareAndSwap(get(), null)`)
|
||||||
* Otherwise memory leak could happen.
|
* once no longer needed. Otherwise memory leak could happen.
|
||||||
*/
|
*/
|
||||||
@Frozen
|
@Frozen
|
||||||
@NoReorderFields
|
@NoReorderFields
|
||||||
class AtomicReference<T>(private var value: T? = null) {
|
public class AtomicReference<T>(private var value_: T) {
|
||||||
// A spinlock to fix potential ARC race. Not an AtomicInt just for the effeciency sake.
|
// A spinlock to fix potential ARC race.
|
||||||
private var lock: Int = 0
|
private var lock: Int = 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,6 +180,15 @@ class AtomicReference<T>(private var value: T? = null) {
|
|||||||
checkIfFrozen(value)
|
checkIfFrozen(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value to [new] value
|
||||||
|
* If [new] value is not null, it must be frozen or permanent object, otherwise an
|
||||||
|
* @InvalidMutabilityException is thrown.
|
||||||
|
*/
|
||||||
|
public var value: T
|
||||||
|
get() = @Suppress("UNCHECKED_CAST")(getImpl() as T)
|
||||||
|
set(new) = setImpl(new)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares value with [expected] and replaces it with [new] value if values matches.
|
* Compares value with [expected] and replaces it with [new] value if values matches.
|
||||||
* If [new] value is not null, it must be frozen or permanent object, otherwise an
|
* If [new] value is not null, it must be frozen or permanent object, otherwise an
|
||||||
@@ -145,75 +196,21 @@ class AtomicReference<T>(private var value: T? = null) {
|
|||||||
* Returns the old value.
|
* Returns the old value.
|
||||||
*/
|
*/
|
||||||
@SymbolName("Kotlin_AtomicReference_compareAndSwap")
|
@SymbolName("Kotlin_AtomicReference_compareAndSwap")
|
||||||
external public fun compareAndSwap(expected: T?, new: T?): T?
|
external public fun compareAndSwap(expected: T, new: T): T
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_AtomicReference_compareAndSet")
|
||||||
|
external public fun compareAndSet(expected: T, new: T): Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the value to [new] value
|
* Returns the string representation of this object.
|
||||||
* If [new] value is not null, it must be frozen or permanent object, otherwise an
|
|
||||||
* @InvalidMutabilityException is thrown.
|
|
||||||
*/
|
*/
|
||||||
|
public override fun toString(): String = "Atomic reference to $value"
|
||||||
|
|
||||||
|
// Implementation details.
|
||||||
@SymbolName("Kotlin_AtomicReference_set")
|
@SymbolName("Kotlin_AtomicReference_set")
|
||||||
external public fun set(new: T?): Unit
|
private external fun setImpl(new: Any?): Unit
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current value.
|
|
||||||
*/
|
|
||||||
@SymbolName("Kotlin_AtomicReference_get")
|
@SymbolName("Kotlin_AtomicReference_get")
|
||||||
external public fun get(): T?
|
private external fun getImpl(): Any?
|
||||||
}
|
|
||||||
|
|
||||||
internal object UNINITIALIZED {
|
}
|
||||||
// So that single-threaded configs can use those as well.
|
|
||||||
init {
|
|
||||||
freeze()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal object INITIALIZING {
|
|
||||||
// So that single-threaded configs can use those as well.
|
|
||||||
init {
|
|
||||||
freeze()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Frozen
|
|
||||||
internal class AtomicLazyImpl<out T>(initializer: () -> T) : Lazy<T> {
|
|
||||||
private val initializer_ = AtomicReference<Function0<T>?>(initializer.freeze())
|
|
||||||
private val value_ = AtomicReference<Any?>(UNINITIALIZED)
|
|
||||||
|
|
||||||
override val value: T
|
|
||||||
get() {
|
|
||||||
if (value_.compareAndSwap(UNINITIALIZED, INITIALIZING) === UNINITIALIZED) {
|
|
||||||
// We execute exclusively here.
|
|
||||||
val ctor = initializer_.get()
|
|
||||||
if (ctor != null && initializer_.compareAndSwap(ctor, null) === ctor) {
|
|
||||||
value_.compareAndSwap(INITIALIZING, ctor().freeze())
|
|
||||||
} else {
|
|
||||||
// Something wrong.
|
|
||||||
assert(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var result: Any?
|
|
||||||
do {
|
|
||||||
result = value_.get()
|
|
||||||
} while (result === INITIALIZING)
|
|
||||||
|
|
||||||
assert(result !== UNINITIALIZED && result !== INITIALIZING)
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
return result as T
|
|
||||||
}
|
|
||||||
|
|
||||||
// Racy!
|
|
||||||
override fun isInitialized(): Boolean = value_.get() !== UNINITIALIZED
|
|
||||||
|
|
||||||
override fun toString(): String = if (isInitialized())
|
|
||||||
value_.get().toString() else "Lazy value not initialized yet."
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Atomic lazy initializer, could be used in frozen objects, freezes initializing lambda,
|
|
||||||
* so use very carefully. Also, as with other uses of an @AtomicReference may potentially
|
|
||||||
* leak memory, so it is recommended to use `atomicLazy` in cases of objects living forever,
|
|
||||||
* such as object signletons, or in cases where it's guaranteed not to have cyclical garbage.
|
|
||||||
*/
|
|
||||||
public fun <T> atomicLazy(initializer: () -> T): Lazy<T> = AtomicLazyImpl(initializer)
|
|
||||||
@@ -5,10 +5,9 @@
|
|||||||
|
|
||||||
package kotlin.native.concurrent
|
package kotlin.native.concurrent
|
||||||
|
|
||||||
import kotlin.native.internal.ExportForCppRuntime
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exception thrown whenever freezing is not possible.
|
* Exception thrown whenever freezing is not possible.
|
||||||
|
* [blocker] is an object preventing freezing, usually one marked with [ensureNeverFrozen] earlier.
|
||||||
*/
|
*/
|
||||||
public class FreezingException(toFreeze: Any, blocker: Any) :
|
public class FreezingException(toFreeze: Any, blocker: Any) :
|
||||||
RuntimeException("freezing of $toFreeze has failed, first blocker is $blocker")
|
RuntimeException("freezing of $toFreeze has failed, first blocker is $blocker")
|
||||||
@@ -23,31 +22,20 @@ public class InvalidMutabilityException(where: Any) :
|
|||||||
* Freezes object subgraph reachable from this object. Frozen objects can be freely
|
* Freezes object subgraph reachable from this object. Frozen objects can be freely
|
||||||
* shared between threads/workers.
|
* shared between threads/workers.
|
||||||
*/
|
*/
|
||||||
fun <T> T.freeze(): T {
|
public fun <T> T.freeze(): T {
|
||||||
freezeInternal(this)
|
freezeInternal(this)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
val Any?.isFrozen
|
/**
|
||||||
|
* Checks if given object is null or frozen or permanent (i.e. instantiated at compile-time).
|
||||||
|
*/
|
||||||
|
public val Any?.isFrozen
|
||||||
get() = isFrozenInternal(this)
|
get() = isFrozenInternal(this)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function ensures that if we see such an object during freezing attempt - freeze fails and FreezingException
|
* This function ensures that if we see such an object during freezing attempt - freeze fails and
|
||||||
* is thrown. Is object is already frozen - FreezingException is thrown immediately.
|
* [FreezingException] is thrown. Is object is already frozen - [FreezingException] is thrown immediately.
|
||||||
*/
|
*/
|
||||||
@SymbolName("Kotlin_Worker_ensureNeverFrozen")
|
@SymbolName("Kotlin_Worker_ensureNeverFrozen")
|
||||||
external fun Any.ensureNeverFrozen()
|
public external fun Any.ensureNeverFrozen()
|
||||||
|
|
||||||
@SymbolName("Kotlin_Worker_freezeInternal")
|
|
||||||
internal external fun freezeInternal(it: Any?)
|
|
||||||
|
|
||||||
@SymbolName("Kotlin_Worker_isFrozenInternal")
|
|
||||||
internal external fun isFrozenInternal(it: Any?): Boolean
|
|
||||||
|
|
||||||
@ExportForCppRuntime
|
|
||||||
internal fun ThrowFreezingException(toFreeze: Any, blocker: Any): Nothing =
|
|
||||||
throw FreezingException(toFreeze, blocker)
|
|
||||||
|
|
||||||
@ExportForCppRuntime
|
|
||||||
internal fun ThrowInvalidMutabilityException(where: Any): Nothing = throw InvalidMutabilityException(where)
|
|
||||||
@@ -5,13 +5,7 @@
|
|||||||
|
|
||||||
package kotlin.native.concurrent
|
package kotlin.native.concurrent
|
||||||
|
|
||||||
import kotlin.native.SymbolName
|
import kotlin.native.internal.Frozen
|
||||||
import kotlin.native.internal.ExportForCppRuntime
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unique identifier of the future. Futures can be used from other workers.
|
|
||||||
*/
|
|
||||||
typealias FutureId = Int
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* State of the future object.
|
* State of the future object.
|
||||||
@@ -29,7 +23,8 @@ enum class FutureState(val value: Int) {
|
|||||||
/**
|
/**
|
||||||
* Class representing abstract computation, whose result may become available in the future.
|
* Class representing abstract computation, whose result may become available in the future.
|
||||||
*/
|
*/
|
||||||
public inline class Future<T>(val id: FutureId) {
|
@Frozen
|
||||||
|
public class Future<T> internal constructor(val id: Int) {
|
||||||
/**
|
/**
|
||||||
* Blocks execution until the future is ready.
|
* Blocks execution until the future is ready.
|
||||||
*/
|
*/
|
||||||
@@ -45,7 +40,11 @@ public inline class Future<T>(val id: FutureId) {
|
|||||||
throw IllegalStateException("Future is cancelled")
|
throw IllegalStateException("Future is cancelled")
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun result(): T = consume { it -> it }
|
/**
|
||||||
|
* Blocks execution until the future is ready. Second attempt to get will result in an error.
|
||||||
|
*/
|
||||||
|
public val result: T
|
||||||
|
get() = consume { it -> it }
|
||||||
|
|
||||||
public val state: FutureState
|
public val state: FutureState
|
||||||
get() = FutureState.values()[stateOfFuture(id)]
|
get() = FutureState.values()[stateOfFuture(id)]
|
||||||
@@ -53,6 +52,8 @@ public inline class Future<T>(val id: FutureId) {
|
|||||||
public override fun equals(other: Any?): Boolean = (other is Future<*>) && (id == other.id)
|
public override fun equals(other: Any?): Boolean = (other is Future<*>) && (id == other.id)
|
||||||
|
|
||||||
public override fun hashCode(): Int = id
|
public override fun hashCode(): Int = id
|
||||||
|
|
||||||
|
override public fun toString(): String = "future $id"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,19 +82,4 @@ public fun <T> Collection<Future<T>>.waitForMultipleFutures(millis: Int): Set<Fu
|
|||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private APIs.
|
|
||||||
@SymbolName("Kotlin_Worker_stateOfFuture")
|
|
||||||
external internal fun stateOfFuture(id: FutureId): Int
|
|
||||||
|
|
||||||
@SymbolName("Kotlin_Worker_consumeFuture")
|
|
||||||
@PublishedApi
|
|
||||||
external internal fun consumeFuture(id: FutureId): Any?
|
|
||||||
|
|
||||||
@SymbolName("Kotlin_Worker_waitForAnyFuture")
|
|
||||||
external internal fun waitForAnyFuture(versionToken: Int, millis: Int): Boolean
|
|
||||||
|
|
||||||
@SymbolName("Kotlin_Worker_versionToken")
|
|
||||||
external internal fun versionToken(): Int
|
|
||||||
|
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package kotlin.native.concurrent
|
||||||
|
|
||||||
|
import kotlin.native.internal.ExportForCppRuntime
|
||||||
|
import kotlinx.cinterop.*
|
||||||
|
|
||||||
|
// Implementation details.
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_Worker_stateOfFuture")
|
||||||
|
external internal fun stateOfFuture(id: Int): Int
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_Worker_consumeFuture")
|
||||||
|
@PublishedApi
|
||||||
|
external internal fun consumeFuture(id: Int): Any?
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_Worker_waitForAnyFuture")
|
||||||
|
external internal fun waitForAnyFuture(versionToken: Int, millis: Int): Boolean
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_Worker_versionToken")
|
||||||
|
external internal fun versionToken(): Int
|
||||||
|
|
||||||
|
@kotlin.native.internal.ExportForCompiler
|
||||||
|
internal fun executeImpl(worker: Worker, mode: TransferMode, producer: () -> Any?,
|
||||||
|
job: CPointer<CFunction<*>>): Future<Any?> =
|
||||||
|
Future<Any?>(executeInternal(worker.id, mode.value, producer, job))
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_Worker_startInternal")
|
||||||
|
external internal fun startInternal(): Int
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_Worker_requestTerminationWorkerInternal")
|
||||||
|
external internal fun requestTerminationInternal(id: Int, processScheduledJobs: Boolean): Int
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_Worker_executeInternal")
|
||||||
|
external internal fun executeInternal(
|
||||||
|
id: Int, mode: Int, producer: () -> Any?, job: CPointer<CFunction<*>>): Int
|
||||||
|
|
||||||
|
@ExportForCppRuntime
|
||||||
|
internal fun ThrowWorkerUnsupported(): Unit =
|
||||||
|
throw UnsupportedOperationException("Workers are not supported")
|
||||||
|
|
||||||
|
@ExportForCppRuntime
|
||||||
|
internal fun ThrowWorkerInvalidState(): Unit =
|
||||||
|
throw IllegalStateException("Illegal transfer state")
|
||||||
|
|
||||||
|
@ExportForCppRuntime
|
||||||
|
internal fun WorkerLaunchpad(function: () -> Any?) = function()
|
||||||
|
|
||||||
|
@PublishedApi
|
||||||
|
@SymbolName("Kotlin_Worker_detachObjectGraphInternal")
|
||||||
|
external internal fun detachObjectGraphInternal(mode: Int, producer: () -> Any?): NativePtr
|
||||||
|
|
||||||
|
@PublishedApi
|
||||||
|
@SymbolName("Kotlin_Worker_attachObjectGraphInternal")
|
||||||
|
external internal fun attachObjectGraphInternal(stable: NativePtr): Any?
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_Worker_freezeInternal")
|
||||||
|
internal external fun freezeInternal(it: Any?)
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_Worker_isFrozenInternal")
|
||||||
|
internal external fun isFrozenInternal(it: Any?): Boolean
|
||||||
|
|
||||||
|
@ExportForCppRuntime
|
||||||
|
internal fun ThrowFreezingException(toFreeze: Any, blocker: Any): Nothing =
|
||||||
|
throw FreezingException(toFreeze, blocker)
|
||||||
|
|
||||||
|
@ExportForCppRuntime
|
||||||
|
internal fun ThrowInvalidMutabilityException(where: Any): Nothing = throw InvalidMutabilityException(where)
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_AtomicReference_checkIfFrozen")
|
||||||
|
external internal fun checkIfFrozen(ref: Any?)
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
package kotlin.native.concurrent
|
package kotlin.native.concurrent
|
||||||
|
|
||||||
|
import kotlin.native.internal.Frozen
|
||||||
import kotlin.native.internal.NoReorderFields
|
import kotlin.native.internal.NoReorderFields
|
||||||
|
|
||||||
@SymbolName("Konan_ensureAcyclicAndSet")
|
@SymbolName("Konan_ensureAcyclicAndSet")
|
||||||
@@ -61,4 +62,59 @@ internal class FreezeAwareLazyImpl<out T>(initializer: () -> T) : Lazy<T> {
|
|||||||
|
|
||||||
override fun toString(): String = if (isInitialized())
|
override fun toString(): String = if (isInitialized())
|
||||||
value.toString() else "Lazy value not initialized yet."
|
value.toString() else "Lazy value not initialized yet."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal object UNINITIALIZED {
|
||||||
|
// So that single-threaded configs can use those as well.
|
||||||
|
init {
|
||||||
|
freeze()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal object INITIALIZING {
|
||||||
|
// So that single-threaded configs can use those as well.
|
||||||
|
init {
|
||||||
|
freeze()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Frozen
|
||||||
|
internal class AtomicLazyImpl<out T>(initializer: () -> T) : Lazy<T> {
|
||||||
|
private val initializer_ = AtomicReference<Function0<T>?>(initializer.freeze())
|
||||||
|
private val value_ = AtomicReference<Any?>(UNINITIALIZED)
|
||||||
|
|
||||||
|
override val value: T
|
||||||
|
get() {
|
||||||
|
if (value_.compareAndSwap(UNINITIALIZED, INITIALIZING) === UNINITIALIZED) {
|
||||||
|
// We execute exclusively here.
|
||||||
|
val ctor = initializer_.value
|
||||||
|
if (ctor != null && initializer_.compareAndSet(ctor, null)) {
|
||||||
|
value_.compareAndSet(INITIALIZING, ctor().freeze())
|
||||||
|
} else {
|
||||||
|
// Something wrong.
|
||||||
|
assert(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var result: Any?
|
||||||
|
do {
|
||||||
|
result = value_.value
|
||||||
|
} while (result === INITIALIZING)
|
||||||
|
|
||||||
|
assert(result !== UNINITIALIZED && result !== INITIALIZING)
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return result as T
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isInitialized(): Boolean = value_.value !== UNINITIALIZED
|
||||||
|
|
||||||
|
override fun toString(): String = if (isInitialized())
|
||||||
|
value_.value.toString() else "Lazy value not initialized yet."
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atomic lazy initializer, could be used in frozen objects, freezes initializing lambda,
|
||||||
|
* so use very carefully. Also, as with other uses of an @AtomicReference may potentially
|
||||||
|
* leak memory, so it is recommended to use `atomicLazy` in cases of objects living forever,
|
||||||
|
* such as object signletons, or in cases where it's guaranteed not to have cyclical garbage.
|
||||||
|
*/
|
||||||
|
public fun <T> atomicLazy(initializer: () -> T): Lazy<T> = AtomicLazyImpl(initializer)
|
||||||
@@ -30,7 +30,7 @@ internal class Lock {
|
|||||||
}
|
}
|
||||||
0 -> {
|
0 -> {
|
||||||
// We just got the lock.
|
// We just got the lock.
|
||||||
assert(reenterCount_.get() == 0)
|
assert(reenterCount_.value == 0)
|
||||||
break@loop
|
break@loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ internal class Lock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun unlock() {
|
fun unlock() {
|
||||||
if (reenterCount_.get() > 0) {
|
if (reenterCount_.value > 0) {
|
||||||
reenterCount_.decrement()
|
reenterCount_.decrement()
|
||||||
} else {
|
} else {
|
||||||
val lockData = CurrentThread.id.hashCode()
|
val lockData = CurrentThread.id.hashCode()
|
||||||
|
|||||||
@@ -6,57 +6,70 @@
|
|||||||
package kotlin.native.concurrent
|
package kotlin.native.concurrent
|
||||||
|
|
||||||
import kotlinx.cinterop.*
|
import kotlinx.cinterop.*
|
||||||
|
import kotlin.native.internal.Frozen
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object Transfer Basics.
|
* Object Transfer Basics.
|
||||||
*
|
*
|
||||||
* Objects can be passed between threads in one of two possible modes.
|
* Objects can be passed between threads in one of two possible modes.
|
||||||
*
|
*
|
||||||
* - CHECKED - object subgraph is checked to be not reachable by other globals or locals, and passed
|
* - SAFE - object subgraph is checked to be not reachable by other globals or locals, and passed
|
||||||
* if so, otherwise an exception is thrown
|
* if so, otherwise an exception is thrown
|
||||||
* - UNCHECKED - object is blindly passed to another worker, if there are references
|
* - UNSAFE - object is blindly passed to another worker, if there are references
|
||||||
* left in the passing worker - it may lead to crash or program malfunction
|
* left in the passing worker - it may lead to crash or program malfunction
|
||||||
*
|
*
|
||||||
* Checked mode checks if object is no longer used in passing worker, using memory-management
|
* Safe mode checks if object is no longer used in passing worker, using memory-management
|
||||||
* specific algorithm (ARC implementation relies on trial deletion on object graph rooted in
|
* specific algorithm (ARC implementation relies on trial deletion on object graph rooted in
|
||||||
* passed object), and throws IllegalStateException if object graph rooted in transferred object
|
* passed object), and throws an [IllegalStateException] if object graph rooted in transferred object
|
||||||
* is reachable by some other means,
|
* is reachable by some other means,
|
||||||
*
|
*
|
||||||
* Unchecked mode, intended for most performance crititcal operations, where object graph ownership
|
* Unsafe mode is intended for most performance critical operations, where object graph ownership
|
||||||
* is expected to be correct (such as application debugged earlier in CHECKED mode), just transfers
|
* is expected to be correct (such as application debugged earlier in [SAFE] mode), just transfers
|
||||||
* ownership without further checks.
|
* ownership without further checks.
|
||||||
*
|
*
|
||||||
* Note, that for some cases cycle collection need to be done to ensure that dead cycles do not affect
|
* Note, that for some cases cycle collection need to be done to ensure that dead cycles do not affect
|
||||||
* reachability of passed object graph. See `konan.internal.GC.collect()`.
|
* reachability of passed object graph. See `[kotlin.native.internal.GC.collect]`.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
enum class TransferMode(val value: Int) {
|
public enum class TransferMode(val value: Int) {
|
||||||
CHECKED(0),
|
SAFE(0),
|
||||||
UNCHECKED(1) // USE UNCHECKED MODE ONLY IF ABSOLUTELY SURE WHAT YOU'RE DOING!!!
|
UNSAFE(1) // USE UNSAFE MODE ONLY IF ABSOLUTELY SURE WHAT YOU'RE DOING!!!
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Frozen
|
||||||
* Creates stable pointer to object, ensuring associated object subgraph is disjoint in specified mode
|
public class DetachedObjectGraph<T> internal constructor(pointer: NativePtr) {
|
||||||
* ([TransferMode.CHECKED] by default).
|
@PublishedApi
|
||||||
* It could be stored to C variable or passed to another thread, where it could be retrieved with [attachObjectGraph].
|
internal val stable = AtomicNativePtr(pointer)
|
||||||
*/
|
|
||||||
inline fun <reified T> detachObjectGraph(mode: TransferMode = TransferMode.CHECKED, noinline producer: () -> T): COpaquePointer? =
|
/**
|
||||||
detachObjectGraphInternal(mode.value, producer as () -> Any?)
|
* Creates stable pointer to object, ensuring associated object subgraph is disjoint in specified mode
|
||||||
|
* ([TransferMode.SAFE] by default).
|
||||||
|
* Raw value returned by [asCPointer] could be stored to a C variable or passed to another Kotlin machine.
|
||||||
|
*/
|
||||||
|
public constructor(mode: TransferMode = TransferMode.SAFE, producer: () -> T)
|
||||||
|
: this(detachObjectGraphInternal(mode.value, producer as () -> Any?))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates detached object graph from value stored earlier in a C raw pointer.
|
||||||
|
*/
|
||||||
|
public constructor(pointer: COpaquePointer?) : this(pointer?.rawValue ?: NativePtr.NULL)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns raw C pointer value.
|
||||||
|
*/
|
||||||
|
public fun asCPointer(): COpaquePointer? = interpretCPointer<COpaque>(stable.value)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attaches previously detached with [detachObjectGraph] object subgraph.
|
* Attaches previously detached with [detachObjectGraph] object subgraph.
|
||||||
* Please note, that once object graph is attached, the stable pointer does not have sense anymore,
|
* Please note, that once object graph is attached, the stable pointer does not have sense anymore,
|
||||||
* and shall be discarded.
|
* and shall be discarded.
|
||||||
*/
|
*/
|
||||||
inline fun <reified T> attachObjectGraph(stable: COpaquePointer?): T =
|
public inline fun <reified T> DetachedObjectGraph<T>.attach(): T {
|
||||||
attachObjectGraphInternal(stable) as T
|
var rawStable: NativePtr
|
||||||
|
do {
|
||||||
// Private APIs.
|
rawStable = stable.value
|
||||||
@PublishedApi
|
} while (!stable.compareAndSet(rawStable, NativePtr.NULL))
|
||||||
@SymbolName("Kotlin_Worker_detachObjectGraphInternal")
|
val result = attachObjectGraphInternal(rawStable) as T
|
||||||
external internal fun detachObjectGraphInternal(mode: Int, producer: () -> Any?): COpaquePointer?
|
return result
|
||||||
|
}
|
||||||
@PublishedApi
|
|
||||||
@SymbolName("Kotlin_Worker_attachObjectGraphInternal")
|
|
||||||
external internal fun attachObjectGraphInternal(stable: COpaquePointer?): Any?
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
package kotlin.native.concurrent
|
package kotlin.native.concurrent
|
||||||
|
|
||||||
import kotlin.native.SymbolName
|
|
||||||
import kotlin.native.internal.ExportForCppRuntime
|
import kotlin.native.internal.ExportForCppRuntime
|
||||||
|
import kotlin.native.internal.Frozen
|
||||||
import kotlin.native.internal.VolatileLambda
|
import kotlin.native.internal.VolatileLambda
|
||||||
import kotlinx.cinterop.*
|
import kotlinx.cinterop.*
|
||||||
|
|
||||||
@@ -21,26 +21,29 @@ import kotlinx.cinterop.*
|
|||||||
* workers as needed.
|
* workers as needed.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* Unique identifier of the worker. Workers can be used from other workers.
|
|
||||||
*/
|
|
||||||
typealias WorkerId = Int
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class representing worker.
|
* Class representing worker.
|
||||||
*/
|
*/
|
||||||
// TODO: make me value class!
|
@Frozen
|
||||||
public class Worker(val id: WorkerId) {
|
public class Worker private constructor(val id: Int) {
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Start new scheduling primitive, such as thread, to accept new tasks via `execute` interface.
|
||||||
|
* Typically new worker may be needed for computations offload to another core, for IO it may be
|
||||||
|
* better to use non-blocking IO combined with more lightweight coroutines.
|
||||||
|
*/
|
||||||
|
public fun start(): Worker = Worker(startInternal())
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests termination of the worker. `processScheduledJobs` controls is we shall wait
|
* Requests termination of the worker. `processScheduledJobs` controls is we shall wait
|
||||||
* until all scheduled jobs processed, or terminate immediately.
|
* until all scheduled jobs processed, or terminate immediately.
|
||||||
*/
|
*/
|
||||||
public fun requestTermination(processScheduledJobs: Boolean = true) =
|
public fun requestTermination(processScheduledJobs: Boolean = true) =
|
||||||
Future<Nothing?>(requestTerminationInternal(id, processScheduledJobs))
|
Future<Unit>(requestTerminationInternal(id, processScheduledJobs))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedule a job for further execution in the worker. Schedule is a two-phase operation,
|
* Plan job for further execution in the worker. Execute is a two-phase operation,
|
||||||
* first `producer` function is executed, and resulting object and whatever it refers to
|
* first `producer` function is executed, and resulting object and whatever it refers to
|
||||||
* is analyzed for being an isolated object subgraph, if in checked mode.
|
* is analyzed for being an isolated object subgraph, if in checked mode.
|
||||||
* Afterwards, this disconnected object graph and `job` function pointer is being added to jobs queue
|
* Afterwards, this disconnected object graph and `job` function pointer is being added to jobs queue
|
||||||
@@ -50,10 +53,10 @@ public class Worker(val id: WorkerId) {
|
|||||||
* the future, can use result of worker's computations.
|
* the future, can use result of worker's computations.
|
||||||
*/
|
*/
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
public fun <T1, T2> schedule(mode: TransferMode, producer: () -> T1, @VolatileLambda job: (T1) -> T2): Future<T2> =
|
public fun <T1, T2> execute(mode: TransferMode, producer: () -> T1, @VolatileLambda job: (T1) -> T2): Future<T2> =
|
||||||
/**
|
/**
|
||||||
* This function is a magical operation, handled by lowering in the compiler, and replaced with call to
|
* This function is a magical operation, handled by lowering in the compiler, and replaced with call to
|
||||||
* scheduleImpl(worker, mode, producer, job)
|
* executeImpl(worker, mode, producer, job)
|
||||||
* but first ensuring that `job` parameter doesn't capture any state.
|
* but first ensuring that `job` parameter doesn't capture any state.
|
||||||
*/
|
*/
|
||||||
throw RuntimeException("Shall not be called directly")
|
throw RuntimeException("Shall not be called directly")
|
||||||
@@ -61,38 +64,6 @@ public class Worker(val id: WorkerId) {
|
|||||||
override public fun equals(other: Any?): Boolean = (other is Worker) && (id == other.id)
|
override public fun equals(other: Any?): Boolean = (other is Worker) && (id == other.id)
|
||||||
|
|
||||||
override public fun hashCode(): Int = id
|
override public fun hashCode(): Int = id
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
override public fun toString(): String = "worker $id"
|
||||||
* Start new scheduling primitive, such as thread, to accept new tasks via `schedule` interface.
|
}
|
||||||
* Typically new worker may be needed for computations offload to another core, for IO it may be
|
|
||||||
* better to use non-blocking IO combined with more lightweight coroutines.
|
|
||||||
*/
|
|
||||||
public fun startWorker(): Worker = Worker(startInternal())
|
|
||||||
|
|
||||||
// Private APIs.
|
|
||||||
@kotlin.native.internal.ExportForCompiler
|
|
||||||
internal fun scheduleImpl(worker: Worker, mode: TransferMode, producer: () -> Any?,
|
|
||||||
job: CPointer<CFunction<*>>): Future<Any?> =
|
|
||||||
Future<Any?>(scheduleInternal(worker.id, mode.value, producer, job))
|
|
||||||
|
|
||||||
@SymbolName("Kotlin_Worker_startInternal")
|
|
||||||
external internal fun startInternal(): WorkerId
|
|
||||||
|
|
||||||
@SymbolName("Kotlin_Worker_requestTerminationWorkerInternal")
|
|
||||||
external internal fun requestTerminationInternal(id: WorkerId, processScheduledJobs: Boolean): FutureId
|
|
||||||
|
|
||||||
@SymbolName("Kotlin_Worker_scheduleInternal")
|
|
||||||
external internal fun scheduleInternal(
|
|
||||||
id: WorkerId, mode: Int, producer: () -> Any?, job: CPointer<CFunction<*>>): FutureId
|
|
||||||
|
|
||||||
@ExportForCppRuntime
|
|
||||||
internal fun ThrowWorkerUnsupported(): Unit =
|
|
||||||
throw UnsupportedOperationException("Workers are not supported")
|
|
||||||
|
|
||||||
@ExportForCppRuntime
|
|
||||||
internal fun ThrowWorkerInvalidState(): Unit =
|
|
||||||
throw IllegalStateException("Illegal transfer state")
|
|
||||||
|
|
||||||
@ExportForCppRuntime
|
|
||||||
internal fun WorkerLaunchpad(function: () -> Any?) = function()
|
|
||||||
@@ -19,13 +19,13 @@ public abstract class NativeRandom {
|
|||||||
* Random generator seed value.
|
* Random generator seed value.
|
||||||
*/
|
*/
|
||||||
var seed: Long
|
var seed: Long
|
||||||
get() = _seed.get()
|
get() = _seed.value
|
||||||
set(value) = update(mult(value))
|
set(value) = update(mult(value))
|
||||||
|
|
||||||
private fun mult(value: Long) = (value xor MULTIPLIER) and ((1L shl 48) - 1)
|
private fun mult(value: Long) = (value xor MULTIPLIER) and ((1L shl 48) - 1)
|
||||||
|
|
||||||
private fun update(seed: Long) {
|
private fun update(seed: Long): Unit {
|
||||||
_seed.compareAndSwap(_seed.get(), seed)
|
_seed.value = seed
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -87,10 +87,10 @@ internal abstract class AbstractCharClass : SpecialToken() {
|
|||||||
get() = this
|
get() = this
|
||||||
|
|
||||||
|
|
||||||
private val surrogates_ = AtomicReference<AbstractCharClass>()
|
private val surrogates_ = AtomicReference<AbstractCharClass?>(null)
|
||||||
val surrogates: AbstractCharClass
|
val surrogates: AbstractCharClass
|
||||||
get() {
|
get() {
|
||||||
surrogates_.get()?.let {
|
surrogates_.value?.let {
|
||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
val result = object : AbstractCharClass() {
|
val result = object : AbstractCharClass() {
|
||||||
@@ -104,15 +104,15 @@ internal abstract class AbstractCharClass : SpecialToken() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.setNegative(this.altSurrogates)
|
result.setNegative(this.altSurrogates)
|
||||||
surrogates_.compareAndSwap(null, result.freeze())
|
surrogates_.compareAndSet(null, result.freeze())
|
||||||
return surrogates_.get()!!
|
return surrogates_.value!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private val withoutSurrogates_ = AtomicReference<AbstractCharClass>()
|
private val withoutSurrogates_ = AtomicReference<AbstractCharClass?>(null)
|
||||||
val withoutSurrogates: AbstractCharClass
|
val withoutSurrogates: AbstractCharClass
|
||||||
get() {
|
get() {
|
||||||
withoutSurrogates_.get()?.let {
|
withoutSurrogates_.value?.let {
|
||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
val result = object : AbstractCharClass() {
|
val result = object : AbstractCharClass() {
|
||||||
@@ -129,8 +129,8 @@ internal abstract class AbstractCharClass : SpecialToken() {
|
|||||||
}
|
}
|
||||||
result.setNegative(isNegative())
|
result.setNegative(isNegative())
|
||||||
result.mayContainSupplCodepoints = mayContainSupplCodepoints
|
result.mayContainSupplCodepoints = mayContainSupplCodepoints
|
||||||
withoutSurrogates_ .compareAndSwap(null, result.freeze())
|
withoutSurrogates_ .compareAndSet(null, result.freeze())
|
||||||
return withoutSurrogates_.get()!!
|
return withoutSurrogates_.value!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -561,8 +561,8 @@ internal abstract class AbstractCharClass : SpecialToken() {
|
|||||||
PF("Pf", { CachedCategory(CharCategory.FINAL_QUOTE_PUNCTUATION.value, false) })
|
PF("Pf", { CachedCategory(CharCategory.FINAL_QUOTE_PUNCTUATION.value, false) })
|
||||||
}
|
}
|
||||||
|
|
||||||
private val classCache = Array<AtomicReference<CachedCharClass>>(CharClasses.values().size, {
|
private val classCache = Array<AtomicReference<CachedCharClass?>>(CharClasses.values().size, {
|
||||||
AtomicReference<CachedCharClass>()
|
AtomicReference<CachedCharClass?>(null)
|
||||||
})
|
})
|
||||||
private val classCacheMap = CharClasses.values().associate { it -> it.regexName to it }
|
private val classCacheMap = CharClasses.values().associate { it -> it.regexName to it }
|
||||||
|
|
||||||
@@ -578,9 +578,9 @@ internal abstract class AbstractCharClass : SpecialToken() {
|
|||||||
|
|
||||||
fun getPredefinedClass(name: String, negative: Boolean): AbstractCharClass {
|
fun getPredefinedClass(name: String, negative: Boolean): AbstractCharClass {
|
||||||
val charClass = classCacheMap[name] ?: throw PatternSyntaxException("No such character class")
|
val charClass = classCacheMap[name] ?: throw PatternSyntaxException("No such character class")
|
||||||
val cachedClass = classCache[charClass.ordinal].get() ?: run {
|
val cachedClass = classCache[charClass.ordinal].value ?: run {
|
||||||
classCache[charClass.ordinal].compareAndSwap(null, charClass.factory().freeze())
|
classCache[charClass.ordinal].compareAndSwap(null, charClass.factory().freeze())
|
||||||
classCache[charClass.ordinal].get()!!
|
classCache[charClass.ordinal].value!!
|
||||||
}
|
}
|
||||||
return cachedClass.getValue(negative)
|
return cachedClass.getValue(negative)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ fun main(args: Array<String>) {
|
|||||||
sharedData.f = 0.5f
|
sharedData.f = 0.5f
|
||||||
sharedData.string = "Hello Kotlin!".cstr.getPointer(arena)
|
sharedData.string = "Hello Kotlin!".cstr.getPointer(arena)
|
||||||
// Here we create detached mutable object, which could be later reattached by another thread.
|
// Here we create detached mutable object, which could be later reattached by another thread.
|
||||||
sharedData.kotlinObject = detachObjectGraph {
|
sharedData.kotlinObject = DetachedObjectGraph {
|
||||||
SharedData("A string", 42, SharedDataMember(2.39))
|
SharedData("A string", 42, SharedDataMember(2.39))
|
||||||
}
|
}.asCPointer()
|
||||||
// Here we create shared frozen object reference,
|
// Here we create shared frozen object reference,
|
||||||
val stableRef = StableRef.create(SharedData("Shared", 239, SharedDataMember(2.71)).freeze())
|
val stableRef = StableRef.create(SharedData("Shared", 239, SharedDataMember(2.71)).freeze())
|
||||||
sharedData.frozenKotlinObject = stableRef.asCPointer()
|
sharedData.frozenKotlinObject = stableRef.asCPointer()
|
||||||
@@ -67,12 +67,12 @@ fun main(args: Array<String>) {
|
|||||||
argC ->
|
argC ->
|
||||||
initRuntimeIfNeeded()
|
initRuntimeIfNeeded()
|
||||||
dumpShared("thread2")
|
dumpShared("thread2")
|
||||||
val kotlinObject = attachObjectGraph<SharedData>(sharedData.kotlinObject)
|
val kotlinObject = DetachedObjectGraph<SharedData>(sharedData.kotlinObject).attach()
|
||||||
val arg = attachObjectGraph<SharedDataMember>(argC)
|
val arg = DetachedObjectGraph<SharedDataMember>(argC).attach()
|
||||||
println("thread arg is $arg Kotlin object is $kotlinObject frozen is $globalObject")
|
println("thread arg is $arg Kotlin object is $kotlinObject frozen is $globalObject")
|
||||||
// Workaround for compiler issue.
|
// Workaround for compiler issue.
|
||||||
null as COpaquePointer?
|
null as COpaquePointer?
|
||||||
}, detachObjectGraph { SharedDataMember(3.14)} ).ensureUnixCallResult("pthread_create")
|
}, DetachedObjectGraph { SharedDataMember(3.14)}.asCPointer() ).ensureUnixCallResult("pthread_create")
|
||||||
pthread_join(thread.value, null).ensureUnixCallResult("pthread_join")
|
pthread_join(thread.value, null).ensureUnixCallResult("pthread_join")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import kotlin.native.concurrent.attachObjectGraph
|
import kotlin.native.concurrent.*
|
||||||
import kotlin.native.concurrent.detachObjectGraph
|
|
||||||
import kotlinx.cinterop.*
|
import kotlinx.cinterop.*
|
||||||
import platform.AppKit.*
|
import platform.AppKit.*
|
||||||
import platform.Foundation.*
|
import platform.Foundation.*
|
||||||
@@ -32,12 +31,12 @@ private class Controller : NSObject() {
|
|||||||
@ObjCAction
|
@ObjCAction
|
||||||
fun onClick() {
|
fun onClick() {
|
||||||
// Execute some async action on button click.
|
// Execute some async action on button click.
|
||||||
dispatch_async_f(asyncQueue, detachObjectGraph {
|
dispatch_async_f(asyncQueue, DetachedObjectGraph {
|
||||||
Data(clock_gettime_nsec_np(CLOCK_REALTIME.convert()))
|
Data(clock_gettime_nsec_np(CLOCK_REALTIME.convert()))
|
||||||
}, staticCFunction {
|
}.asCPointer(), staticCFunction {
|
||||||
it ->
|
it ->
|
||||||
initRuntimeIfNeeded()
|
initRuntimeIfNeeded()
|
||||||
val data = attachObjectGraph<Data>(it)
|
val data = DetachedObjectGraph<Data>(it).attach()
|
||||||
println("in async: $data")
|
println("in async: $data")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -325,19 +325,14 @@ private class Decoder(
|
|||||||
fun audioVideoSynced() = (audio?.isSynced() ?: true) || done()
|
fun audioVideoSynced() = (audio?.isSynced() ?: true) || done()
|
||||||
}
|
}
|
||||||
|
|
||||||
class DecoderWorker : Disposable {
|
class DecoderWorker(val worker: Worker) : Disposable {
|
||||||
// This class must have no other state, but this worker object.
|
// This class must have no other state, but this worker object.
|
||||||
// All the real state must be stored on the worker's side.
|
// All the real state must be stored on the worker's side.
|
||||||
private val worker: Worker
|
constructor() : this(Worker.start())
|
||||||
|
|
||||||
constructor() { worker = startWorker() }
|
|
||||||
constructor(id: WorkerId) { worker = Worker(id) }
|
|
||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
worker.requestTermination().result()
|
worker.requestTermination().result
|
||||||
}
|
}
|
||||||
|
|
||||||
val workerId get() = worker.id
|
|
||||||
|
|
||||||
fun initDecode(context: AVFormatContext, useVideo: Boolean = true, useAudio: Boolean = true): CodecInfo {
|
fun initDecode(context: AVFormatContext, useVideo: Boolean = true, useAudio: Boolean = true): CodecInfo {
|
||||||
// Find the first video/audio streams.
|
// Find the first video/audio streams.
|
||||||
@@ -362,7 +357,7 @@ class DecoderWorker : Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pack all state and pass it to the worker.
|
// Pack all state and pass it to the worker.
|
||||||
worker.schedule(TransferMode.CHECKED, {
|
worker.execute(TransferMode.SAFE, {
|
||||||
Decoder(context.ptr,
|
Decoder(context.ptr,
|
||||||
videoStreamIndex, audioStreamIndex,
|
videoStreamIndex, audioStreamIndex,
|
||||||
videoContext, audioContext)
|
videoContext, audioContext)
|
||||||
@@ -371,7 +366,7 @@ class DecoderWorker : Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun start(videoOutput: VideoOutput, audioOutput: AudioOutput) {
|
fun start(videoOutput: VideoOutput, audioOutput: AudioOutput) {
|
||||||
worker.schedule(TransferMode.CHECKED,
|
worker.execute(TransferMode.SAFE,
|
||||||
{ Pair(
|
{ Pair(
|
||||||
videoOutput.toVideoDecoderOutput(),
|
videoOutput.toVideoDecoderOutput(),
|
||||||
audioOutput.toAudioDecoderOutput())
|
audioOutput.toAudioDecoderOutput())
|
||||||
@@ -381,27 +376,26 @@ class DecoderWorker : Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
worker.schedule(TransferMode.CHECKED, { null }) {
|
worker.execute(TransferMode.SAFE, { null }) {
|
||||||
decoder?.run {
|
decoder?.run {
|
||||||
dispose()
|
dispose()
|
||||||
decoder = null
|
decoder = null
|
||||||
}
|
}
|
||||||
}.result()
|
}.result
|
||||||
}
|
}
|
||||||
|
|
||||||
fun done(): Boolean =
|
fun done(): Boolean =
|
||||||
worker.schedule(TransferMode.CHECKED, { null }) { decoder?.done() ?: true }.result()
|
worker.execute(TransferMode.SAFE, { null }) { decoder?.done() ?: true }.result
|
||||||
|
|
||||||
fun requestDecodeChunk() {
|
fun requestDecodeChunk() =
|
||||||
worker.schedule(TransferMode.CHECKED, { null }) { decoder?.decodeIfNeeded() }.result()
|
worker.execute(TransferMode.SAFE, { null }) { decoder?.decodeIfNeeded() }.result
|
||||||
}
|
|
||||||
|
|
||||||
fun nextVideoFrame(): VideoFrame? =
|
fun nextVideoFrame(): VideoFrame? =
|
||||||
worker.schedule(TransferMode.CHECKED, { null }) { decoder?.nextVideoFrame() }.result()
|
worker.execute(TransferMode.SAFE, { null }) { decoder?.nextVideoFrame() }.result
|
||||||
|
|
||||||
fun nextAudioFrame(size: Int): AudioFrame? =
|
fun nextAudioFrame(size: Int): AudioFrame? =
|
||||||
worker.schedule(TransferMode.CHECKED, { size }) { decoder?.nextAudioFrame(it) }.result()
|
worker.execute(TransferMode.SAFE, { size }) { decoder?.nextAudioFrame(it) }.result
|
||||||
|
|
||||||
fun audioVideoSynced(): Boolean =
|
fun audioVideoSynced(): Boolean =
|
||||||
worker.schedule(TransferMode.CHECKED, { null }) { decoder?.audioVideoSynced() ?: true }.result()
|
worker.execute(TransferMode.SAFE, { null }) { decoder?.audioVideoSynced() ?: true }.result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import kotlin.native.concurrent.Worker
|
||||||
import kotlinx.cinterop.*
|
import kotlinx.cinterop.*
|
||||||
import sdl.*
|
import sdl.*
|
||||||
import platform.posix.memset
|
import platform.posix.memset
|
||||||
@@ -32,7 +33,7 @@ private fun SampleFormat.toSDLFormat(): SDL_AudioFormat? = when (this) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SDLAudio(val player: VideoPlayer) : DisposableContainer() {
|
class SDLAudio(val player: VideoPlayer) : DisposableContainer() {
|
||||||
private val threadData = arena.alloc<IntVar>().ptr
|
private val workerStable = StableRef.create(player.worker)
|
||||||
private var state = State.STOPPED
|
private var state = State.STOPPED
|
||||||
|
|
||||||
fun start(audio: AudioOutput) {
|
fun start(audio: AudioOutput) {
|
||||||
@@ -48,10 +49,9 @@ class SDLAudio(val player: VideoPlayer) : DisposableContainer() {
|
|||||||
channels = audio.channels.convert()
|
channels = audio.channels.convert()
|
||||||
silence = 0u
|
silence = 0u
|
||||||
samples = 4096u
|
samples = 4096u
|
||||||
userdata = threadData
|
userdata = workerStable.asCPointer()
|
||||||
callback = staticCFunction(::audioCallback)
|
callback = staticCFunction(::audioCallback)
|
||||||
}
|
}
|
||||||
threadData.pointed.value = player.workerId
|
|
||||||
val realSpec = alloc<SDL_AudioSpec>()
|
val realSpec = alloc<SDL_AudioSpec>()
|
||||||
if (SDL_OpenAudio(spec.ptr, realSpec.ptr) < 0)
|
if (SDL_OpenAudio(spec.ptr, realSpec.ptr) < 0)
|
||||||
throwSDLError("SDL_OpenAudio")
|
throwSDLError("SDL_OpenAudio")
|
||||||
@@ -72,6 +72,7 @@ class SDLAudio(val player: VideoPlayer) : DisposableContainer() {
|
|||||||
fun stop() {
|
fun stop() {
|
||||||
pause()
|
pause()
|
||||||
state = state.transition(State.PAUSED, State.STOPPED) { SDL_CloseAudio() }
|
state = state.transition(State.PAUSED, State.STOPPED) { SDL_CloseAudio() }
|
||||||
|
workerStable.dispose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +84,7 @@ private fun audioCallback(userdata: COpaquePointer?, buffer: CPointer<Uint8Var>?
|
|||||||
// This handler will be invoked in the audio thread, so reinit runtime.
|
// This handler will be invoked in the audio thread, so reinit runtime.
|
||||||
kotlin.native.initRuntimeIfNeeded()
|
kotlin.native.initRuntimeIfNeeded()
|
||||||
val decoder = decoder ?:
|
val decoder = decoder ?:
|
||||||
DecoderWorker(userdata!!.reinterpret<IntVar>().pointed.value).also { decoder = it }
|
DecoderWorker(userdata!!.asStableRef<Worker>().get()).also { decoder = it }
|
||||||
var outPosition = 0
|
var outPosition = 0
|
||||||
while (outPosition < length) {
|
while (outPosition < length) {
|
||||||
val frame = decoder.nextAudioFrame(length - outPosition)
|
val frame = decoder.nextAudioFrame(length - outPosition)
|
||||||
|
|||||||
@@ -41,15 +41,15 @@ enum class PlayMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class VideoPlayer(val requestedSize: Dimensions?) : DisposableContainer() {
|
class VideoPlayer(val requestedSize: Dimensions?) : DisposableContainer() {
|
||||||
|
private val decoder = disposable { DecoderWorker() }
|
||||||
private val video = disposable { SDLVideo() }
|
private val video = disposable { SDLVideo() }
|
||||||
private val audio = disposable { SDLAudio(this) }
|
private val audio = disposable { SDLAudio(this) }
|
||||||
private val input = disposable { SDLInput(this) }
|
private val input = disposable { SDLInput(this) }
|
||||||
private val decoder = disposable { DecoderWorker() }
|
|
||||||
private val now = arena.alloc<platform.posix.timespec>().ptr
|
private val now = arena.alloc<platform.posix.timespec>().ptr
|
||||||
|
|
||||||
private var state = State.STOPPED
|
private var state = State.STOPPED
|
||||||
|
|
||||||
val workerId get() = decoder.workerId
|
val worker get() = decoder.worker
|
||||||
var lastFrameTime = 0.0
|
var lastFrameTime = 0.0
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
|
|||||||
@@ -9,15 +9,13 @@ and connected to other worker. This relies on fact that memory management
|
|||||||
engine can ensure, that one worker doesn't keep references to certain object and
|
engine can ensure, that one worker doesn't keep references to certain object and
|
||||||
whatever it refers to, and so the object could be safely transferred to another worker.
|
whatever it refers to, and so the object could be safely transferred to another worker.
|
||||||
|
|
||||||
Workers do not share any state (i.e. globals and Kotlin static objects have different
|
Workers do not share mutable state, but share executable code of the program and some
|
||||||
values in different workers), but share executable code of the program and some
|
immutable data, such as immutable blobs. But Kotlin objects can be transferred
|
||||||
immutable data, such as immutable binary blobs. But Kotlin objects can be transferred
|
|
||||||
between workers, as long, as they do not refer to objects, having external references.
|
between workers, as long, as they do not refer to objects, having external references.
|
||||||
|
|
||||||
The transfer is implemented with the function `schedule()` having the following signature
|
The transfer is implemented with the function `execute()` having the following signature
|
||||||
|
|
||||||
fun <T1, T2>
|
fun <T1, T2> execute(mode: TransferMode,
|
||||||
schedule(mode: TransferMode,
|
|
||||||
producer: () -> T1,
|
producer: () -> T1,
|
||||||
@VolatileLambda job: (T1) -> T2): Future<T2>
|
@VolatileLambda job: (T1) -> T2): Future<T2>
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ data class WorkerResult(val intResult: Int, val stringResult: String)
|
|||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
val COUNT = 5
|
val COUNT = 5
|
||||||
val workers = Array(COUNT, { _ -> startWorker()})
|
val workers = Array(COUNT, { _ -> Worker.start()})
|
||||||
|
|
||||||
for (attempt in 1 .. 3) {
|
for (attempt in 1 .. 3) {
|
||||||
val futures = Array(workers.size, { workerIndex -> workers[workerIndex].schedule(TransferMode.CHECKED, {
|
val futures = Array(workers.size, { workerIndex -> workers[workerIndex].execute(TransferMode.SAFE, {
|
||||||
WorkerArgument(workerIndex, "attempt $attempt") }) { input ->
|
WorkerArgument(workerIndex, "attempt $attempt") }) { input ->
|
||||||
var sum = 0
|
var sum = 0
|
||||||
for (i in 0..input.intParam * 1000) {
|
for (i in 0..input.intParam * 1000) {
|
||||||
@@ -24,12 +24,13 @@ fun main(args: Array<String>) {
|
|||||||
ready.forEach {
|
ready.forEach {
|
||||||
it.consume { result ->
|
it.consume { result ->
|
||||||
if (result.stringResult != "attempt $attempt result") throw Error("Unexpected $result")
|
if (result.stringResult != "attempt $attempt result") throw Error("Unexpected $result")
|
||||||
consumed++ }
|
consumed++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
workers.forEach {
|
workers.forEach {
|
||||||
it.requestTermination().consume { _ -> }
|
it.requestTermination().result
|
||||||
}
|
}
|
||||||
println("OK")
|
println("OK")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user