0

With hyper, I need to make an HTTP connection and read the results. I want to wrap the whole thing in a timeout, so I start a thread and use recv_timeout to wait for it.

Wrapping just the send works, but I want to also wrap read_to_string. Here is the code:

fn send_request(url: &str) -> Result<Response, MyAppError> {
    let mut c = Client::new();
    let mut req = c.get(url);
    req.send().map_err(|e| MyAppError::TcpError(e))
}

fn get_url(url: &str, mut buf: &mut String) -> Result<u16, MyAppError> {
    let mut resp = send_request(url)?;
    resp.read_to_string(&mut buf).map_err(|e| MyAppError::ReadError(e))?;
    Ok(resp.status.to_u16())
}

fn get_url_with_timeout_2(url: &str, mut buf: &mut String) -> Result<u16, MyAppError> {
    let (tx, rx) = mpsc::channel();
    let url = url.to_owned();
    let t = thread::spawn(move || {
        match tx.send(get_url(&url, &mut buf)) {
            Ok(()) => {} // everything good
            Err(_) => {} // we have been released, no biggie
        }
    });
    match rx.recv_timeout(Duration::from_millis(5000)) {
        Ok(resp) => resp,
        Err(_) => Err(MyAppError::Timeout),
    }
}

Unfortunately I get a compiler error:

error[E0477]: the type `[closure@src/main.rs:53:25: 58:4 tx:std::sync::mpsc::Sender<std::result::Result<u16, MyAppError>>, url:std::string::String, buf:&mut std::string::String]` does not fulfill the required lifetime
  --> src/main.rs:53:11
   |
53 |   let t = thread::spawn(move || {
   |           ^^^^^^^^^^^^^
   |
   = note: type must outlive the static lifetime

How can I pass the buffer to the thread, let it fill it, and then print out the buffer back on the main thread?

(This is Rust 1.15.1.)

This repository gives a complete main.rs and shows three examples for getting the webpage:

  1. With no timeout.

  2. With a timeout just on send.

  3. With a timeout on the whole thing.

If you take out 3, it all compiles and runs. What can I change about 3 to make that work too?

By the way, making a web request is really just the "occasion" for this question. I've already seen this question about doing a timeout. My own interest is not the timeout per se, but about how to fill up a buffer on one thread and read it on another.

Share mutable object between threads suggests using Arc and Mutex to safely share data between threads. Here is an attempt at that:

fn get_url_with_timeout_3(url: &str) -> Result<(u16, String), MyAppError> {
    let (tx, rx) = mpsc::channel();
    let url = url.to_owned();
    let shbuf = Arc::new(Mutex::new(String::new()));
    let shbuf2 = shbuf.clone();
    let t = thread::spawn(move || {
        let mut c = Client::new();
        let mut req = c.get(&url);
        let mut ret = match req.send() {
            Ok(mut resp) => {
                let mut buf2 = shbuf2.lock().unwrap();
                match resp.read_to_string(&mut buf2) {
                    Ok(_) => Ok(resp.status.to_u16()),
                    Err(e) => Err(MyAppError::ReadError(e)),
                }
            }
            Err(e) => Err(MyAppError::TcpError(e)),
        };

        match tx.send(ret) {
            Ok(()) => {} // everything good
            Err(_) => {} // we have been released, no biggie
        }
    });
    match rx.recv_timeout(Duration::from_millis(5000)) {
        Ok(maybe_status_code) => {
            let buf2 = shbuf.lock().unwrap();
            Ok((maybe_status_code?, *buf2))
        }
        Err(_) => Err(MyAppError::Timeout),
    }
}

This gives me the error cannot move out of borrowed content for trying to return *buf2 (which makes sense, since it would be leaking data out of the Mutex), but I'm not sure how to express this in a structure where Rust can see the pattern.

If the request thread times out, it holds the lock forever — but I never try to read the buffer, so who cares. If the request thread finishes, it releases the lock and goes away, and the main thread will hold the only reference. I can reason that it is safe, but I'm not sure how to convince the compiler.

I'm not allowed to answer this question since it is supposedly a duplicate, but I've added 3 working implementations to my GitHub repository using the ideas from the comments.

Community
  • 1
  • 1
Paul A Jungwirth
  • 23,504
  • 14
  • 74
  • 93
  • Likely duplicate of http://stackoverflow.com/q/31373255/155423; but also http://stackoverflow.com/q/29540407/155423; http://stackoverflow.com/q/40035731/155423; http://stackoverflow.com/q/28654978/155423 (and other searches for "thread mutable" and friends). – Shepmaster Mar 11 '17 at 01:09
  • It is not clear to me how to apply those answers to my question, so perhaps you or someone else could explain. I'll add an attempt using Arc+Mutex that also doesn't compile. – Paul A Jungwirth Mar 11 '17 at 05:08
  • 1
    I think in your second example you will either have to return Result<(u16, Arc>), MyAppError> or return a clone of buf2 – Ankur Mar 11 '17 at 05:57
  • Both suggestions actually work @Ankur. Thanks for your help! The only other improvement I wish for is to pass in a pre-allocated `String` (like the first two examples) rather than returning one, but I haven't figured that one out yet. – Paul A Jungwirth Mar 11 '17 at 06:11
  • Actually, building on the "return the mutex" suggestion, I can also *allocate* the mutex outside the function and pass it in. Even nicer! – Paul A Jungwirth Mar 11 '17 at 06:17
  • 1
    Allocating the `Mutex` outside the threads (including the allocated `String`) is exactly what http://stackoverflow.com/a/31373349/155423 does. It also [links to another answer](http://stackoverflow.com/a/42195774/155423) that shows how to get the `String` back when the threads are done, so you don't need to return the `Arc>`. – Shepmaster Mar 11 '17 at 15:38

0 Answers0