Futures-rs: Single-threaded Executor trait

Created on 10 Mar 2018  路  7Comments  路  Source: rust-lang/futures-rs

The 0.2.0 Executor trait requires the boxed future in spawn to be Send. That prohibits code that is generic over single-threaded executors.

Is the Send requirement actually necessary for single-threaded executors, or did you just chose the more restrictive type to allow multi-threaded executors? If that's the only reason, I'd love to see an ExecutorUnsync trait that drops the Send requirement and is implemented by LocalExecutor.

And a (perhaps related?) question: Why does spawn_local require a 'static bound but spawn does not?

Most helpful comment

Thank you for the responses, that really cleared things up for me.

At the moment, I don't know of a lot of reason to be generic over single-threaded pools, and I'd prefer not to add a new trait until there's a clear need for it in the ecosystem. Do you have a scenario in mind?

Mostly general unhappiness with forcing synchronization overhead on api consumers that only want to run things on a single thread.

Here's the setting that made me stumble over these questions:
I'm implementing an rpc-protocol, and currently I need to return two futures when doing a request: One for actually sending the request, one for receiving the response. Sending may also perform some closing cleanup if it is the last rpc, so I can't simply have the sending future yield the receiving future: It might for example successfully send the request but then block on the cleanup.

If the rpc handler held a handle to the event loop, it could automatically spawn the cleanup work as needed, so I could switch to the much nicer API of returning a single request future that yields a response future (and spawns cleanup on the event loop, encapsulating this from the user).

But to do that, the current trait situation forces me to make all the implementation details Send, even though the API consumer might run the whole thing on a single event loop (actually that's the expected usage).

So I'd like to expose two APIs: One for multi-threaded usage with synchronization, and one for single-threaded usage that can use a more efficient implementation. But the latter can not be written with the current traits.

All 7 comments

A question that is perhaps easier to answer: Is it possible to write a specialized executor that requires neither Send nor 'static on the futures it spawns? And if so, could it replace the current LocalExecutor with its 'static bound?

Why does spawn_local require a 'static bound but spawn does not?

They both require 'static-- 'static is implicit in trait objects,so it doesn't appear in the signature of the spawn function.

Is it possible to write a specialized executor that requires neither Send nor 'static on the futures it spawns? And if so, could it replace the current LocalExecutor with its 'static bound?

The answer to both of these questions is "yes". To do this, you'd need to add a + 'a to the Box<Future> here, making Task and LocalExecutor generic on that lifetime.

The Executor trait is used primarily for providing the default executor for Context::spawn. Since we want to support thread pools, we have to conservatively require Send.

At the moment, I don't know of a lot of reason to be generic over single-threaded pools, and I'd prefer not to add a new trait until there's a clear need for it in the ecosystem. Do you have a scenario in mind?

Thank you for the responses, that really cleared things up for me.

At the moment, I don't know of a lot of reason to be generic over single-threaded pools, and I'd prefer not to add a new trait until there's a clear need for it in the ecosystem. Do you have a scenario in mind?

Mostly general unhappiness with forcing synchronization overhead on api consumers that only want to run things on a single thread.

Here's the setting that made me stumble over these questions:
I'm implementing an rpc-protocol, and currently I need to return two futures when doing a request: One for actually sending the request, one for receiving the response. Sending may also perform some closing cleanup if it is the last rpc, so I can't simply have the sending future yield the receiving future: It might for example successfully send the request but then block on the cleanup.

If the rpc handler held a handle to the event loop, it could automatically spawn the cleanup work as needed, so I could switch to the much nicer API of returning a single request future that yields a response future (and spawns cleanup on the event loop, encapsulating this from the user).

But to do that, the current trait situation forces me to make all the implementation details Send, even though the API consumer might run the whole thing on a single event loop (actually that's the expected usage).

So I'd like to expose two APIs: One for multi-threaded usage with synchronization, and one for single-threaded usage that can use a more efficient implementation. But the latter can not be written with the current traits.

Theoretically Context could contain an optional single-threaded executor, just like it currently contains an optional thread-safe Executor (pub fn executor(&mut self) -> Option<&mut Executor>).

But what would your code do if there is no single-threaded executor in a Context available? Could you really switch to a thread-safe implementation during poll? This seems unlikely to me (and I think the optional executor was a mistake in the first place, see #923). You could try to document your requirement of course, but I'd prefer a compile time check...

I believe the futures_core::task::LocalSpawn and futures_core::task::Spawn traits resolve this issue. (Also I'm very happy std::task::Context doesn't include a method to get an executor/spawner anymore.)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Nemo157 picture Nemo157  路  5Comments

seanmonstar picture seanmonstar  路  4Comments

xrl picture xrl  路  3Comments

aturon picture aturon  路  5Comments

jonhere picture jonhere  路  4Comments