On Monday, March 9, 2020 at 11:18:20 AM UTC, Frederick Gotham wrote:
> I want to write a robust function to start a second program and
> to retrieve the first line of text from its stdout. If any kind
> of error takes places, the function will simply return an empty
> string. No exceptions will be thrown.
Here's my fifth attempt. But first, an explanation:
* Each individual command is expected to return its first line of stdout within 2 seconds
* After all commands have returned their first line (or timed out), all outstanding threads are collectively given 8 seconds to finish, and after the 8 seconds, everything gets nuked (safely).
I'm still having a little trouble with segfaults inside the function "Handle_Cleanup", but anyway here's my fifth attempt. Oh by the way I realise I went a little crazy with keeping everything inside the function "Run_Command_string", I can of course take some stuff outside the function.
ATTEMPT FIVE:
#include <boost/process.hpp> /* boost::process::child, ipstream */
#include <boost/chrono.hpp> /* boost::chrono::seconds */
#include <boost/thread.hpp> /* try_join_for */
#include <boost/lockfree/queue.hpp> /* boost::lockfree:queue */
#include <string>
/* ===== For Testing ===== */
#include <iostream>
using std::cout;
using std::endl;
/* ======================= */
/* The next function is to run a command and capture its
first line of stdout. On failure, returns empty string. */
std::string Run_Command_string(std::string const &str_cmd, unsigned const timeout = 2u) noexcept
{
struct Thread_Manager {
struct Handle {
boost::process::ipstream *p_ip;
boost::process::child *p_child;
boost::thread *p_thread;
};
boost::lockfree::queue<Handle> ctr;
Thread_Manager(void) : ctr(600u) {} /* Pre-allocate for 600 elements */
static void Cleanup_Handle(Handle const &h)
{
/* I'm not entirely sure why but I've had
to do some strange stuff in this
function in order to prevent a segfault */
h.p_child->terminate();
delete h.p_thread;
h.p_ip->pipe().close();
boost::this_thread::sleep_for(boost::chrono::milliseconds(100u)); /* Segfault if you don't do this */
delete h.p_ip;
delete h.p_child;
}
~Thread_Manager(void)
{
cout << "==============================================\n"
" BEGIN THREAD CLEAN-UP\n"
"==============================================\n";
bool man_alive = false;
Handle h;
for ( ; ctr.pop(h); Cleanup_Handle(h) )
{
if ( nullptr == h.p_thread )
{
cout << "= = = Alpha Loop: Thread pointer is nullptr = = =\n";
continue;
}
if ( false == h.p_thread->joinable() )
{
cout << "= = = Alpha Loop: Thread object is not for an actual thread = = =\n";
continue;
}
if ( true == h.p_thread->try_join_for(boost::chrono::milliseconds(1u)) )
{
cout << "= = = Alpha Loop: Thread object is for a finished thread = = =\n";
continue;
}
/* If control reaches here, we have at least one
thread that hasn't finished yet */
cout << "= = = Alpha Loop: Thread object is for thread still running = = =\n";
man_alive = true;
break; /* Note that the three pointers haven't been deleted when we break out */
}
if ( false == man_alive )
{
/* Nothing more to do, we can return from the destructor */
cout << "= = = All threads finished. No need for amnesty. = = =\n";
return;
}
/* If control reaches here, there are still some threads running,
and the first one is pointed to by 'p' */
/* Give everything 8 seconds to finish up */
cout << "============================ BEGIN 8 SECOND AMNESTY =====================================\n";
boost::this_thread::sleep_for(boost::chrono::seconds(8u));
cout << "============================ END 8 SECOND AMNESTY =====================================\n";
/* Now just kill any threads still running */
goto Label_To_Jump_Into_Loop; /* Because the current handle is still to be deal with */
for ( ; ctr.pop(h); Cleanup_Handle(h) )
{
Label_To_Jump_Into_Loop:
if ( nullptr == h.p_thread )
{
cout << "= = = Beta Loop: Thread pointer is nullptr = = =\n";
continue;
}
if ( false == h.p_thread->joinable() )
{
cout << "= = = Beta Loop: Thread object is not for an actual thread = = =\n";
continue;
}
if ( true == h.p_thread->try_join_for(boost::chrono::milliseconds(1)) )
{
cout << "= = = Beta Loop: Thread object is for a finished thread = = =\n";
continue;
}
/* If control reaches here, thread that hasn't finished yet */
cout << "= = = Beta Loop: Killing thread that didn't stop in time = = =\n";
/* To kill a thread, you call interrupt and immediately follow
it with either "join" or "detach" */
//cout << "From Start\n";
h.p_thread->interrupt();
h.p_thread->detach();
//cout << "To Finish\n";
}
cout << "> = > = > = > = > = > = Destruction of Thread_Manager is finitio > = > = > = > = > = > =\n";
/* All threads are now dead and gone */
return;
}
};
using std::string;
namespace bp = boost::process;
try
{
static Thread_Manager mgr;
string retval;
Thread_Manager::Handle handle{new bp::ipstream, nullptr, nullptr}; /* First pointer is for capturing stdin from child process */
handle.p_child = new bp::child(bp::search_path("sh"), "-c", str_cmd, bp::std_in.close(), bp::std_out > *handle.p_ip, bp::std_err > bp::null);
boost::mutex mutex_for_messaging;
boost::atomic_flag have_first_line_yet;
boost::mutex::scoped_lock lock_for_condition(mutex_for_messaging);
boost::condition_variable have_first_line_yet_CONDITION;
//cout << "About to start Reader thread\n";
/* Start second thread here to capture the first line of stdout */
handle.p_thread = new boost::thread(
[handle, &retval, &have_first_line_yet, &have_first_line_yet_CONDITION] /* Must take 'handle' by value */
{
try
{
/* Retain the first line of text */
try
{
std::getline(*handle.p_ip,retval);
}
catch (...)
{
retval.clear();
}
have_first_line_yet.test_and_set();
have_first_line_yet_CONDITION.notify_one(); /* The main thread is doing 'wait_for' */
/* ==== DANGER DANGER DANGER ==== Run_Command_string might have returned by this point ==== DANGER ==== DANGER */
/* so do not use retval, have_first_line_yet, bHave_first_line_yet past this point */
try
{
/* Now just discard the rest of the output */
for (string tmp; std::getline(*handle.p_ip,tmp); ) { /* Do Nothing */ }
}
catch (...)
{
/* Do Nothing */
}
}
catch (...)
{
/* Do Nothing */
}
}
);
mgr.ctr.push(handle);
boost::thread &reader = *handle.p_thread;
bool const did_not_timeout = (boost::cv_status::no_timeout == have_first_line_yet_CONDITION.wait_for(lock_for_condition, boost::chrono::seconds(timeout)));
/* A few seconds might pass here */
if ( did_not_timeout && have_first_line_yet.test_and_set() )
{
/* We got the first line within the timeout! :-) */
return retval;
}
else
{
/* Program has frozen, so just return an empty string */
reader.interrupt();
/* reader.detach(); DON'T DO THIS */
handle.p_child->terminate(); /* This might get terminated a second time by Thread_Manager */
}
}
catch (...)
{
/* Do Nothing */
}
return string();
}
auto main(void) -> int
{
for (unsigned i = 0; i != 12; ++i)
{
std::string const &retval1 = Run_Command_string("find / | grep bin");
cout << "==== ONE ==== " + retval1 + "\n";
std::string const &retval2 = Run_Command_string("find / | grep usr");
cout << "==== TWO ==== " + retval2 + "\n";
std::string const &retval3 = Run_Command_string("find / | grep lib");
cout << "==== TR3 ==== " + retval3 + "\n";
}
}