What I want to is invoking a method foo()
with a timeout (say 1 minute). If its execution costs less than 1 minute, return the result. Otherwise an exception will be thrown. Here is the code:
//PRINT "START" IN THE LOG
auto m = std::make_shared<std::mutex>();
auto cv = std::make_shared<std::condition_variable>();
auto ready = std::make_shared<bool>(false);
auto response = std::make_shared<TResponse>();
auto exception = std::make_shared<FooException>();
exception->Code = ErrorCode::None;
std::thread([=]
{
std::unique_lock<std::mutex> lk(*m);
cv->wait(lk, [=]{ return *ready; });
try
{
//PRINT "PROCESS" IN THE LOG
auto r = foo();
*response = std::move(r);
}
catch(const FooException& e)
{
*exception = std::move(e);
}
lk.unlock();
cv->notify_one();
}).detach();
std::unique_lock<std::mutex> lk(*m);
*ready = true;
cv->notify_one();
auto status = cv->wait_for(lk, std::chrono::seconds(60));
if (status == std::cv_status::timeout)
{
//PRINT "TIMEOUT" IN THE LOG
//throw timeout exception
}
else
{
//PRINT "FINISH" IN THE LOG
if (exception->Code == ErrorCode::None)
{
return *response;
}
else
{
throw *exception;
}
}
You can see I add logs START/PROCESS/FINISH/TIMEOUT in the code, every time this method is executed, I can see START/PROCESS/FINISH or START/PROCESS/TIMEOUT pattern in the logs. However, sometimes the logs are START/PROCESS, without any FINISH/TIMEOUT. I think cv->wait_for
should block the current thread for 60 seconds at most, then it exists with either TIMEOUT or FINISH.
The foo()
method contains disk IO operations to network drives that sometimes hangs for more than 1 hour(the reason is not related to this question, and it can't be resolved now), I tried to replace foo
with a thread sleep, everything is working as expected. What's wrong with this code and how can I improve this?
Because you have no predicate in the cv->wait_for
call, the thread might be unblocked spuriously. However, it is strange that no FINISH/TIMEOUT is printed. So we might need more information here: What does happen with the program? Does it hang, does it throw, does it just exit, does it print in the line after cv->wait_for
?
You could try using std::async
and see if the same behavior appears (furthermore, it would greatly simplify your code):
std::future<int> res = std::async(foo);
std::future_status stat = res.wait_for(std::chrono::seconds(60));
if (stat != std::future_status::ready) {
std::cout << "Timed out..." << "\n";
} else {
try {
int result = res.get();
std::cout << "Result = " << result << std::endl;
} catch (const FooException& e) {
std::cerr << e.what() << '\n';
}
}
EDIT As pointed out in the comments by CuriouslyRecurringThoughts
the future of std::async
blocks in the destructor. If that is not an option, the following code uses a std::promise
and a detached thread instead:
std::promise<int> prom;
std::future<int> res = prom.get_future();
std::thread([p = std::move(prom)]() mutable {
try {
p.set_value(foo());
} catch (const std::exception& e) {
p.set_exception(std::current_exception());
}
}).detach();
Waiting for the std::future
is done as shown before.
It seems that despite the timed wait your main thread deadlocks because even when cv->wait_for
returns with timeout it still tries to lk.lock()
on the mutex which is currently locked by the second thread.
As mentioned on cppreference about wait_for
:
When unblocked, regardless of the reason, lock is reacquired and wait_for() exits.
I'm not sure why the promise/future solution didn't work for you since you didn't post that example here, but I've tried a simple version of it which seems to work even when the second thread "hangs":
using namespace std::chrono_literals;
std::cout << "START" << std::endl;
std::promise<void> p;
auto f = p.get_future();
std::thread t([p = std::move(p)]() mutable {
std::cout << "PROCESS" << std::endl;
std::this_thread::sleep_for(5min);
p.set_value();
});
auto status = f.wait_for(5s);
std::cout << (status == std::future_status::ready ? "FINISH" : "TIMEOUT") << std::endl;
t.join();
The output is as expected:
START
PROCESS
TIMEOUT
We can create a separate thread to run the call itself, and wait on a condition variable back in your main thread which will be signaled by the thread doing the call to foo once it returns.
The trick is to wait on the condition variable with your 60s 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.
Please find below a code example:
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std::chrono_literals;
int foo()
{
//std::this_thread::sleep_for(10s); //Will Return Success
std::this_thread::sleep_for(70s); //Will Return Timeout
return 1;
}
int foo_wrapper()
{
std::mutex m;
std::condition_variable cv;
int retValue;
std::thread t([&cv, &retValue]()
{
retValue = foo();
cv.notify_one();
});
t.detach();
{
std::unique_lock<std::mutex> lock(m);
if(cv.wait_for(lock, 60s) == std::cv_status::timeout)
throw std::runtime_error("Timeout");
}
return retValue;
}
int main()
{
bool timedout = false;
try {
foo_wrapper();
}
catch(std::runtime_error& e) {
std::cout << e.what() << std::endl;
timedout = true;
}
if(!timedout)
std::cout << "Success" << std::endl;
else
std::cout << "Failure" << std::endl;
return 0;
}
If we use std::this_thread::sleep_for(10s);
inside foo
will return SUCCESS
And, if we use std::this_thread::sleep_for(70s);
inside foo
will return TIMEOUT
I hope it helps!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With