27

I have got function f; I want to throw exception 1s after start f. I can't modify f(). It it possible to do it in c++?

try {
   f();
}
catch (TimeoutException& e) {
//timeout
}
Newbie
  • 435
  • 2
  • 6
  • 13

4 Answers4

26

You can create a separate thread to run the call itself, and wait on a condition variable back in your main thread which will be signalled by the thread doing the call to f once it returns. The trick is to wait on the condition variable with your 1s timeout, so that if the call takes longer than the timeout you will still wake up, know about it, and be able to throw the exception - all in the main thread. Here is the code (live demo here):

#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>

using namespace std::chrono_literals;

int f()
{
    std::this_thread::sleep_for(10s); //change value here to less than 1 second to see Success
    return 1;
}

int f_wrapper()
{
    std::mutex m;
    std::condition_variable cv;
    int retValue;

    std::thread t([&cv, &retValue]() 
    {
        retValue = f();
        cv.notify_one();
    });

    t.detach();

    {
        std::unique_lock<std::mutex> l(m);
        if(cv.wait_for(l, 1s) == std::cv_status::timeout) 
            throw std::runtime_error("Timeout");
    }

    return retValue;    
}

int main()
{
    bool timedout = false;
    try {
        f_wrapper();
    }
    catch(std::runtime_error& e) {
        std::cout << e.what() << std::endl;
        timedout = true;
    }

    if(!timedout)
        std::cout << "Success" << std::endl;

    return 0;
}
Smeeheey
  • 9,906
  • 23
  • 39
  • 2
    Doesn't stop `f()` from running. If run again before `f()` exits from the 1st attempt is `f()` thread safe? Looks like this could make a lot more problems. – Richard Critten Nov 11 '16 at 15:39
  • 2
    The OP didn't state that stopping `f` is a requirement. Re-entrancy / thread safety issues can be resolved with this approach with further elaboration - what is here is enough to demonstrate the essence. – Smeeheey Nov 11 '16 at 15:42
  • why do you capture `m` in the lambda ? – fwyzard Jul 20 '18 at 07:01
  • Don't know. Edited to remove said capture – Smeeheey Jul 20 '18 at 10:41
  • The cv inside the lock is not protected by a mutex/lock, so mutex/lock is effectively useless here – ibayramli Feb 09 '23 at 04:56
14

You can also use std::packaged_task to run your function f() in another thread. This solution is more or less similar to this one, only that it uses standard classes to wrap things up.

std::packaged_task<void()> task(f);
auto future = task.get_future();
std::thread thr(std::move(task));
if (future.wait_for(1s) != std::future_status::timeout)
{
   thr.join();
   future.get(); // this will propagate exception from f() if any
}
else
{
   thr.detach(); // we leave the thread still running
   throw std::runtime_error("Timeout");
}

You can probably even try to wrap it into a function template, to allow calling arbitrary functions with timeout. Something along the lines of:

template <typename TF, typename TDuration, class... TArgs>
std::result_of_t<TF&&(TArgs&&...)> run_with_timeout(TF&& f, TDuration timeout, TArgs&&... args)
{
    using R = std::result_of_t<TF&&(TArgs&&...)>;
    std::packaged_task<R(TArgs...)> task(f);
    auto future = task.get_future();
    std::thread thr(std::move(task), std::forward<TArgs>(args)...);
    if (future.wait_for(timeout) != std::future_status::timeout)
    {
       thr.join();
       return future.get(); // this will propagate exception from f() if any
    }
    else
    {
       thr.detach(); // we leave the thread still running
       throw std::runtime_error("Timeout");
    }
}

And then use:

void f1() { ... }
call_with_timeout(f1, 5s);

void f2(int) { ... }
call_with_timeout(f2, 5s, 42);

int f3() { ... }
int result = call_with_timeout(f3, 5s);

This is an online example: http://cpp.sh/7jthw

Alex Che
  • 6,659
  • 4
  • 44
  • 53
  • why not use std::async instead of packaged_task, and let it handle the thread for you – MauricioJuanes Dec 04 '19 at 02:55
  • 1
    While std::async handles the thread for you, there's no way to detach from the thread in case of timeout. The caller code will still wait for the f() to complete at the point of future's destruction. Usually it means that the wrapper function or code scope will last as long as f() itself, which makes no sense for our goal. There are [ways to overcome this](https://stackoverflow.com/questions/21531096/can-i-use-stdasync-without-waiting-for-the-future-limitation), but then it's probably easier just to use the thread explicitly. – Alex Che Dec 04 '19 at 08:21
2

You can create a new thread and asynchronously wait for 1s to pass, and then throw an exception. However, exceptions can only be caught in the same thread where they're thrown, so, you cannot catch in the same thread where you called f(), like in your example code - but that's not a stated requirement, so it may be OK for you.

Only if f is guaranteed to return in less than 1s, can you do this synchronously:

  • store current time
  • call f()
  • wait for current time - stored time + 1s

But it may be quite difficult to prove that f in fact does return in time.

eerorika
  • 232,697
  • 12
  • 197
  • 326
0

This builds on Smeehee's example, if you need one that takes variable number of arguments (see also https://github.com/goblinhack/c-plus-plus-examples/blob/master/std_thread_timeout_template/README.md)

#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>

int my_function_that_might_block(int x)
{
    std::this_thread::sleep_for(std::chrono::seconds(10));
    return 1;
}

template<typename ret, typename T, typename... Rest>
using fn = std::function<ret(T, Rest...)>;

template<typename ret, typename T, typename... Rest>
ret wrap_my_slow_function(fn<ret, T, Rest...> f, T t, Rest... rest)
{
    std::mutex my_mutex;
    std::condition_variable my_condition_var;
    ret result = 0;

    std::unique_lock<std::mutex> my_lock(my_mutex);

    //
    // Spawn a thread to call my_function_that_might_block(). 
    // Pass in the condition variables and result by reference.
    //
    std::thread my_thread([&]() 
    {
        result = f(t, rest...);
        // Unblocks one of the threads currently waiting for this condition.
        my_condition_var.notify_one();
    });

    //
    // Detaches the thread represented by the object from the calling 
    // thread, allowing them to execute independently from each other. B
    //
    my_thread.detach();

    if (my_condition_var.wait_for(my_lock, std::chrono::seconds(1)) == 
            std::cv_status::timeout)  {
        //
        // Throw an exception so the caller knows we failed
        //
        throw std::runtime_error("Timeout");
    }

    return result;    
}

int main()
{
    // Run a function that might block

    try {
        auto f1 = fn<int,int>(my_function_that_might_block);
        wrap_my_slow_function(f1, 42);
        //
        // Success, no timeout
        //
    } catch (std::runtime_error& e) {
        //
        // Do whatever you need here upon timeout failure
        //
        return 1;
    }

    return 0;
}
Goblinhack
  • 2,859
  • 1
  • 26
  • 26