<div dir="ltr"><div>Hey everyone.<br><br></div><div>Nick and I clarified our understanding of coroutines via IRC. Between this email and IRC, it seems Nick's primary concern is that it may be difficult to make the FFI libraries internally threadsafe [1]. By default, coroutines dispatch to the `CommonPool`, where 1) coroutines can be dispatched to arbitrary common pool threads and 2) an already dispatched coroutine, after suspension, can resume on an arbitrary thread in the common pool.<br></div><div><div><br></div><div>Unfortunately, I don't understand the FFI thread safety in Rust issue well enough to provide a single solution, however, some varied solutions to thread safety are:</div><div>- Coroutines optionally suspend when they call a function with the `suspend` keyword. afaik, if you never call one of these functions, you'll never suspend and you'll remain on a single thread. This is hard to enforce, however.<br></div><div>- Use a single dedicated thread for all FFI calls by running on a dedicated CoroutineContext: see `<a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/new-single-thread-context.html">newSingleThreadContext</a>`</div><div>- (may not be possible) Implement a new CoroutineContext/CoroutineDispatcher that dispatches to a thread pool but will resume only on the same thread. CoroutineContexts can be built on top of Executor, if that's helpful.</div><div>- If your problem is sequential calls rather than which actual thread the coroutines run on, you can use a coroutine `<a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.sync/-mutex/index.html">mutex</a>` which will suspend until the resource can be acquired.<br></div></div><div><br></div><div>For our raw conversation, see <a href="https://mozilla.logbot.info/mobile/20180723#c15057582" target="_blank">the IRC logs</a>.</div><div><br></div><div>Hope it helps! Please let me know if you have more questions!<br></div><div>- Mike<br></div><div><br></div><div>[1]: <a href="https://mozilla.logbot.info/mobile/20180724#c15058851" target="_blank">https://mozilla.logbot.info/mobile/20180724#c15058851</a> (and previous day)<br></div></div><br><div class="gmail_quote"><div dir="ltr">On Fri, Jul 20, 2018 at 9:44 AM Nicholas Alexander <<a href="mailto:nalexander@mozilla.com" target="_blank">nalexander@mozilla.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div>Mike,</div><div><br></div><div>Thanks so much for teaching us some things.<br></div><div><div class="gmail_extra"><br><div class="gmail_quote">On Thu, Jul 19, 2018 at 1:20 PM, Michael Comella <span dir="ltr"><<a href="mailto:mcomella@mozilla.com" target="_blank">mcomella@mozilla.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div>Thanks for the updates, Nick – it's interesting to hear what other teams are doing and how it may affect me. :) One quick thought:<br></div><span><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Rust code should be synchronous</blockquote><div><br></div></span><div>I haven't thought about this enough to have an opinion on whether this is the right decision or not. However, this may encourage the Rust library consumers to make a mistake I once made with Kotlin coroutines: <b>I want to share so others don't make the same mistake.</b></div><div><br></div><div>While you can create an obscenely large number of coroutines, there is a limit to the number that can run concurrently: this limit is defined by the thread pool they run on. The default built-in `CommonPool` has a fixed number of threads. If you run blocking code (e.g. potentially synchronous Rust code ^) on this thread pool, i<b>t prevents other coroutines from beginning to run</b> until the blocking code completes. <a href="https://github.com/mcomella/BlockingCoroutinesExample" target="_blank">Here's a demonstration using Android.</a></div></div></blockquote><div><br></div><div>I was concerned about this, for slightly different reasons: I care about the same thread of execution servicing the next FFI call, i.e., that FFI calls don't rotate between threads non-deterministically. But I think it's very similar to the situation you warn about. I took an action item to read more about Kotlin co-routines, and summarized (in the notes) like this:</div><div><br></div><div><ul style="margin-top:0pt;margin-bottom:0pt" id="m_4377754816220112884m_3764060669431637260m_-276860367009069368gmail-docs-internal-guid-a6c72c1f-b88e-7449-9fa4-2a5bf7bcc40e"><ul style="margin-top:0pt;margin-bottom:0pt"><ul style="margin-top:0pt;margin-bottom:0pt"><li dir="ltr" style="list-style-type:square;font-size:11pt;font-family:"Open Sans";color:rgb(0,0,0);background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt"><span style="font-size:11pt;font-family:"Open Sans";color:rgb(0,0,0);background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap">AI: </span><span style="font-size:11pt;font-family:"Open Sans";color:rgb(0,0,0);background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap">nalexander to read more</span><span style="font-size:11pt;font-family:"Open Sans";color:rgb(0,0,0);background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap"> about how Kotlin coroutines are implemented</span></p></li><ul style="margin-top:0pt;margin-bottom:0pt"><li dir="ltr" style="list-style-type:disc;font-size:11pt;font-family:"Open Sans";color:rgb(0,0,0);background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt"><span style="font-size:11pt;font-family:"Open Sans";color:rgb(0,0,0);background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap">[nalexander] </span><span style="font-size:11pt;font-family:"Open Sans";color:rgb(0,0,0);background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap">OK, I’ve done this.</span><span style="font-size:11pt;font-family:"Open Sans";color:rgb(0,0,0);background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap"> Based on the </span><a href="https://kotlinlang.org/docs/reference/coroutines.html" style="text-decoration:none" target="_blank"><span style="font-size:11pt;font-family:"Open Sans";color:rgb(17,85,204);background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:underline;vertical-align:baseline;white-space:pre-wrap">Kotlin 1.1+ coroutines documentation</span></a><span style="font-size:11pt;font-family:"Open Sans";color:rgb(0,0,0);background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap">, Kotlin coroutines are implemented as a CPS transformation and an internal state machine. That state machine is pumped from a single thread. That’s very similar to </span><a href="https://github.com/clojure/core.async" style="text-decoration:none" target="_blank"><span style="font-size:11pt;font-family:"Open Sans";color:rgb(17,85,204);background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:underline;vertical-align:baseline;white-space:pre-wrap">clojure/core.async</span></a><span style="font-size:11pt;font-family:"Open Sans";color:rgb(0,0,0);background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap"> and means that there’s no thread pool servicing coroutines! Any additional thread must be the result of the application (or a library). </span><span style="font-size:11pt;font-family:"Open Sans";color:rgb(0,0,0);background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre-wrap">That means that all my hypothetical concerns about coroutines invoking FFI methods on multiple threads are not justified.</span></p></li></ul></ul></ul></ul></div><div>Am I incorrect? Please correct me if so!<br></div><div><br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div>Why is this a problem? <b>If you're a library (e.g. android-components) that uses the same fixed thread pool as the application, you may block the application's coroutines from starting quickly!</b> For example, if the library makes several calls to read configuration files from disk, each on a new coroutine on the CommonPool, these may block all of the CommonPool threads. If all the threads are blocked and the application spawns a coroutine on the CommonPool to do background processing before rendering the results in the UI, the application's coroutine (and thus the UI update) will wait until the library's coroutines finish blocking before it runs.</div><div><br></div><div>---<br></div><div><br></div><div>Some possible solutions to this are:</div><div>- Use a separate thread pool for blocking calls (here's an <a href="https://github.com/Kotlin/kotlinx.coroutines/issues/79" target="_blank">open ticket for a system-wide IO thread pool</a>)</div><div>- Use non-blocking IO (<a href="https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-nio/" target="_blank">supported in Java 7+ with nio</a>)</div></div></blockquote><div><br></div></div><div class="gmail_quote">I agree that `CommonPool` can cause issues. (Just for the record, I had not heard of `CommonPool`.) But to me, those issues are clearly _above_ the Rust FFI layer, and the _common_ part of the name is pretty clear that applications and libraries need to coordinate in order to prevent bad things (in this case, limiting throughput due to resource starvation, or, in the worst case, livelocking).</div><div class="gmail_quote"><br></div><div class="gmail_quote">Coming back to my summary of how Kotlin coroutines are implemented, something above the Rust library layer has to handle adding asynchrony to the synchronous Rust -- either by spawning threads (with a pool, common or otherwise, if desired); or by doing non-blocking things where possible; or ...</div><div class="gmail_quote"><br></div><div class="gmail_quote">Are we in accord?<br></div><div class="gmail_quote">Nick</div><div class="gmail_quote"><br></div></div></div></div>
</blockquote></div>