Allow for process termination when polling with isRunning

On *NIX, one needs to call `waitpid()` in order for process to exit the
zombie state. If one uses `Process::isRunning()` to emulate non-blocking
wait for child process termination, process will stay zombie and function
will always return true.

This commit changes `Process::isRunning()` to call `waitpid()` with
`WNOHANG` instead of using `kill()` when checking for child process (i.e.
the one we have ProcessHandle for), which allows for process termination.
Additional trickery with mutex and event is needed to prevent exceptions
when `Process::isRunning()` and/or `Process::wait()` is called concurrently
on the same handle from different threads.

Fixes #1097.
This commit is contained in:
Mike Gelfand
2016-01-09 02:16:55 +03:00
parent 279ea9d0e2
commit 0425866486
4 changed files with 82 additions and 11 deletions

View File

@@ -18,6 +18,7 @@
#include "Poco/Exception.h"
#include "Poco/NumberFormatter.h"
#include "Poco/Pipe.h"
#include "Poco/Thread.h"
#include <limits>
#include <errno.h>
#include <signal.h>
@@ -42,7 +43,10 @@ namespace Poco {
// ProcessHandleImpl
//
ProcessHandleImpl::ProcessHandleImpl(pid_t pid):
_pid(pid)
_pid(pid),
_mutex(),
_event(Event::EVENT_MANUALRESET),
_status()
{
}
@@ -60,24 +64,60 @@ pid_t ProcessHandleImpl::id() const
int ProcessHandleImpl::wait() const
{
int status;
int rc;
do
{
rc = waitpid(_pid, &status, 0);
}
while (rc < 0 && errno == EINTR);
if (rc != _pid)
if (wait(0) != _pid)
throw SystemException("Cannot wait for process", NumberFormatter::format(_pid));
const int status = _status.value();
if (WIFEXITED(status))
return WEXITSTATUS(status);
if (WIFSIGNALED(status))
return -WTERMSIG(status);
// This line should never be reached.
return std::numeric_limits<int>::max();
}
int ProcessHandleImpl::wait(int options) const
{
{
FastMutex::ScopedLock lock(_mutex);
if (_status.isSpecified())
{
return _pid;
}
}
int status;
int rc;
do
{
rc = waitpid(_pid, &status, options);
}
while (rc < 0 && errno == EINTR);
if (rc == _pid)
{
FastMutex::ScopedLock lock(_mutex);
_status = status;
_event.set();
}
else if (rc < 0 && errno == ECHILD)
{
// Looks like another thread was lucky and it should update the status for us shortly
_event.wait();
FastMutex::ScopedLock lock(_mutex);
if (_status.isSpecified())
{
rc = _pid;
}
}
return rc;
}
//
// ProcessImpl
//
@@ -251,7 +291,7 @@ void ProcessImpl::killImpl(PIDImpl pid)
bool ProcessImpl::isRunningImpl(const ProcessHandleImpl& handle)
{
return isRunningImpl(handle.id());
return handle.wait(WNOHANG) == 0;
}