Cost of rebuilding futures in a loop?

I'm working on an async library and I have a Connection object with a run function that advances a bunch of internal state. I need to handle a bunch of different things concurrently, so this leads me to have a big blob that kinda looks like this:

use futures_concurrency::future::Race;

loop {
    let wait_for_thing1 = async { ... };
    let wait_for_thing2 = async { ... };
    let wait_for_thing3 = async { ... };
    let wait_for_thing4 = async { ... };
    match (wait_for_thing1, wait_for_thing2, wait_for_thing3, wait_for_thing4).race() {
        // handle each thing
    }
}

All of the things use some piece of internal state (i.e. socket, channel) on the Connection and I use some helper methods on Connection for handling some of the events (in the match) to keep things less messy.

My main question is, is rebuilding futures in a loop like this problematic performance wise? Would it be worth switching to a design that uses Streams or something? Or is this not worth worrying about?

The first question I'd ask here is not the performance, but cancellation safety. Is it okay that, e.g., when thing4 resolves, thing1..thing3 are restarted from scratch?

1 Like

Yes, I did consider this and this is fine in this case.

Creating an async block future is no more significant than creating a closure or a struct value. Unless it's especially large, all the significant costs are in what the async block future does when polled.

5 Likes

It depends on what those futures do, but if you can write them in a way that is not taking ownership of their internal state, you can create them outside the loop, stack pin them, and then poll them in the loop by polling on exclusive reference.

use std::pin::pin;

async fn main() {
    let mut wait_for_thing1 = pin!(async { ... });
    let mut wait_for_thing2 = pin!(async { ... });
    let mut wait_for_thing3 = pin!(async { ... });
    let mut wait_for_thing4 = pin!(async { ... });
    loop {
        match (
            &mut wait_for_thing1,
            &mut wait_for_thing2,
            &mut wait_for_thing3,
            &mut wait_for_thing4,
        ).race().await {
            // handle each thing
        }
    }
}