Years ago, I wrote a client library for Apache BookKeeper and chose Send
, !Sync
and Clone
for the client object initially.
Clone
andSend
so the client can be shared among concurrent tasks.!Sync
so the every clients can have their own data without cross thread synchronization.
This way the client can batch simultaneous requests in single asynchronous task and serve parallel requests in multiple concurrent asynchronous tasks. But it failed due to .await
requires &self
to be Send
which is not possible by definition if Self
is !Sync
.
I complained it a lot with quotes from What shall Sync mean across an .await?. Recently, in developing spawns, I found many async runtimes have spawn_local
to spawn !Send
tasks. It is boring. I said A future should be Send
unless it captures !Send
before. Currently, some !Send
tasks should actually be Send
. This time, I want to go further about what make a future !Send
and how Rust could solve them.
Before continue, I want to state two points.
Codes before .await
happens before codes after .await
.
This is the ground truth in our mental, otherwise everything fucked up. It is same for codes in thread and process.
Rust future is a combination of states and Future::poll
. .await
is the call site of Future::poll
which advances states. Then above statement become: Future::poll
observe data changes from last run. In single thread executor, Future::poll
is invoked sequentially, so above statement hold. In multi-thread executor, thread which acquire
the future will observe changes made in thread which release
that future. Multi-thread executors are considered buggy if they can’t guarantee above statement.
Futures are self-contained concurrent execution units, just like threads to multi-cores.
From above, we know that codes in future are executed sequentially, we fear no contention inside single future and we are capable to run multiple futures concurrently. Additionally, a Future + Send + 'static
is self-contained, it contains nothing !Send
or no static to outside. All those are same to what threads provide to us, self-contained sequential execution unit in itself but concurrent with each other. If we are able to use !Send
after thread::sleep, we should be able to do the same after .await
.