New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multi-threaded coroutines on Kotlin/Native #462

Open
elizarov opened this Issue Jul 27, 2018 · 28 comments

Comments

Projects
None yet
@elizarov
Copy link
Collaborator

elizarov commented Jul 27, 2018

You can have multiple threads in Kotlin/Native. Each thread can have its own event loop with runBlocking and have number of coroutines running there. Currently communication between those threads via coroutine primitives (like channels) is not supported. This issue it to track enhancement of Kotlin/Native in kotlinx.coroutines library so that all the following becomes possible:

  • Launching coroutines from one thread with a dispatcher on another thread
  • Await/join coroutine running on another thread
  • Send/Receive elements to/from coroutines on other threads

UPDATE: Currently, coroutines are supported only on the main thread. You cannot have coroutines off the main thread due to the way the library is currently structured.

@brettwillis

This comment has been minimized.

Copy link

brettwillis commented Aug 1, 2018

Do you have a ballpark time frame for implementing this (days, weeks, months, ...)? This will help me plan how to implement the first revision of our project. Thanks!

@mohit-gurumukhani

This comment has been minimized.

Copy link

mohit-gurumukhani commented Aug 3, 2018

Second that. Can we please get a rough estimate?

@elizarov

This comment has been minimized.

Copy link
Collaborator

elizarov commented Aug 3, 2018

We're in the design phase now. I'll update you on the status in couple of weeks.

@Alex009

This comment has been minimized.

Copy link

Alex009 commented Oct 11, 2018

Have any progress?

@elizarov

This comment has been minimized.

Copy link
Collaborator

elizarov commented Oct 11, 2018

We have a work-in-progress branch in this repo with some of the code that is implemented, but it is way too complex a change, so the work there was stopped. It is hard to get it done in the current state. We've refocused our efforts on delivering high-quality single-threaded coroutines which work really well for sharing logic between Android and iOS UI apps (I highly recommend to checkout the code of KotlinConf app here https://github.com/JetBrains/kotlinconf-app). With respect to multithreading, we'll be back to drawing board to see how this story can be made easier to code with. Don't expect results soon, though.

@LouisCAD

This comment has been minimized.

Copy link
Contributor

LouisCAD commented Oct 11, 2018

@elizarov

This comment has been minimized.

Copy link
Collaborator

elizarov commented Oct 12, 2018

Yes, it works without runBlocking. The only extra effort you have to make, is you have to write a trivial UI CoroutineDispatcher for iOS. We don't include it in the library yet (that's issue #470), but you can copy-and-paste code from KotlinConf app (swift version here https://github.com/JetBrains/kotlinconf-app/blob/master/konfios/konfswift/ui/UI.swift) of from discussion in #470 (Kotlin version here #470 (comment))

@luca992

This comment has been minimized.

Copy link

luca992 commented Oct 16, 2018

