user846566
user846566

Reputation: 395

use popen without blocking

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;
}

Upvotes: 3

Views: 1085

Answers (1)

AndyG
AndyG

Reputation: 41090

  1. Use std::async to express that you may get your result asynchronously (a std::future<ExecuteCmdReturn>)
  2. Use 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].

Live Demo


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).

Upvotes: 4

Related Questions