Server-side async/await with coroutines


#1

Hi there,

I’m currently exploring coroutines for server-side. I have some experience with async/await in JavaScript and Hack (https://docs.hhvm.com/hack/async/introduction)

On first impression, coroutines appear more powerful than simple async/await, but also much more daunting

What I’m trying to achieve is a simple server-side model as in JS and Hack:

  • each request is served by a single thread (no multi-threading per request)
  • does not block on I/O

What I think I have figured out so far:

  • Each time I use async, I should pass coroutineContext to avoid using thread pool
  • The JS and Hack behavior seems to match CoroutineStart.LAZY, so I have to pass start as well (I am not sure of the implications of the default behavior where coroutines are started eagerly)
  • I should probably have my async helper which automatically passes those parameters
  • Is there a utility to wait for a list of Deferreds in parallel? Something like Promise.all in JS
  • If every method in codebase is marked as suspend, will there be a performance penalty, since this is not language level concept in Kotlin?

Am I generally looking in the right direction?

Thanks!


#2

The reactor pattern is already provided by Vert.x, take a look here:

http://vertx.io/docs/vertx-lang-kotlin-coroutines/kotlin/

On reactor pattern coroutine must start later because the event loop is busy when coroutine is launched. Lazy or eager depends by your use case.

Vert.x may fit your use case, it looks like a NodeJS server.


#3

Thanks for the reference! Vert.x seems very good.

I am asking specifically about user code. Let’s say I’m using Vert.x in Kotlin to process a request, and I have a database client with a suspending query method (this is probably provided by Vert.x?)

I would like to run queries from a single request in parallel (without multithreading). Also I would like to use coroutines instead of callbacks, futures, etc.

Would I be using coroutines in the way I described?

If you know about any such example please let me know

What I was referring to is this, in JavaScript:

async function foo(things) {
  const results = [];
  for (const thing of things) {
    results.push(await bar(thing));
  }
  return baz(results);
}

This will execute bars serially. Instead, await Promise.all should be used

In Kotlin by default, the first time await is called, it will start other coroutines too, so bars are executed in parallel. I am not sure about pros and cons of the two approaches (never seen Kotlin’s approach before in practice)

Thanks!


#4

Maybe, please refer to Vert.x documentation.

Yes, it is the purpose of documentation.

No, for is a iteration, so bar is executed one at time and results are pushed in the correct order.
async/await are the same in Kotlin and EcmaScript.

You are welcome


#5

I don’t think you really need your async helper to pass an appropriate coroutine context. Instead, you should try to avoid using async in your code as much as possible (ideally, not at all). You should focus on trying to represent your needs with suspend functions. You should just launch a new coroutine on each request and that is going to be only place in your code where you’d explicitly specify coroutine context. Then the actual business-logic that handles the request would be written in such a way that all the functions that perform any kind of asynchronous activity and/or need to wait for anything are marked with suspend.

I’d suggest to check out “Introduction to Coroutines” presentation from KotlinConf for an overview of all the coroutine-related concepts and how they are different from traditional async/await model that you’d find in Hack and elsewhere: https://www.youtube.com/watch?v=_hfBv0a09Jc


#6

Hey @elizarov thanks for your reply!

The suggestion makes sense, and I can get away with it until I want to do things in parallel. E.g. I would like to be able to make two database queries (which are independent) in parallel, form a single request. At that point, I have to use async. This is well supported by Node.js and Vert.x

If I want to use Reactor pattern, which I believe is an extremely useful model (judging by Node.js and Vert.x success etc.), then I have to pass coroutineContext (every time) to avoid multi-threading. Currently the default (which cannot be changed) will use a pool of threads. For the use case this is both inconvenient and dangerous (if my code is not thread-safe, which it isn’t).

Ideally, the library should allow to change the default and allow single-threading execution in the parent thread (i.e. coroutineContext) as the default. Is this something that you would consider?


#7

It looks very promising!

I was looking into its integration with Kotlin coroutines, and one thing I find odd is, there is a lot of usage of awaitResult primitive, e.g. in https://github.com/vert-x3/vertx-examples/blob/master/kotlin-examples/coroutines/src/main/kotlin/movierating/App.kt:

    val result = awaitResult<ResultSet> { client.queryWithParams("SELECT TITLE FROM MOVIE WHERE ID=?", json { array(id) }, it) }

I was wondering why is there a need for it in Kotlin from API standpoint (understand it has to be like that in e.g. Java).

With Kotlin coroutines, it should be possible to write:

    val result = client.queryWithParams("SELECT TITLE FROM MOVIE WHERE ID=?", json { array(id) })

and have queryWithParams a suspending function. Wouldn’t such code be more natural?

Consider this:

class CoroutinesSimpleTest {
	@Test
	fun test() = runBlocking {
		val l = List(60) {
			async {
				delay(1000L)
			}
		}

		l.forEach { it.await() }
	}
}

This code will take 1s to execute in Kotlin, but 60s in JavaScript or Hack. The default settings are not the same (in terms of when a coroutine is started).

I’m not advocating for either (I have no experience with Kotlin’s approach), but I’m curious about the reasons behind the default


#8

I agree with you, it is annoying, however awaitResult is a function (not a primitive).

I raised same issue, take a look here: https://github.com/vert-x3/vertx-lang-kotlin/pull/14

Ok, I understand your question, but I am not the right person to offer you a right answer.
In your example can use launch (and join) instead of async, launch build and start a Job, async build and start a Deferred; that’s all.
In my personal experience I often use these builder with default values.


#9

Indeed, you should be using async when you need parallel execution. However, I don’t think you should worry about the context of its execution most of the time and using async { ... } with a default executor should be Ok for your parallelization needs. I’m curious to learn why would like to avoid a default thread pool that async use when you need to parallelize something?

Answering some of your other questions, I don’t think JS default corresponds to CoroutineStart.LAZY. I think that the default start parameter actually corresponds to the way async functions work in JS. What makes you think otherwise?


#10

I think the whole idea is to parallelize IO-heavy code on a single thread in order to keep the number of threads under control and to be able to mutate shared state without having to worry about thread safety.


#11

Your concern is well-founded, but I do suggest you to consider abolishing shared mutable stated as much as you can, especially in IO parts of your application. I understand that it might be hard to avoid shared mutable state in UI parts, but IO… I strongly believe that IO part of a well-architected application should not have any shared mutable stable.


#12

I’m not sure I agree completely, as there are some light use cases where shared mutable state is acceptable and preferable over agents and other mechanisms of explicit communication.

Also, there are other reasons to allow changing the default context. E.g., an application that balances threads number based on available hardware and load may want to maintain the thread pool properties and individual threads in the pool. So it would be beneficial to allow such applications (or containers) set their own default context.


#13

C# had walked that walk. They get a default execution context for new async tasks from a thread-local variable. It is a disaster for library writers who must not ever forget to always explicitly specify their context, because the current default is unknown and may be inappropriate. We’ve discussed this issue back and forth and had decided that we’d stick with a stable default for a time being. That does not save you from explicit specification of the context where you need it, but, at least, when you don’t specify the context, then behavior of your code is predictable and reproducible regardless of the environment it runs in.