Async Coroutine Execution Order

Hey there, I am using Kotlin coroutines to perform API calls asynchronously. Said API expects a non-decreasing nonce with every request. This seems to requires some control over the execution order of the coroutines since frequently a request with a larger nonce will invalidate older requests with a smaller nonce.

For example:

suspend fun submit() {
  val one =   GlobalScope.async { api.doThis()  }
  val two =   GlobalScope.async { api.doThat()  }
  val three = GlobalScope.async { api.doStuff() }

In this example, the first request has a nonce of 1, the second a nonce of 2, the third a nonce of… 3. If now the third request is executed first, it effectively invalidates the other two requests since their nonces are now too small.

That said, it feels somewhat stupid to impose these requirements while using asynchronous requests. But maybe there is a way to achieve this without going fully sequential. Thanks!

1 Like

Not sure if “invalidate” means “breaks functionality” or “can be safely ignored”.

Do you need doThis, doThat, doStuff to each run one after the other? If so, they need to be sequential.

Does running doStuff mean that doThis and doThat no longer matter and can be canceled? If so then you can track the latest Job or Jobs in a variable and cancel them when you get a newer one (where latest/newest is determined by nonce).

Invalidation here means that the API that I am calling is keeping track of the nonces and requires them to be non-decreasing. When the request to api.doStuff() has a nonce of 3 and this is the first request received by the API, then the API will reject the calls to doThat() and doThis() since their nonces (1 and 2) are less than 3 - which by then has already been observed by the API.

So, the calls are somewhat independent, but they are tied together by the requirement to send non-decreasing nonces. What bothers me is that all calls take roughly 10 seconds to complete, and I have to wait for all of them, so I’d really like to solve this with coroutines.

The problem is not just limited to coroutines in this case. I assume we are talking HTTP requests, meaning that a new TCP connection is established for each requests. If you send multiple requests right after another, then there is no guarantee that they will arrive in the same order. That is simply how network traffic works. The only way that you can be sure that they will arrive the correct order is by not sending the second request until you have received an acknowledgement on the first. With HTTP, you probably won’t get any such acknowledgement before the response itself. In other words, you pretty much have to go fully sequential in order to guarantee the order.

Maybe you could work around it in most cases by delaying the second request, i.e. something like this:

async { api.doThis() }
async { delay(100); api.doThat() }
async { delay(200); api.doStuff() }

This still does not guarantee anything, and it also relies on the server even being able to handle multiple requests concurrently (I suspect that might not be the case; otherwise I see no reason to even require a total ordering of the requests through the use of strictly increasing identifiers).

If the nonce goes in a request header, then maybe if you send an Expect: 100-continue header, then maybe the API will process the nonce and send you an intermediate 100 response quickly after processing it. That would allow you to send the next request much earlier.

See: 100 Continue - HTTP | MDN

Note: this only works if the docs say it works. Normally, default behaviour for web servers processing a POST is to send the 100 response immediately after reading the headers if it’s requested. You really need it to wait until the nonce is processed.