The Wayback Machine - https://web.archive.org/web/20201127150515/https://github.com/Kotlin/kotlinx.coroutines/issues/462
Skip to content
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 · 139 comments
Open

Support multi-threaded coroutines on Kotlin/Native #462

elizarov opened this issue Jul 27, 2018 · 139 comments

Comments

@elizarov
Copy link
Member

@elizarov 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
Copy link

@brettwillis 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
Copy link

@mohit-gurumukhani mohit-gurumukhani commented Aug 3, 2018

Second that. Can we please get a rough estimate?

@elizarov
Copy link
Member Author

@elizarov elizarov commented Aug 3, 2018

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

@Alex009
Copy link

@Alex009 Alex009 commented Oct 11, 2018

Have any progress?

@elizarov
Copy link
Member Author

@elizarov 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
Copy link
Contributor

@LouisCAD LouisCAD commented Oct 11, 2018

@elizarov
Copy link
Member Author

@elizarov 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
Copy link

@luca992 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
Copy link
Member

@qwwdfsad 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
Copy link

@luca992 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
Copy link

@LanderlYoung 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
Copy link

@LanderlYoung 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
Copy link
Member Author

@elizarov 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
Copy link

@LanderlYoung 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
Copy link

@horita-yuya horita-yuya commented Dec 25, 2018

I'm also very concerned about this.

@SeekDaSky
Copy link

@SeekDaSky 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
Copy link

@sellmair 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
Copy link

@SeekDaSky 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
Copy link

@sellmair 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
Copy link

@SeekDaSky 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
Copy link

@sellmair 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 ☺️

@hbucius
Copy link

@hbucius hbucius commented Aug 13, 2020

Do we have any change log and doc for 1.3.8-native-mt-1.4.0-rc build?

qwwdfsad added a commit that referenced this issue Aug 18, 2020
* Provides newSingleThreadedContext.
* Provides Dispatchers.Main on iOS, Dispatchers.Default everywhere.
* Coroutine references (Job) and all kinds of channels are shareable across workers.
* Each individual coroutine is confined to a single worker.
* Update Dispatchers docs to account for native-mt changes.
* Multithreaded support in select expression.

Additional fixes:
* Fixed broadcast builder with different thread
* Fixed adding a child to a frozen parent job

Fixes #462
Fixes #470
Fixes #765
Fixes #1645
Fixes #1751
Fixes #1828
Fixes #1831
Fixes #1764

~ Kotlin 1.4-M2-eap-83
qwwdfsad added a commit that referenced this issue Aug 18, 2020
* Provides newSingleThreadedContext.
* Provides Dispatchers.Main on iOS, Dispatchers.Default everywhere.
* Coroutine references (Job) and all kinds of channels are shareable across workers.
* Each individual coroutine is confined to a single worker.
* Update Dispatchers docs to account for native-mt changes.
* Multithreaded support in select expression.

Additional fixes:
* Fixed broadcast builder with different thread
* Fixed adding a child to a frozen parent job

Fixes #462
Fixes #470
Fixes #765
Fixes #1645
Fixes #1751
Fixes #1828
Fixes #1831
Fixes #1764

~ Kotlin 1.4-M2-eap-83
@qwwdfsad
Copy link
Member

@qwwdfsad qwwdfsad commented Aug 18, 2020

1.3.9-native-mt is here: mirror of 1.3.9 with the same changes in Native part as in previous -native-mt versions

@thoutbeckers thoutbeckers mentioned this issue Aug 18, 2020
3 of 3 tasks complete
qwwdfsad added a commit that referenced this issue Sep 1, 2020
* Provides newSingleThreadedContext.
* Provides Dispatchers.Main on iOS, Dispatchers.Default everywhere.
* Coroutine references (Job) and all kinds of channels are shareable across workers.
* Each individual coroutine is confined to a single worker.
* Update Dispatchers docs to account for native-mt changes.
* Multithreaded support in select expression.

Additional fixes:
* Fixed broadcast builder with different thread
* Fixed adding a child to a frozen parent job

Fixes #462
Fixes #470
Fixes #765
Fixes #1645
Fixes #1751
Fixes #1828
Fixes #1831
Fixes #1764
Fixes #2064
Fixes #2025
@uOOOO
Copy link

@uOOOO uOOOO commented Sep 14, 2020

