Concurrent API update. (#1949)

This commit is contained in:
Nikolay Igotti
2018-08-29 10:20:07 +03:00
committed by GitHub
parent 63cf6777b1
commit f47fe79ef8
42 changed files with 587 additions and 417 deletions
+8 -9
View File
@@ -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.
@@ -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()
@@ -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)
@@ -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
@@ -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))
@@ -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
} }
} }
+27 -27
View File
@@ -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")
} }
+10 -10
View File
@@ -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")
} }
+76 -6
View File
@@ -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);
+26
View File
@@ -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
+1 -1
View File
@@ -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")
} }
+4 -5
View File
@@ -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() {
+4 -6
View File
@@ -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 -4
View File
@@ -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")
} }