1

Although the code compiles, I don't understand why when using await directly it gets stuck in the first request. Why do I need to use the method execute_requests instead of calling it on the Future implementation?

// ...

async fn send(url: &str) {
    println!("Sending to URL {}", url);
    // Simulate the sending process
    sleep(Duration::from_millis(500)).await;
}

type Request = Pin<Box<dyn Future<Output = ()>>>;

struct Proxy;

impl Proxy {
    async fn execute_requests(&self) {
        let request_1 = async {
            send("url 1").await;
        };
        let request_2 = async {
            send("url 2").await;
        };

        let mut requests: Vec<Request> = vec![];
        requests.push(Box::pin(request_2));
        requests.push(Box::pin(request_1));

        while let Some(request) = requests.pop() {
            request.await;
        }
    }
}

impl Future for Proxy {
    type Output = ();

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let mut queue_process = Box::pin(self.execute_requests());
        queue_process.as_mut().poll(cx)
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let proxy = Proxy;
    // Executes both requests
    // Ok(proxy.execute_requests().await)
    // FIXME: Timeouts on the first request
    Ok(proxy.await)
}

Rust Playground

execute_requests is a simplification: it needs access to self to get the requests and other things.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
Deveres
  • 97
  • 7

1 Answers1

2

Each time you poll, you create a new execute_requests() future and poll it once. It will never advance to the next poll - next time your poll() is called, a new future will be created and will be polled once, and so on.

Instead, you should store the execute_requests() future within Proxy, and poll the same future:

struct Proxy(Pin<Box<dyn Future<Output = ()>>>);

impl Proxy {
    fn new() -> Self {
        Self(Box::pin(Self::execute_requests()))
    }
    
    async fn execute_requests() {
        let request_1 = async {
            send("url 1").await;
        };
        let request_2 = async {
            send("url 2").await;
        };

        let mut requests: Vec<Request> = vec![];
        requests.push(Box::pin(request_2));
        requests.push(Box::pin(request_1));

        while let Some(request) = requests.pop() {
            request.await;
        }
    }
}

impl Future for Proxy {
    type Output = ();

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        self.0.as_mut().poll(cx)
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let proxy = Proxy::new();
    // Executes both requests
    // Ok(proxy.execute_requests().await)
    // FIXME: Timeouts on the first request
    Ok(proxy.await)
}

Playground.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
  • Thanks, @chayim-friedman. Your solution works, but, what if I need to access `self` inside `execute_requests`? – Deveres Feb 18 '22 at 11:20
  • You will have to provide the data to it as parameters (you can also create a helper struct and pass it as parameter), but to explain more I'd need a concrete use case. – Chayim Friedman Feb 18 '22 at 11:48
  • I've created a new failed version: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=a71960d15171f76da9f0361d78c316ec – Deveres Feb 18 '22 at 13:55
  • 1
    A nitpick: better to use `get_or_insert_with(|| ...)`. – Chayim Friedman Feb 18 '22 at 14:03
  • 1
    Also, no need to `.unwrap()`: `get_or_insert()` returns the new value. – Chayim Friedman Feb 18 '22 at 14:04
  • Thanks, again. Changed that in https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=e2ca2978dc50816ed9a562f74d329dd6, although now I don't know how to solve the problem with the lifetimes. – Deveres Feb 18 '22 at 14:27
  • 1
    Basically what you have here is a self-referential struct: the future returned by `execute_requests()` needs to reference `self` which is captured inside it. So all usual techniques for self-referential structs apply here, too. Mainly, to avoid them (that's why I recommended to turn this into a parameter). If you need more help, I'd suggest you open a new question. – Chayim Friedman Feb 18 '22 at 14:38
  • Thanks again. I'll investigate it. – Deveres Feb 18 '22 at 14:42