@elizarov Trying to convert Waiting for a job example to work without run blocking using (#470 (comment)) in a native macOs program. But I am still getting There is no event loop. Use runBlocking { ... } to start one. I think it probably is because my native program isn't starting NSRunLoop's mainloop. I can't quite figure it out.

I've tried starting the loop like:

fun main(args: Array<String>) {
    val job = GlobalScope.launch(MainLoopDispatcher) { // launch new coroutine and keep a reference to its Job
        delay(1000L)
        println("World!")
    }
    println("Hello,")
    NSRunLoop.currentRunLoop().runUntilDate(NSDate().dateByAddingTimeInterval(3.toDouble()))
}

But I don't think I'm doing that correctly, any ideas?

@qwwdfsad

This comment has been minimized.

Copy link
Member

qwwdfsad commented Oct 16, 2018

@luca992 please use runBlocking:

fun main(args: Array<String>) = runBlocking<Unit> { // <- This line
    val job = GlobalScope.launch(MainLoopDispatcher) {
        delay(1000L)
        println("World!")
    }
    println("Hello,")
}
@luca992

This comment has been minimized.

Copy link

luca992 commented Oct 16, 2018

@qwwdfsad I know that it works with run blocking... Are you saying it is only possible to run without runBlocking on iOS for some reason?

Edit:
I'm using Qt for the UI in my native kotlin desktop app. Didn't figure out NSRunLoop. But, I figured out that if I run QGuiApplication.exec() inside runBlocking, I can call coroutines with Dispatchers.Unconfined. (And not have to wrap each one in runBlocking) .... Which is great beacuse now I can share presenters between the android app and the native desktop apps 👍

@LanderlYoung

This comment has been minimized.

Copy link

LanderlYoung commented Nov 15, 2018

Is there any solution to support MultiThreaded coroutines yet?
As far as I know, under current Kotlin/Native threading model, if an object is going to be shared between workers/threads, it must be either frozen or use a DetachedObjectGraph, while none of which works with CouroutineDispatcher, because we have a Continuation to pass through. Sadly the Continuation captures coroutine context (maybe more othre objects), which makes it impossible to froze or detach a Continuation.

IMHO, It's nearly impossible to implement a multi-threading coroutine dispatcher under current Kotlin/Native threading model. Should we redesign the threading model?

Maybe it is good for writing rebust code that, Kotlin/Native implement Lock/Thread/ThreadPool, and use those tools to implement coroutine. For those just want to offload jobs to different thread, it is good enough to use coroutine. And for those who cares very much about performace, give them the ability to use raw thread/thread pool. For example, to write a high performance low latency audio app, one usually create threads with the hieghest scheduling proiorty, and event bind those threads to a certain cpu core to eliminate context switch.

@LanderlYoung

This comment has been minimized.

Copy link

LanderlYoung commented Nov 15, 2018

Current my solution is to totally move the threading part into native ios code. like this.

private val requestingHashMap = hashMapOf<String, IosHttpGetAgent.Callback>()

fun notifyHttpGetResponse(url: String, result: String?, error: String) {
    requestingHashMap.remove(url)?.onGetResult(url, result, error)
}

@Throws(IOException::class)
actual suspend fun httpGet(url: String): String {
    return suspendCoroutine { continuation ->
        val cb = object : IosHttpGetAgent.Callback {
            override fun onGetResult(url: String, result: String?, error: String) {
                if (result != null) {
                    continuation.resume(result)
                } else {
                    continuation.resumeWith(Result.failure(IOException(error)))
                }
            }
        }
        requestingHashMap[url] = cb
        iosHttpGetAgent.value!!.httpGet(url)
    }
}

While on the swift code.

    func httpGet(url: String) {
        
        let task = URLSession.shared.dataTask(with: URL(string: url)!) { (data, response, error) in
            if let resultData = data {
                DispatchQueue.main.async {
                    ActualKt.notifyHttpGetResponse(
                        url:url,
                        result: String(data: resultData, encoding: .utf8)!,
                        error: "success")
                }
            } else {
                DispatchQueue.main.async {
                    ActualKt.notifyHttpGetResponse(
                        url:url,
                        result: nil,
                        error: "success")
                }
            }
        }
        task.resume()
        
    }

So kotlin/native code runs totally on the main thread.

@elizarov

This comment has been minimized.

Copy link
Collaborator

elizarov commented Nov 15, 2018

Running totally on the main is the only solution for now. You can track #829 which will slightly expand your options and you'll be able to run coroutines separately on each threads (no easy way to communicate, though).

@LanderlYoung

This comment has been minimized.

Copy link

LanderlYoung commented Dec 17, 2018

Hi, any progress on this issue? Or any possible solution for this issue? @elizarov
I'll be glad to know about this. thanks.

@horita-yuya

This comment has been minimized.

Copy link

horita-yuya commented Dec 25, 2018

I'm also very concerned about this.

@SeekDaSky

This comment has been minimized.

Copy link

SeekDaSky commented Dec 25, 2018

Well, the current state of multithreading in K/N is not really suitable for coroutines, the simple fact of giving a Continuation to a worker freeze the continuation, thus freezing the captured state and making it immutable (and pretty much unsuable).

For me multithreded coroutines is simply impossible with the current model.

@sellmair

This comment has been minimized.

Copy link

sellmair commented Dec 25, 2018

@SeekDaSky I am not very experienced with K/N's concurrency model nor with the way, coroutines work under the hood, but I would also want to see support for multi-threaded coroutines in K/N.
Isn't there a way to make the continuation an immutable object that passes a new copy with the new state to the next K/N worker?

@SeekDaSky

This comment has been minimized.

Copy link

SeekDaSky commented Dec 25, 2018

If I understand correctly you could detach the continuation and keep it mutable, but the state would not be accessible from another worker, so we still can't share values between threads.

We could heavily use channels and the actor paradigm to avoid any variable being shared, but this could lead to some performance degradation.

And this is just the developper side, developing a dispatcher with the limitations of the current concurrency model probably is daunting.

@sellmair

This comment has been minimized.

Copy link

sellmair commented Dec 25, 2018

No, this might be pretty naive, but wouldn't it be possible to capture the whole state of the coroutine in some structure (let's say a data class) and just pass a new copy to the next worker?

