318 lines
10 KiB
C++
318 lines
10 KiB
C++
//
|
|
// Boost.Process
|
|
// ~~~~~~~~~~~~~
|
|
//
|
|
// Copyright (c) 2006, 2007 Julio M. Merino Vidal
|
|
// Copyright (c) 2008 Ilya Sokolov, Boris Schaeling
|
|
// Copyright (c) 2009 Boris Schaeling
|
|
// Copyright (c) 2010 Felipe Tanus, Boris Schaeling
|
|
//
|
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
//
|
|
|
|
/**
|
|
* \file boost/process/detail/basic_status_service.hpp
|
|
*
|
|
* Includes the declaration of the basic status service class.
|
|
*/
|
|
|
|
#ifndef BOOST_PROCESS_DETAIL_BASIC_STATUS_SERVICE_HPP
|
|
#define BOOST_PROCESS_DETAIL_BASIC_STATUS_SERVICE_HPP
|
|
|
|
#include <boost/process/config.hpp>
|
|
|
|
#if defined(BOOST_POSIX_API)
|
|
# include <boost/process/operations.hpp>
|
|
# include <string>
|
|
# include <sys/types.h>
|
|
# include <sys/wait.h>
|
|
#elif defined(BOOST_WINDOWS_API)
|
|
# include <windows.h>
|
|
#else
|
|
# error "Unsupported platform."
|
|
#endif
|
|
|
|
#include <boost/process/pid_type.hpp>
|
|
#include <boost/process/detail/status_impl.hpp>
|
|
#include <boost/asio.hpp>
|
|
#include <boost/thread.hpp>
|
|
#include <boost/shared_ptr.hpp>
|
|
#include <boost/make_shared.hpp>
|
|
#include <boost/scoped_ptr.hpp>
|
|
#include <boost/system/error_code.hpp>
|
|
#include <boost/unordered_map.hpp>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
|
|
namespace boost {
|
|
namespace process {
|
|
namespace detail {
|
|
|
|
/**
|
|
* The basic_status_service class provides the service to wait for processes
|
|
* synchronously and asynchronously.
|
|
*/
|
|
template <typename StatusImplementation = status_impl>
|
|
class basic_status_service
|
|
: public boost::asio::detail::service_base<StatusImplementation>
|
|
{
|
|
public:
|
|
explicit basic_status_service(boost::asio::io_service &io_service)
|
|
: boost::asio::detail::service_base<StatusImplementation>(io_service),
|
|
#if defined(BOOST_POSIX_API)
|
|
interrupt_pid_(-1),
|
|
pids_(0)
|
|
#elif defined(BOOST_WINDOWS_API)
|
|
run_(true)
|
|
#endif
|
|
{
|
|
#if defined(BOOST_WINDOWS_API)
|
|
handles_.push_back(CreateEvent(NULL, FALSE, FALSE, NULL));
|
|
if (handles_[0] == NULL)
|
|
BOOST_PROCESS_THROW_LAST_SYSTEM_ERROR("CreateEvent() failed");
|
|
work_thread_ = boost::thread(
|
|
&basic_status_service<StatusImplementation>::work_thread, this);
|
|
#endif
|
|
}
|
|
|
|
~basic_status_service()
|
|
{
|
|
#if defined(BOOST_POSIX_API)
|
|
boost::unique_lock<boost::mutex> lock(work_thread_mutex_);
|
|
bool worker_thread_active = (pids_ != 0);
|
|
lock.unlock();
|
|
if (worker_thread_active)
|
|
{
|
|
stop_work_thread();
|
|
work_thread_.join();
|
|
}
|
|
#elif defined(BOOST_WINDOWS_API)
|
|
stop_work_thread();
|
|
work_thread_.join();
|
|
CloseHandle(handles_[0]);
|
|
#endif
|
|
}
|
|
|
|
typedef boost::shared_ptr<StatusImplementation> implementation_type;
|
|
|
|
void construct(implementation_type &impl)
|
|
{
|
|
impl = boost::make_shared<StatusImplementation>();
|
|
boost::unique_lock<boost::mutex> lock(work_thread_mutex_);
|
|
impls_.push_back(impl);
|
|
}
|
|
|
|
void destroy(implementation_type &impl)
|
|
{
|
|
boost::unique_lock<boost::mutex> lock(work_thread_mutex_);
|
|
typename std::vector<implementation_type>::iterator it =
|
|
std::find(impls_.begin(), impls_.end(), impl);
|
|
if (it != impls_.end())
|
|
impls_.erase(it);
|
|
#if defined(BOOST_WINDOWS_API)
|
|
interrupt_work_thread();
|
|
work_thread_cond_.wait(work_thread_mutex_);
|
|
impl->clear(handles_);
|
|
work_thread_cond_.notify_all();
|
|
#endif
|
|
impl.reset();
|
|
}
|
|
|
|
int wait(implementation_type &impl, pid_type pid)
|
|
{
|
|
boost::system::error_code ec;
|
|
int status = impl->wait(pid, ec);
|
|
#if defined(BOOST_POSIX_API)
|
|
if (ec.value() == ECHILD)
|
|
{
|
|
boost::unique_lock<boost::mutex> lock(work_thread_mutex_);
|
|
boost::unordered_map<pid_t, int>::iterator it = statuses_.find(pid);
|
|
if (it == statuses_.end())
|
|
{
|
|
work_thread_cond_.wait(work_thread_mutex_);
|
|
it = statuses_.find(pid);
|
|
}
|
|
if (it != statuses_.end())
|
|
{
|
|
status = it->second;
|
|
statuses_.erase(it);
|
|
ec.clear();
|
|
}
|
|
}
|
|
#endif
|
|
boost::asio::detail::throw_error(ec);
|
|
return status;
|
|
}
|
|
|
|
template <typename Handler>
|
|
void async_wait(implementation_type &impl, pid_type pid, Handler handler)
|
|
{
|
|
#if defined(BOOST_POSIX_API)
|
|
boost::unique_lock<boost::mutex> lock(work_thread_mutex_);
|
|
if (++pids_ == 1)
|
|
{
|
|
work_.reset(new boost::asio::io_service::work(
|
|
this->get_io_service()));
|
|
work_thread_ = boost::thread(
|
|
&basic_status_service<StatusImplementation>::work_thread,
|
|
this);
|
|
}
|
|
impl->async_wait(pid, this->get_io_service().wrap(handler));
|
|
#elif defined(BOOST_WINDOWS_API)
|
|
HANDLE handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION,
|
|
FALSE, pid);
|
|
if (handle == NULL)
|
|
BOOST_PROCESS_THROW_LAST_SYSTEM_ERROR("OpenProcess() failed");
|
|
boost::unique_lock<boost::mutex> lock(work_thread_mutex_);
|
|
if (!work_)
|
|
work_.reset(new boost::asio::io_service::work(
|
|
this->get_io_service()));
|
|
interrupt_work_thread();
|
|
work_thread_cond_.wait(work_thread_mutex_);
|
|
handles_.push_back(handle);
|
|
impl->async_wait(handle, this->get_io_service().wrap(handler));
|
|
work_thread_cond_.notify_all();
|
|
#endif
|
|
}
|
|
|
|
private:
|
|
void shutdown_service()
|
|
{
|
|
#if defined(BOOST_WINDOWS_API)
|
|
boost::unique_lock<boost::mutex> lock(work_thread_mutex_);
|
|
work_.reset();
|
|
#endif
|
|
}
|
|
|
|
void work_thread()
|
|
{
|
|
#if defined(BOOST_POSIX_API)
|
|
for (;;)
|
|
{
|
|
int status;
|
|
pid_t pid = ::wait(&status);
|
|
if (pid == -1)
|
|
{
|
|
if (errno != EINTR)
|
|
BOOST_PROCESS_THROW_LAST_SYSTEM_ERROR("wait(2) failed");
|
|
}
|
|
else if (interrupted(pid))
|
|
{
|
|
// On POSIX the only reason to interrupt is to break out.
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
boost::unique_lock<boost::mutex> lock(work_thread_mutex_);
|
|
bool regchild = false;
|
|
for (typename std::vector<implementation_type>::iterator it =
|
|
impls_.begin(); it != impls_.end(); ++it)
|
|
regchild |= (*it)->complete(pid, status);
|
|
if (regchild && --pids_ == 0)
|
|
{
|
|
work_.reset();
|
|
break;
|
|
}
|
|
else if (!regchild)
|
|
{
|
|
statuses_.insert(boost::unordered_map<pid_t, int>::
|
|
value_type(pid, status));
|
|
work_thread_cond_.notify_all();
|
|
}
|
|
}
|
|
}
|
|
#elif defined(BOOST_WINDOWS_API)
|
|
for (;;)
|
|
{
|
|
DWORD res = WaitForMultipleObjects(handles_.size(), &handles_[0],
|
|
FALSE, INFINITE);
|
|
if (res == WAIT_FAILED)
|
|
BOOST_PROCESS_THROW_LAST_SYSTEM_ERROR(
|
|
"WaitForMultipleObjects() failed");
|
|
else if (res - WAIT_OBJECT_0 == 0)
|
|
{
|
|
boost::unique_lock<boost::mutex> lock(work_thread_mutex_);
|
|
if (!run_)
|
|
break;
|
|
work_thread_cond_.notify_all();
|
|
work_thread_cond_.wait(work_thread_mutex_);
|
|
}
|
|
else if (res - WAIT_OBJECT_0 > 0)
|
|
{
|
|
HANDLE handle = handles_[res - WAIT_OBJECT_0];
|
|
DWORD exit_code;
|
|
if (!GetExitCodeProcess(handle, &exit_code))
|
|
BOOST_PROCESS_THROW_LAST_SYSTEM_ERROR(
|
|
"GetExitCodeProcess() failed");
|
|
boost::unique_lock<boost::mutex> lock(work_thread_mutex_);
|
|
for (typename std::vector<implementation_type>::iterator it =
|
|
impls_.begin(); it != impls_.end(); ++it)
|
|
(*it)->complete(handle, exit_code);
|
|
std::vector<HANDLE>::iterator it = handles_.begin();
|
|
std::advance(it, res - WAIT_OBJECT_0);
|
|
handles_.erase(it);
|
|
if (handles_.size() == 1)
|
|
work_.reset();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void interrupt_work_thread()
|
|
{
|
|
#if defined(BOOST_POSIX_API)
|
|
// By creating a child process which immediately exits
|
|
// we interrupt wait().
|
|
std::vector<std::string> args;
|
|
args.push_back("-c");
|
|
args.push_back("'exit'");
|
|
interrupt_pid_ = create_child("/bin/sh", args).get_id();
|
|
#elif defined(BOOST_WINDOWS_API)
|
|
// By signaling the event in the first slot WaitForMultipleObjects()
|
|
// will return. The work thread won't do anything except checking if
|
|
// it should continue to run.
|
|
if (!SetEvent(handles_[0]))
|
|
BOOST_PROCESS_THROW_LAST_SYSTEM_ERROR("SetEvent() failed");
|
|
#endif
|
|
}
|
|
|
|
#if defined(BOOST_POSIX_API)
|
|
bool interrupted(pid_t pid)
|
|
{
|
|
boost::mutex::scoped_lock lock(work_thread_mutex_);
|
|
return interrupt_pid_ == pid;
|
|
}
|
|
#endif
|
|
|
|
void stop_work_thread()
|
|
{
|
|
boost::mutex::scoped_lock lock(work_thread_mutex_);
|
|
#if defined(BOOST_WINDOWS_API)
|
|
// Access to run_ must be sychronized with running().
|
|
run_ = false;
|
|
#endif
|
|
// Access to interrupt_pid_ must be sychronized with interrupted().
|
|
interrupt_work_thread();
|
|
}
|
|
|
|
boost::scoped_ptr<boost::asio::io_service::work> work_;
|
|
std::vector<implementation_type> impls_;
|
|
boost::mutex work_thread_mutex_;
|
|
boost::thread work_thread_;
|
|
boost::condition_variable_any work_thread_cond_;
|
|
#if defined(BOOST_POSIX_API)
|
|
pid_t interrupt_pid_;
|
|
int pids_;
|
|
boost::unordered_map<pid_t, int> statuses_;
|
|
#elif defined(BOOST_WINDOWS_API)
|
|
bool run_;
|
|
std::vector<HANDLE> handles_;
|
|
#endif
|
|
};
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|