6

I have a function in Rust (that I did not write) which either returns in milliseconds or grinds for ~10 minutes before failing.

I would like to wrap the call to this function in something that returns an Option which is None if it takes over 10 seconds to run, and contains the result if it took less time to run. However I haven't been able to find any way to interrupt the evaluation of this function once it has been called.

For example:

// This is the unpredictable function
fn f() {
    // Wait randomly for between 0 and 10 seconds
    let mut rng = rand::thread_rng();
    std::thread::sleep(std::time::Duration::from_secs(rng.gen_range(0, 10)));
}

fn main() {
    for _ in 0..100 {
        // Run f() here but so that the whole loop takes no more than 100 seconds
        // by aborting f() if it takes longer than 1 second
    }
}

I have found some methods that might work using futures with timeouts, but I would like to minimize overhead, and I am not sure how expensive it would be to create a future for every call to this function given that it will be called so many times.

Thanks

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
user42257
  • 61
  • 2
  • 1
    Does this answer your question? [What is the correct way in Rust to create a timeout for a thread or a function?](https://stackoverflow.com/questions/36181719/what-is-the-correct-way-in-rust-to-create-a-timeout-for-a-thread-or-a-function) – SCappella Jan 18 '20 at 23:50
  • @SCappella I saw that thread but I don't think it quite answers my case because that involves spawning entire threads, which is far too expensive for what I am doing. I think Peter's solution below though solves this by using futures instead of threads. – user42257 Jan 23 '20 at 04:24
  • Alternatively, just solve the halting problem /s (Obviously kidding). This is a very well written question, @user42257. – Ben Aubin Sep 08 '20 at 01:19

1 Answers1

4

The overhead of asynchronous execution is likely to be minimal, especially since your function runs in at least milliseconds, which is already very slow.

Something like this will work:

use rand::Rng;
use std::time::Duration;
use tokio::time::timeout;

async fn f() -> i32 {
    // Wait randomly for between 0 and 10 seconds
    let mut rng = rand::thread_rng();
    tokio::time::delay_for(Duration::from_secs(rng.gen_range(0, 10))).await;
    // return something
    1000
}

#[tokio::main]
async fn main() {
    for _ in 0..100 {
        if let Ok(result) = timeout(Duration::from_secs(1), f()).await {
            println!("result = {}", result);
        } else {
            // took too long
        }
    }
}

As ever with performance, if you are concerned that one particular approach could be slow, test the theory rather than assuming you're right. Performance characteristics can often be surprising.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • Thanks for your response, but it still appears to be willing to wait for more than 1 second between printing "result = 1000". That is, I don't think the timeout is actually aborting the evaluation and the else statement is never run. – user42257 Jan 23 '20 at 04:32
  • @user42257 `thread::sleep` is a blocking operation, so shouldn't be used in an `async` function. That's why this doesn't work right. Your function is probably also blocking, so threads are the only option. Unless your function was designed for `async` use, it just blocks the whole thread. – SCappella Jan 23 '20 at 20:31
  • @SCappella Ah, I didn't know that tokio::time::timeout had that restriction. I'll look into how to do this with threads starting with the link you recommended before. – user42257 Jan 23 '20 at 22:32
  • My bad! I blindly copied OP's code, and didn't test. I've updated my answer now. – Peter Hall Jan 24 '20 at 18:18