Do you have any plan for a new native-mt version including fix #2025?
I can't use ktor log level BODY and ALL with 1.3.9-native-mt.

qwwdfsad added a commit that referenced this issue Sep 21, 2020
* Provides newSingleThreadedContext.
* Provides Dispatchers.Main on iOS, Dispatchers.Default everywhere.
* Coroutine references (Job), all kinds of channels and StateFlow are shareable across workers.
* Each individual coroutine is confined to a single worker.
* Update Dispatchers docs to account for native-mt changes.
* Multithreaded support in select expression.

Additional fixes:
* Fixed broadcast builder with different thread
* Fixed adding a child to a frozen parent job

Fixes #462
Fixes #470
Fixes #765
Fixes #1645
Fixes #1751
Fixes #1828
Fixes #1831
Fixes #1764
Fixes #2064
Fixes #2025
Fixes #2226
Fixes #2138
@BartArys BartArys mentioned this issue Sep 21, 2020
0 of 7 tasks complete
@uOOOO
Copy link

@uOOOO uOOOO commented Sep 24, 2020

1.3.9-native-mt-2 was released and ktor is working without any problem on native target. 👍

@werner77
Copy link

@werner77 werner77 commented Oct 26, 2020

Anybody has any idea why this issue exists in the mt version? #2335

runBlocking is not the only problem, I also witnessed code not being executed in main code. In particular with nested withContext() blocks.

qwwdfsad added a commit that referenced this issue Oct 27, 2020
* Provides newSingleThreadedContext.
* Provides Dispatchers.Main on iOS, Dispatchers.Default everywhere.
* Coroutine references (Job), all kinds of channels and StateFlow are shareable across workers.
* Each individual coroutine is confined to a single worker.
* Update Dispatchers docs to account for native-mt changes.
* Multithreaded support in select expression.

Additional fixes:
* Fixed broadcast builder with different thread
* Fixed adding a child to a frozen parent job

Fixes #462
Fixes #470
Fixes #765
Fixes #1645
Fixes #1751
Fixes #1828
Fixes #1831
Fixes #1764
Fixes #2064
Fixes #2025
Fixes #2226
Fixes #2138
Fixes #2263
@ntherning
Copy link

@ntherning ntherning commented Nov 2, 2020

I filed this report https://youtrack.jetbrains.com/issue/KT-42898 which I am a bit surprised no one else is running into. Basically, what happens is that a child coroutine returning a new instance of a class causes that instance to be frozen, even when all coroutines run on the same thread. I wouldn't expect this.

Perhaps I'm doing something stupid. Would be awesome if someone on the team could have a quick look and determine whether this is just me or an actual bug.

@brettwillis
Copy link

@brettwillis brettwillis commented Nov 11, 2020

May I have an ETA on a 1.4.x-native-mt release? This is blocking ktorio/ktor#2160, and as ktor seems to enforce strict coroutines version check on native, and I've already migrated my codebase to use shared flows, this is blocking my development.

As it's been two week since 1.4.0 I'm wondering if this will happen soon or if I should migrate back to 1.3.9 to unblock development...

Thanks 🙏

qwwdfsad added a commit that referenced this issue Nov 12, 2020
* Provides newSingleThreadedContext.
* Provides Dispatchers.Main on iOS, Dispatchers.Default everywhere.
* Coroutine references (Job), all kinds of channels and StateFlow are shareable across workers.
* Each individual coroutine is confined to a single worker.
* Update Dispatchers docs to account for native-mt changes.
* Multithreaded support in select expression.

Additional fixes:
* Fixed broadcast builder with different thread
* Fixed adding a child to a frozen parent job

Fixes #462
Fixes #470
Fixes #765
Fixes #1645
Fixes #1751
Fixes #1828
Fixes #1831
Fixes #1764
Fixes #2064
Fixes #2025
Fixes #2226
Fixes #2138
Fixes #2263
elizarov added a commit that referenced this issue Nov 18, 2020
* Provides newSingleThreadedContext.
* Provides Dispatchers.Main on iOS, Dispatchers.Default everywhere.
* Coroutine references (Job), all kinds of channels and StateFlow are shareable across workers.
* Each individual coroutine is confined to a single worker.
* Update Dispatchers docs to account for native-mt changes.
* Multithreaded support in select expression.

Additional fixes:
* Fixed broadcast builder with different thread
* Fixed adding a child to a frozen parent job

