Best way to enforce lineal sequence of operation in async code?
from to on 17 Sep 2023 09:51 +0000

Hi there,

I’m working on a bot to do social games on the fedi, and using the mastodon-async crate for communicating with the ActivityPub server in question. At the moment I’m using tokio mt as a runtime, though I’m new at async so if you think I shouldn’t let me know.

The pattern I want to implement is the following:

What I’m not very clear is how to keep this list to assure that sequence will be respected. I.a., if two play messages come reasonably quick together, I want one to be processed, then entered on the list, or get the match to start; then the other to get processed.

My current thoughts:

Any thoughts on what the reasonable thing to do is here? I’m very new to async and while I realise there’s probably lots of ways to do this, they’re not all equally ergonomic and I want to avoid myself future pain.

#rust on 17 Sep 2023 10:23 +0000

Both would work. The channel-based one is probably more ergonomic, because there you can write the sequence linearly just as you described.

One important thing for both approaches is that you must not use blocking variants of the mutex or channel. There are async versions of both available, though. on 17 Sep 2023 10:30 +0000

So probably the tokio mpsc channel, right? Why is it not possible tu use normal sync channels? I’ve read about it but I don’t understand the reason.

Also I’m thinking of spawning a thread to do this part, or should it run on the tokio main function? on 17 Sep 2023 11:14 +0000 next

tokio uses thread pools for scheduling async tasks, which generally is what you want (because spawning threads is expensive and can lead to DoS vulnerabilities).

If you block in an async task, the thread the task is running on is no longer available to other async tasks. If you have the same amount of tasks currently blocking as you have threads, the whole system grinds to a halt. on 17 Sep 2023 11:20 +0000

The regular versions are blocking. If an item isn’t available they will put the current OS thread to sleep and wait for the channel or mutex.

This sort of defeats the purpose of async which is to allow many logical threads but actually consumes a small number of OS threads.

When you wait on an async channel or mutex instead of blocking the current thread it will yield to the current executor. This will allow the current OS thread to run other tasks to run in the meantime. Then when the channel has an item or the lock is unlocked the executor will then continue running the original future from where it left off.

It’s worth noting that on most major desktop executors/async runtimes this won’t actually cause any problems. They will spawn a new OS thread to run other tasks if you block one. This will use a bit more memory and maybe a small latency blip but ultimately not be a major issue. However some runtimes will have a fixed number of backing OS threads and blocking them will result in higher latency or deadlocks. So in general you shouldn’t block in async functions.