Wrapping non-Send Future to be Send


#1

I have a T: Future which is not Send, i.e. it’s tied to the thread that created it. It means that poll on this Future is allowed only on its original thread (and in my case that thread belongs to the event loop, so it’s out of my control).

I’d like futures in other threads to wait for this future.

I’ve tried a few approaches to this, but it all comes down to one of two situations:

  1. All kinds of wrappers and combinators, like Shared<T> & BiLock<T>, depend on the original T, so they either aren’t Send themselves, or require T: Send, so I can’t touch them from other threads.

  2. If I try to use some other synchronization method that isn’t directly polling the original future, I end up with various deadlocks/never-ending waits. I assume that’s because the original future may be unable to make progress if it doesn’t get polled. I can’t poll it from another thread, so some calls to poll end up doing no work.

Is there a way around it?


#2

Have you considered using a oneshot channel instead? When your future completes on its event loop, you send its value (or error) over the channel.


#3

Won’t this fall into the #2 problem above? If I return a Future that waits for the channel, what will drive the original future that is supposed to get the data?

To add more context: I’m processing requests in Actix-web. I need to give it result as a Future (although Actix-web also allows blocking responses, I need ability to cheaply handle more requests than threads).


#4

Shouldn’t the event loop on the future’s thread drive it? Naively, I’d imagine you’d have something driving that future within the context of its event loop, such as a combinator chained on to it that then forwards that future’s result over the channel - this combinator plus your underlying future would be scheduled for execution on that event loop.


#5

That was my assumption too, but after writing a few broken combinators, I’m suspecting it’s wrong. My current theory is that futures are not allowed to “waste” any poll - the original source future has to see the poll call in order to save the Task that is polling in order to notify it when it makes progress. If I add a non-Future indirection there, the chain between it and the task driving it gets broken.


#6

What type of event loop is running there? In tokio, for example, a future added to its reactor will have its poll() called at least once, the first time being when it’s being added to the reactor (IIRC). If it’s not ready at that point, the implementation would save the notify (task) handle for later (when whatever async action makes it ready). After the handle is notified, tokio would poll it again.

I guess your setup is different, namely there’s no “bootstrap” poll of the future upon it being added to the reactor.


#7

OK, problem solved. I’ve initialized Actix’s System incorrectly, which was causing all kinds of trouble. I’ve unnecessarily copied a complicated set up from Actix’s own unit tests. System::new("test").block_on(future) is the right way.