In C++ I am running a bash command. The command is "echo | openssl s_client -connect zellowork.io:443"
But if this fails I want it to timeout in 4 seconds. The typical "/usr/bin/timeout 4 /usr/bin/sh -c" before the command does not work when run from the c++ code.
So I was trying to make a function that uses popen to send out the command and then waits for up to 4 seconds for the command to complete before it returns. The difficulty that I have is that fgets is blocking and it will wait for 20 seconds (on this command) before it unblocks and fails and I can not find anyway to see if there is something to read in a stream before I call fgets. Here is my code.
ExecuteCmdReturn Utils::executeCmdWithTimeout(string cmd, int ms)
{
ExecuteCmdReturn ecr;
ecr.success = false;
ecr.outstr = "";
FILE *in;
char buff[4096];
u64_t startTime = TWTime::ticksSinceStart();
u64_t stopTime = startTime + ms;
if(!(in = popen(cmd.c_str(), "r"))){
return ecr;
}
fseek(in,0,SEEK_SET);
stringstream ss("");
long int lastPos = 0;
long int newPos = 0;
while (TWTime::ticksSinceStart() < stopTime) {
newPos = ftell(in);
if (newPos > lastPos) {
lastPos = newPos;
if (fgets(buff, sizeof(buff), in) == NULL) {
break;
} else {
ss << buff;
}
} else {
msSleep(10);
}
}
auto rc = pclose(in);
ecr.success = true;
ecr.outstr = ss.str();
return ecr;
}
std::async to express that you may get your result asynchronously (a std::future<ExecuteCmdReturn>)std::future<T>::wait_for to timeout waiting for the result.Here's an example:
First, a surrogate for your executeCmdWithTimeout function that randomly sleeps between 0 and 5 seconds.
int do_something_silly()
{
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> distribution(0, 5);
auto sleep_time = std::chrono::seconds(distribution(gen));
std::cout << "Sleeping for " << sleep_time.count() << " seconds\n";
std::this_thread::sleep_for(sleep_time);
return 42;
}
Then, launching the task asynchronously and timing out on it:
int main()
{
auto silly_result = std::async(std::launch::async, [](){ return do_something_silly();});
auto future_status = silly_result.wait_for(3s);
switch(future_status)
{
case std::future_status::timeout:
std::cout << "timed out\n";
break;
case std::future_status::ready:
std::cout << "finished. Result is " << silly_result.get() << std::endl;
break;
case std::future_status::deferred:
std::cout << "The function hasn't even started yet.\n";
}
}
I used a lambda here even though I didn't need to because in your situation it will be easier because it looks like you are using a member function and you'll want to capture [this].
In your case, main would become ExecuteCmdReturn Utils::executeCmdWithTimeout(string cmd, int ms) and do_something_silly would become a private helper, named something like executeCmdWithTimeout_impl.
If you timeout waiting for the process to complete, you optionally kill the process so that you aren't wasting any extra cycles.
If you find yourself creating many short-lived threads like this, consider thread pooling. I've had a lot of success with boost::thread_pool (and if you end up going that direction, consider using Boost.Process for handling your process creation).
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