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
@@ -50,9 +50,9 @@ fun main(args: Array<String>) {
sharedData.f = 0.5f
sharedData.string = "Hello Kotlin!".cstr.getPointer(arena)
// 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))
}
}.asCPointer()
// Here we create shared frozen object reference,
val stableRef = StableRef.create(SharedData("Shared", 239, SharedDataMember(2.71)).freeze())
sharedData.frozenKotlinObject = stableRef.asCPointer()
@@ -67,12 +67,12 @@ fun main(args: Array<String>) {
argC ->
initRuntimeIfNeeded()
dumpShared("thread2")
val kotlinObject = attachObjectGraph<SharedData>(sharedData.kotlinObject)
val arg = attachObjectGraph<SharedDataMember>(argC)
val kotlinObject = DetachedObjectGraph<SharedData>(sharedData.kotlinObject).attach()
val arg = DetachedObjectGraph<SharedDataMember>(argC).attach()
println("thread arg is $arg Kotlin object is $kotlinObject frozen is $globalObject")
// Workaround for compiler issue.
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")
}
+4 -5
View File
@@ -1,5 +1,4 @@
import kotlin.native.concurrent.attachObjectGraph
import kotlin.native.concurrent.detachObjectGraph
import kotlin.native.concurrent.*
import kotlinx.cinterop.*
import platform.AppKit.*
import platform.Foundation.*
@@ -32,12 +31,12 @@ private class Controller : NSObject() {
@ObjCAction
fun onClick() {
// 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()))
}, staticCFunction {
}.asCPointer(), staticCFunction {
it ->
initRuntimeIfNeeded()
val data = attachObjectGraph<Data>(it)
val data = DetachedObjectGraph<Data>(it).attach()
println("in async: $data")
})
}
@@ -325,19 +325,14 @@ private class Decoder(
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.
// All the real state must be stored on the worker's side.
private val worker: Worker
constructor() { worker = startWorker() }
constructor(id: WorkerId) { worker = Worker(id) }
constructor() : this(Worker.start())
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 {
// Find the first video/audio streams.
@@ -362,7 +357,7 @@ class DecoderWorker : Disposable {
}
// Pack all state and pass it to the worker.
worker.schedule(TransferMode.CHECKED, {
worker.execute(TransferMode.SAFE, {
Decoder(context.ptr,
videoStreamIndex, audioStreamIndex,
videoContext, audioContext)
@@ -371,7 +366,7 @@ class DecoderWorker : Disposable {
}
fun start(videoOutput: VideoOutput, audioOutput: AudioOutput) {
worker.schedule(TransferMode.CHECKED,
worker.execute(TransferMode.SAFE,
{ Pair(
videoOutput.toVideoDecoderOutput(),
audioOutput.toAudioDecoderOutput())
@@ -381,27 +376,26 @@ class DecoderWorker : Disposable {
}
fun stop() {
worker.schedule(TransferMode.CHECKED, { null }) {
worker.execute(TransferMode.SAFE, { null }) {
decoder?.run {
dispose()
decoder = null
}
}.result()
}.result
}
fun done(): Boolean =
worker.schedule(TransferMode.CHECKED, { null }) { decoder?.done() ?: true }.result()
worker.execute(TransferMode.SAFE, { null }) { decoder?.done() ?: true }.result
fun requestDecodeChunk() {
worker.schedule(TransferMode.CHECKED, { null }) { decoder?.decodeIfNeeded() }.result()
}
fun requestDecodeChunk() =
worker.execute(TransferMode.SAFE, { null }) { decoder?.decodeIfNeeded() }.result
fun nextVideoFrame(): VideoFrame? =
worker.schedule(TransferMode.CHECKED, { null }) { decoder?.nextVideoFrame() }.result()
worker.execute(TransferMode.SAFE, { null }) { decoder?.nextVideoFrame() }.result
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 =
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.
*/
import kotlin.native.concurrent.Worker
import kotlinx.cinterop.*
import sdl.*
import platform.posix.memset
@@ -32,7 +33,7 @@ private fun SampleFormat.toSDLFormat(): SDL_AudioFormat? = when (this) {
}
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
fun start(audio: AudioOutput) {
@@ -48,10 +49,9 @@ class SDLAudio(val player: VideoPlayer) : DisposableContainer() {
channels = audio.channels.convert()
silence = 0u
samples = 4096u
userdata = threadData
userdata = workerStable.asCPointer()
callback = staticCFunction(::audioCallback)
}
threadData.pointed.value = player.workerId
val realSpec = alloc<SDL_AudioSpec>()
if (SDL_OpenAudio(spec.ptr, realSpec.ptr) < 0)
throwSDLError("SDL_OpenAudio")
@@ -72,6 +72,7 @@ class SDLAudio(val player: VideoPlayer) : DisposableContainer() {
fun stop() {
pause()
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.
kotlin.native.initRuntimeIfNeeded()
val decoder = decoder ?:
DecoderWorker(userdata!!.reinterpret<IntVar>().pointed.value).also { decoder = it }
DecoderWorker(userdata!!.asStableRef<Worker>().get()).also { decoder = it }
var outPosition = 0
while (outPosition < length) {
val frame = decoder.nextAudioFrame(length - outPosition)
@@ -41,15 +41,15 @@ enum class PlayMode {
}
class VideoPlayer(val requestedSize: Dimensions?) : DisposableContainer() {
private val decoder = disposable { DecoderWorker() }
private val video = disposable { SDLVideo() }
private val audio = disposable { SDLAudio(this) }
private val input = disposable { SDLInput(this) }
private val decoder = disposable { DecoderWorker() }
private val now = arena.alloc<platform.posix.timespec>().ptr
private var state = State.STOPPED
val workerId get() = decoder.workerId
val worker get() = decoder.worker
var lastFrameTime = 0.0
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
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
values in different workers), but share executable code of the program and some
immutable data, such as immutable binary blobs. But Kotlin objects can be transferred
Workers do not share mutable state, but share executable code of the program and some
immutable data, such as immutable blobs. But Kotlin objects can be transferred
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>
schedule(mode: TransferMode,
fun <T1, T2> execute(mode: TransferMode,
producer: () -> T1,
@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>) {
val COUNT = 5
val workers = Array(COUNT, { _ -> startWorker()})
val workers = Array(COUNT, { _ -> Worker.start()})
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 ->
var sum = 0
for (i in 0..input.intParam * 1000) {
@@ -24,12 +24,13 @@ fun main(args: Array<String>) {
ready.forEach {
it.consume { result ->
if (result.stringResult != "attempt $attempt result") throw Error("Unexpected $result")
consumed++ }
consumed++
}
}
}
}
workers.forEach {
it.requestTermination().consume { _ -> }
it.requestTermination().result
}
println("OK")
}