How to properly use sync library in async code?

Hi everybody

I've been "rusting" for a little while and I just convince one of my superiors to use Rust in a kind of mission critical system. The system talk to other equipment through distinct protocols (SNMP, MQTT, MODBUS/TCP and of course SMTP) so that the thing is hard to test. The system was already implemented using NodeRED but I had problems hard to track and decided to replace the critical part by Rust.

I choose the Rust because

  • Stability 1: Once it compiles I want it to be as ready as possible
  • Stability 2: Statically compiled stuff make it bullet proof against system updates (this is a nice to have for me)
  • Small footprint: It's predictable/stable on memory use and light weight

The daemon run on a system with restricted memory but I want it to be concurrent because of a design choice. I have a main task that receives messages through a channel from another tasks and trigger an action if nothing arrives before timeout, or if the value is above some threshold. The values are sensor data, and I may have multiple distinct sources of the same data, if some sensor, or subsystem stop working I still have that data coming from another source. By separating in tasks I can make action triggering agnostic to sensor code.

Now the async stuff... While writing this I found some libraries for this, some libraries for that. It may come the day when I want to use a lib that is sync, but I'm writing async code.

My first idea was to, spawn a new task, an OS thread inside that task, wrap the sync code in that thread, and communicate using async channel, but before going further I decide to come here and see what other people suggests.

PS: My approach feels wrong to me, using thread on async code feels ... wrong ... am I being paranoid?

What are the possible strategies to call sync code from async code without blocking the eventloop?

The first question to ask is if the library does something where the sync/async distinction matters. You can determine this by looking at your .awaits. If the time it takes to go from one .await to another is very small, you're fine, but if it takes time to go between them, then you are blocking and need to do something.

Spawning a thread and using channels is indeed one reasonable approach to handle this issue, but often there is a simpler approach: Put the blocking section inside spawn_blocking. This will run it on a thread pool that has a very large limit on the number of threads (in Tokio, it is 512 threads), and is best suited for blocking IO tasks that you don't have an async alternative to.

2 Likes

Hi @alice thanks for answering

512 threads, mean 512 threads at start? I will need to control this number of threads,

I didn't know that spawn_blocking, I'm really new to tokio :frowning:

I'm concerned about blocking because if my main thread doesn't receive a value before a timeout, it will trigger a shutdown on everything. And if I shutdown things when I shouldn't well, it will not be a very happy day for me and my customer :slight_smile:

4 threads, seem enough to me (I only need 2 since I have only one sync dependence right now, but I want keep some space).

Is there any literature on tokio thread pool behavior?

No, it will not use 512 threads unless you actually use start that many blocking operations concurrently. It's just an upper limit. There's a bunch of stuff written in the tokio::runtime module as well as on the various types in that module.

1 Like

Thank you so much Alice for taking your time to answer this :slight_smile: I'll do some reading

THANKS!

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.