Recently I have been getting into async and tokio. With great success despite the tribulations I brought up here: A year in Rust. And I still can't read the docs!
My latest creation has tokio-tunstentite
serving up web sockets to a test client also using tokio-tungstenite
.
Having got that basic ws functionality working I proceeded to introduce NATS messaging into the mix using the nats
crate. You see, those web socket clients are expecting to be fed data that derives from subscription to our NATS server (among other things). Each ws client's data requirements may require multiple subscriptions to different 'subjects' from NATS.
So here comes the problem:
When a ws client connects a new tokio task is spawned to handle it. That task makes the necessary subscriptions to NATs and then sits in a loop waiting for responses from NATS and passing them back down the web socket.
Well, the NATS crate does not support async. Waiting on responses is a blocking call.
OK, no problem I thought, just do that call in a task::spawn_blocking
and .await the response. So that is what I do and it is working just fine.
But, what did I actually just do?
As far as I understand all the async code could in principle run on a single thread on a single core, no matter how many tasks are involved. Thus saving all that thread overhead. But, as far as I understand, spawn_blocking
fires up an actual new thread from which to run the blocking code, thus not blocking the async tasks.
Which is great but imagine I had a million async tasks all making blocking calls using `spawn_blocking'. Now I have degenerated all that async stuff into a million good old fashioned threads. Thus defeating he whole point.
I have framed this question in terms of tokio and nats but it must be a general problem. Is there an efficient way to do this?