35class GNet::TaskImp :
private FutureEventHandler
38 TaskImp( Task & , ExceptionSink es ,
bool sync ,
41 const G::Path & cd ,
const std::string & exec_error_format ,
const G::Identity &
id ) ;
60 std::pair<int,std::string> wait() ;
69 void onFutureEvent()
override ;
72 TaskImp(
const TaskImp & ) = delete ;
73 TaskImp( TaskImp && ) = delete ;
74 TaskImp & operator=(
const TaskImp & ) = delete ;
75 TaskImp & operator=( TaskImp && ) = delete ;
79 static void waitThread( TaskImp * , HANDLE ) ;
83 FutureEvent m_future_event ;
84 Timer<TaskImp> m_timer ;
87 G::threading::thread_type m_thread ;
88 static std::size_t m_zcount ;
91std::size_t GNet::TaskImp::m_zcount = 0U ;
95GNet::TaskImp::TaskImp( Task & task , ExceptionSink es ,
bool sync ,
98 const G::Path & cd ,
const std::string & exec_error_format ,
101 m_future_event(*this,es) ,
102 m_timer(*this,&TaskImp::onTimeout,es) ,
104 m_process( commandline.exe() , commandline.args() ,
105 G::NewProcess::Config()
108 .set_stdout(fd_stdout)
109 .set_stderr(fd_stderr)
111 .set_strict_exe(true)
114 .set_exec_error_exit(127)
115 .set_exec_error_format(exec_error_format) )
121 else if( !G::threading::works() )
123 if( G::threading::using_std_thread )
124 G_WARNING_ONCE(
"GNet::TaskImp::TaskImp: multi-threading disabled: running tasks synchronously" ) ;
125 waitThread(
this , m_future_event.handle() ) ;
129 G_ASSERT( G::threading::using_std_thread ) ;
131 m_thread = G::threading::thread_type( TaskImp::waitThread ,
this , m_future_event.handle() ) ;
135GNet::TaskImp::~TaskImp()
140 if( m_thread.joinable() )
141 m_process.kill(
true ) ;
142 if( m_thread.joinable() )
150bool GNet::TaskImp::zombify()
153 if( m_thread.joinable() )
156 m_process.kill(
true ) ;
159 m_timer.startTimer( 1U ) ;
161 const std::size_t threshold = 30U ;
162 if( m_zcount == threshold )
163 G_WARNING_ONCE(
"GNet::Task::dtor: large number of threads waiting for processes to finish" ) ;
173void GNet::TaskImp::onTimeout()
175 if( m_thread.joinable() )
178 G_LOG(
"TaskImp::dtor: waiting for killed process to terminate: pid " << m_process.id() ) ;
180 m_timer.startTimer( 1U ) ;
185 G_LOG(
"TaskImp::dtor: killed process has terminated: pid " << m_process.id() ) ;
191std::pair<int,std::string> GNet::TaskImp::wait()
193 m_process.waitable().wait() ;
194 int exit_code = m_process.waitable().get() ;
195 return { exit_code , m_process.waitable().output() } ;
198void GNet::TaskImp::waitThread( TaskImp * This , HANDLE handle )
203 This->m_process.waitable().wait() ;
204 FutureEvent::send( handle ) ;
208 FutureEvent::send( handle ) ;
212void GNet::TaskImp::onFutureEvent()
214 G_DEBUG(
"GNet::TaskImp::onFutureEvent: future event" ) ;
215 if( m_thread.joinable() )
218 int exit_code = m_process.waitable().get( std::nothrow ) ;
219 G_DEBUG(
"GNet::TaskImp::onFutureEvent: exit code " << exit_code ) ;
221 std::string pipe_output = m_process.waitable().output() ;
222 G_DEBUG(
"GNet::TaskImp::onFutureEvent: output: [" <<
G::Str::printable(pipe_output) <<
"]" ) ;
225 m_task->done( exit_code , pipe_output ) ;
231 const std::string & exec_error_format ,
const G::Identity &
id ) :
232 m_callback(callback) ,
234 m_exec_error_format(exec_error_format) ,
255 if( m_imp && m_imp->zombify() )
256 GDEF_IGNORE_RETURN m_imp.release() ;
268 G_ASSERT( !m_busy ) ;
269 m_imp = std::make_unique<TaskImp>( *
this , m_es ,
true , commandline ,
270 env , fd_stdin , fd_stdout , fd_stderr , cd ,
271 m_exec_error_format , m_id ) ;
272 return m_imp->wait() ;
279 G::NewProcess::Fd::devnull() ,
280 G::NewProcess::Fd::pipe() ,
281 G::NewProcess::Fd::devnull() ,
296 m_imp = std::make_unique<TaskImp>( *
this , m_es ,
false , commandline ,
297 env , fd_stdin , fd_stdout , fd_stderr , cd ,
298 m_exec_error_format , m_id ) ;
301void GNet::Task::done(
int exit_code ,
const std::string & output )
304 m_callback.onTaskDone( exit_code , output ) ;
A tuple containing an ExceptionHandler interface pointer and a bound 'exception source' pointer.
An abstract interface for callbacks from GNet::Task.
Task(TaskCallback &, ExceptionSink es, const std::string &exec_error_format={}, const G::Identity &=G::Identity::invalid())
Constructor for an object that can be start()ed or run().
void stop()
Attempts to kill the spawned process.
void start(const G::ExecutableCommand &commandline)
Starts the task by spawning a new process with the given command-line and also starting a thread to w...
std::pair< int, std::string > run(const G::ExecutableCommand &commandline, const G::Environment &env, G::NewProcess::Fd fd_stdin=G::NewProcess::Fd::devnull(), G::NewProcess::Fd fd_stdout=G::NewProcess::Fd::pipe(), G::NewProcess::Fd fd_stderr=G::NewProcess::Fd::devnull(), const G::Path &cd=G::Path())
Runs the task synchronously and returns the exit code and pipe output.
Holds a set of environment variables and also provides static methods to wrap getenv() and putenv().
static Environment minimal(bool sbin=false)
Returns a minimal, safe set of environment variables.
A structure representing an external program, holding a path and a set of arguments.
A combination of user-id and group-id, with a very low-level interface to the get/set/e/uid/gid funct...
A class for creating new processes.
A Path object represents a file system path.
static std::string printable(const std::string &in, char escape='\\')
Returns a printable representation of the given input string, using chacter code ranges 0x20 to 0x7e ...
static bool enabled() noexcept
Returns true if test features are enabled.
A RAII class to temporarily block signal delivery.
Wraps up a file descriptor for passing to G::NewProcess.