Fixes #462
Fixes #470
Fixes #765
Fixes #1645
Fixes #1751
Fixes #1828
Fixes #1831
Fixes #1764
Fixes #2064
Fixes #2025
Fixes #2226
Fixes #2138
Fixes #2263
elizarov added a commit that referenced this issue Nov 18, 2020
* Provides newSingleThreadedContext.
* Provides Dispatchers.Main on iOS, Dispatchers.Default everywhere.
* Coroutine references (Job), all kinds of channels and StateFlow are shareable across workers.
* Each individual coroutine is confined to a single worker.
* Update Dispatchers docs to account for native-mt changes.
* Multithreaded support in select expression.

Additional fixes:
* Fixed broadcast builder with different thread
* Fixed adding a child to a frozen parent job

Fixes #462
Fixes #470
Fixes #765
Fixes #1645
Fixes #1751
Fixes #1828
Fixes #1831
Fixes #1764
Fixes #2064
Fixes #2025
Fixes #2226
Fixes #2138
Fixes #2263
elizarov added a commit that referenced this issue Nov 19, 2020
* Provides newSingleThreadedContext.
* Provides Dispatchers.Main on iOS, Dispatchers.Default everywhere.
* Coroutine references (Job), all kinds of channels and StateFlow are shareable across workers.
* Each individual coroutine is confined to a single worker.
* Update Dispatchers docs to account for native-mt changes.
* Multithreaded support in select expression.

Additional fixes:
* Fixed broadcast builder with different thread
* Fixed adding a child to a frozen parent job

Fixes #462
Fixes #470
Fixes #765
Fixes #1645
Fixes #1751
Fixes #1828
Fixes #1831
Fixes #1764
Fixes #2064
Fixes #2025
Fixes #2226
Fixes #2138
Fixes #2263
@elizarov
Copy link
Member Author

@elizarov elizarov commented Nov 19, 2020

UPDATE: kotlinx-coroutines version 1.4.1-native-mt released.

@jcraane
Copy link

@jcraane jcraane commented Nov 19, 2020

Does this also fix this issue https://youtrack.jetbrains.com/issue/KT-38770 ?

Haven't tested it myself yet but am planning to do that tomorrow. Will update this thread with my results.

@TrevorStoneEpic
Copy link

@TrevorStoneEpic TrevorStoneEpic commented Nov 20, 2020

I might be missing something, when I create a MutableStateFlow but set the value I get an InvalidMutabilityException. The same code worked in 1.3.9. Has anyone else ran into issues?

@twyatt
Copy link
Contributor

@twyatt twyatt commented Nov 20, 2020

@TrevorStoneEpic others are experiencing that issue as well, looks like #2138 was re-opened and is tracking it.

qwwdfsad added a commit that referenced this issue Nov 27, 2020
* Provides newSingleThreadedContext.
* Provides Dispatchers.Main on iOS, Dispatchers.Default everywhere.
* Coroutine references (Job), all kinds of channels and StateFlow are shareable across workers.
* Each individual coroutine is confined to a single worker.
* Update Dispatchers docs to account for native-mt changes.
* Multithreaded support in select expression.

Additional fixes:
* Fixed broadcast builder with different thread
* Fixed adding a child to a frozen parent job

Fixes #462
Fixes #470
Fixes #765
Fixes #1645
Fixes #1751
Fixes #1828
Fixes #1831
Fixes #1764
Fixes #2064
Fixes #2025
Fixes #2226
Fixes #2138
Fixes #2263
qwwdfsad added a commit that referenced this issue Nov 27, 2020
* Provides newSingleThreadedContext.
* Provides Dispatchers.Main on iOS, Dispatchers.Default everywhere.
* Coroutine references (Job), all kinds of channels and StateFlow are shareable across workers.
* Each individual coroutine is confined to a single worker.
* Update Dispatchers docs to account for native-mt changes.
* Multithreaded support in select expression.

Additional fixes:
* Fixed broadcast builder with different thread
* Fixed adding a child to a frozen parent job

Fixes #462
Fixes #470
Fixes #765
Fixes #1645
Fixes #1751
Fixes #1828
Fixes #1831
Fixes #1764
Fixes #2064
Fixes #2025
Fixes #2226
Fixes #2138
Fixes #2263
@qwwdfsad
Copy link
Member

@qwwdfsad qwwdfsad commented Nov 27, 2020

Released 1.4.2-native-mt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.