@SeekDaSky

This comment has been minimized.

Copy link

SeekDaSky commented Dec 25, 2018

To achieve this I think you just have to detach the continuation and re-attach it inside the next worker but you still can't have two threads accessing the data at the same time. And this could lead to some weird side effect if you share a value between two continuations by mistake

@sellmair

This comment has been minimized.

Copy link

sellmair commented Dec 25, 2018

And this could lead to some weird side effect if you share a value between two continuations by mistake

Would you mind explaining this a little? I would be super interested ☺️

@SeekDaSky

This comment has been minimized.

Copy link

SeekDaSky commented Dec 25, 2018

Well if you have a value that get caught in two distinct continuation, for example if you have
two suspensions in two different methods that use the same property of an object, when the first continuation is detached, the second one will fail (because the shared value is now detached) and that would be a real pain to debug.
And I don't know what would happen if a value is detached, re-attach in a worker and then detached from another one, there is so much corner cases that could lead to unexpected behaviour.

@sellmair

This comment has been minimized.

Copy link

sellmair commented Dec 25, 2018

Ah yeah, I got that, thanks! Obviously, the whole situation is way more complex than 'just expressing the state machine as an immutable structure', since you are able to access stuff outside of the scope of the suspending function itself.
So while I really appreciate the work and ideas that went into K/N's concurrency model, I would rather prefer some concepts that coroutines bring to the table. For me personally (and most likely for our team), it would be much easier to work with concepts like Actors in our common source than handling the differences between the JVM and K/N. The new concurrency model (as nice as it might be) seems counter-productive towards multiplatform to me 😞

@qwwdfsad qwwdfsad added the postponed label Dec 26, 2018

@qwwdfsad

This comment has been minimized.

Copy link
Member

qwwdfsad commented Dec 26, 2018

Your concerns about K/N memory model are valid (and pretty precise). In the current memory model, it is almost impossible to introduce multithreaded coroutines (at least implement already introduced common part to work with shared memory) that share the same semantics as JS and JVM primitives.

Yes it is possible to implement a separate library with detach/freeze semantics, actors and channels, but its API surface will be far from perfect and its impossible to write a common code with such primitives without constant fear of accidental freezings, detaches and in a way that some parts of the K/N-specific API do not leak into common and JVM code, so this is not something we are going to do.

But there is another way to address this problem: changes in K/N memory model. I am not going to discuss these changes (yet), but this is what we are aiming to. After these changes, we will implement a proper multithreaded K/N part of kotlinx.coroutines

@bwalter

This comment has been minimized.

Copy link

bwalter commented Dec 26, 2018

Thanks for the update @qwwdfsad, it seems to be a very great news! While the immutability check makes sense for a language like Rust where transfer rules are in the core of the language, it is very confusing in the case of K/N.

Looking forward to hearing about the memory model change!

@ildarsharafutdinov

This comment has been minimized.

Copy link

ildarsharafutdinov commented Dec 27, 2018

@qwwdfsad ,

changes in K/N memory model ... we are aiming to ... multithreaded K/N part of kotlinx.coroutines

Is there an ETA?

@qwwdfsad

This comment has been minimized.

Copy link
Member

qwwdfsad commented Jan 15, 2019

@ildarsharafutdinov no, there is no public ETA beyond "we will see a prototype in this year"

@SeekDaSky

This comment has been minimized.

Copy link

SeekDaSky commented Jan 15, 2019

well that's a kind of ETA, is there a discussion/KEEP/forum post/whatever about the upcoming changes in K/N memory model ? I saw some movement in the Worker API but nothing that would improve the current situation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment