2
0
mirror of https://github.com/boostorg/process.git synced 2026-01-20 04:42:24 +00:00

Compare commits

..

9 Commits

Author SHA1 Message Date
Klemens Morgenstern
deeb975c33 missing files 2022-09-17 20:41:34 +08:00
Klemens Morgenstern
dcee0936c1 Added job-object code - making it stale now though. 2022-09-17 20:36:48 +08:00
Klemens Morgenstern
4cc469b2a4 Merge pull request #252 from grtowel1510f/patch-1
fix issue #251 - fix simple shell command in posix
2022-09-14 11:38:37 +08:00
Klemens Morgenstern
6e4d1e29d2 Merge pull request #271 from boostorg/shell_v2
Shell v2
2022-09-02 20:30:03 +08:00
Klemens Morgenstern
dada865fd0 Merge pull request #269 from boostorg/klemens-morgenstern-patch-2
Closes #266
2022-09-02 20:29:20 +08:00
Klemens Morgenstern
380dd1b00f Merge pull request #268 from boostorg/klemens-morgenstern-patch-1
Closes #267
2022-09-02 20:28:50 +08:00
Klemens Morgenstern
7cb7af6c8b Closes #267 2022-08-31 15:40:57 +08:00
Klemens Morgenstern
90cbe7cec0 Closes #266 2022-08-31 15:35:51 +08:00
grtowel1510f
8a61f8daa3 fix issue #251 - fix simple shell command in posix
see issue #251 for description.
2022-05-21 14:59:37 +00:00
16 changed files with 1076 additions and 7 deletions

5
.gitignore vendored
View File

@@ -32,3 +32,8 @@
/notes_p.txt
.settings
*.make
*.cmake
*.rsp
*.marks
cmake-*

View File

@@ -139,7 +139,7 @@ struct exe_cmd_init<char> : boost::process::detail::api::handler_base_ext
}
static exe_cmd_init cmd_shell(std::string&& cmd)
{
std::vector<std::string> args = {"-c", "\"" + cmd + "\""};
std::vector<std::string> args = {"-c", cmd};
std::string sh = shell().string();
return exe_cmd_init(

View File

@@ -155,8 +155,8 @@ class executor
void write_error(const std::error_code & ec, const char * msg)
{
//I am the child
const auto len = std::strlen(msg);
int data[2] = {ec.value(), static_cast<int>(len + 1)};
const auto len = static_cast<int>(std::strlen(msg));
int data[2] = {ec.value(), len + 1};
boost::ignore_unused(::write(_pipe_sink, &data[0], sizeof(int) * 2));
boost::ignore_unused(::write(_pipe_sink, msg, len));

View File

@@ -263,7 +263,7 @@ public:
auto st1 = key + ::boost::process::detail::equal_sign<Char>();
while (*p != nullptr)
{
const int len = std::char_traits<Char>::length(*p);
const auto len = std::char_traits<Char>::length(*p);
if ((std::distance(st1.begin(), st1.end()) < len)
&& std::equal(st1.begin(), st1.end(), *p))
break;

View File

@@ -0,0 +1,417 @@
// Copyright (c) 2022 Klemens D. Morgenstern
//
// 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)
#ifndef BOOST_PROCESS_V2_GROUP_IMPL_HPP
#define BOOST_PROCESS_V2_GROUP_IMPL_HPP
#include <boost/process/v2/detail/config.hpp>
#if defined(BOOST_PROCESS_V2_STANDALONE)
#include <asio/any_io_executor.hpp>
#include <asio/append.hpp>
#include <asio/compose.hpp>
#include <asio/dispatch.hpp>
#include <asio/post.hpp>
#include <asio/windows/basic_object_handle.hpp>
#else
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/append.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/windows/basic_object_handle.hpp>
#endif
BOOST_PROCESS_V2_BEGIN_NAMESPACE
struct single_process_exit;
namespace detail
{
BOOST_PROCESS_V2_DECL bool job_object_is_empty(HANDLE job_object, error_code & ec);
template<typename Allocator>
BOOST_PROCESS_V2_DECL std::pair<DWORD, DWORD> job_object_something_exited(HANDLE job_object, std::vector<int> & store, Allocator alloc_, error_code & ec)
{
// let's start with a
typename std::aligned_storage<sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST) * 16, alignof(JOBOBJECT_BASIC_PROCESS_ID_LIST)>::type storage;
JOBOBJECT_BASIC_PROCESS_ID_LIST * id_list = static_cast<JOBOBJECT_BASIC_PROCESS_ID_LIST*>(storage);
if (!QueryInformationJobObject(job_object,
JobObjectBasicProcessIdList,
id_list, sizeof(storage), nullptr))
ec = detail::get_last_error();
if (ec)
return {};
using allocator_type = std::allocator_traits<Allocator>::rebind_alloc<JOBOBJECT_BASIC_PROCESS_ID_LIST>;
allocator_type alloc{alloc_};
std::size_t sz = (std::numeric_limits<std::size_t>::max)();
// dit not fit in the buffer, alloc a buffer
if (id_list.NumberOfAssignedProcesses != id_list.NumberOfProcessIdsInList)
{
// required size:
const additional_size = id_list.NumberOfAssignedProcesses - 1;
sz = (additional_size / (sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST) / sizeof(ULONG_PTR))) + 1;
id_list = std::allocator_traits<allocator_type>::allocate(alloc_, sz);
if (!QueryInformationJobObject(job_object,
JobObjectBasicProcessIdList,
id_list, sz, nullptr))
{
ec = detail::get_last_error();
goto complete;
}
}
std::pair<DWORD, DWORD> result;
auto * begin = id_list->ProcessIdList,
* end = id_list->ProcessIdList + id_list->NumberOfProcessIdsInList;
for (auto itr = store.begin(); itr != store.end(); itr++)
{
// cross check if it's in the job object
auto it = std::find(begin, end, *itr);
if (it == end) // missing a job
{
result.first = *itr;
// ::GetExitCodeProcess(); // this can't be done based on PID, i need the f'in handle.
}
}
complete:
if (sz != (std::numeric_limits<std::size_t>::max)())
std::allocator_traits<allocator_type>::deallocate(alloc_, id_list, sz);
return result;
}
#if !defined(BOOST_PROCESS_V2_HEADER_ONLY)
//extern template
#endif
struct basic_group_impl_base
{
using native_handle_type = HANDLE;
struct job_object_deleter
{
bool terminate_on_delete = true;
void operator()(HANDLE h)
{
if (h != nullptr && terminate_on_delete)
::TerminateJobObject(h, 255u);
if (h != nullptr && h != INVALID_HANDLE_VALUE)
::CloseHandle(h);
}
};
BOOST_PROCESS_V2_DECL void add(DWORD pid, HANDLE handle, error_code & ec);
BOOST_PROCESS_V2_DECL bool contains(DWORD pid, error_code & ec);
BOOST_PROCESS_V2_DECL void wait_one(DWORD &pid, int &exit_code, error_code & ec);
BOOST_PROCESS_V2_DECL void wait_all(error_code & ec);
BOOST_PROCESS_V2_DECL void interrupt(error_code & ec);
BOOST_PROCESS_V2_DECL void request_exit(error_code & ec);
BOOST_PROCESS_V2_DECL void terminate(error_code & ec);
bool is_open() const
{
return job_object_.get() != nullptr;
}
struct initializer_t
{
basic_group_impl_base * self;
error_code & success_ec;
error_code on_setup(windows::default_launcher & launcher,
const filesystem::path &,
const std::wstring &) const
{
launcher.creation_flags |= CREATE_SUSPENDED;
return error_code {};
};
void on_success(windows::default_launcher & launcher,
const filesystem::path &,
const std::wstring &) const
{
if (!::AssignProcessToJobObject(self->job_object_.get(), launcher.process_information.hProcess))
success_ec = detail::get_last_error();
if (!success_ec &&
::ResumeThread(launcher.process_information.hThread) == static_cast<DWORD>(-1))
success_ec = detail::get_last_error();
};
};
initializer_t get_initializer(error_code & ec)
{
return initializer_t{this, ec};
}
basic_group_impl_base(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context & ctx)
{
}
~basic_group_impl_base()
{
}
void detach() {job_object_.get_deleter().terminate_on_delete = false;}
protected:
std::unique_ptr<void, job_object_deleter> job_object_{CreateJobObject(nullptr, nullptr)};
};
template<typename Executor = BOOST_PROCESS_V2_ASIO_NAMESPACE::any_io_executor>
struct basic_group_impl : basic_group_impl_base
{
// Get the native group handle
native_handle_type native_handle() {return port_.native_handle();}
basic_group_impl(Executor exec)
: basic_group_impl_base(BOOST_PROCESS_V2_ASIO_NAMESPACE::query(exec,
BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::context)),
port_(exec, ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1))
{
error_code ec;
JOBOBJECT_ASSOCIATE_COMPLETION_PORT port{job_object_.get(), port_.native_handle()};
if (!::SetInformationJobObject(
job_object_.get(),
JobObjectAssociateCompletionPortInformation,
&port, sizeof(port)))
ec = v2::detail::get_last_error();
}
template<BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void(error_code, single_process_exit))
WaitHandler BOOST_PROCESS_V2_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(WaitHandler, void (error_code, single_process_exit))
async_wait_one(
WaitHandler &&handler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type))
{
printf("FOOBAR %d %p\n", port_.native_handle(), this);
return BOOST_PROCESS_V2_ASIO_NAMESPACE::async_compose<
WaitHandler,
void (error_code, single_process_exit)>(
wait_one_op{this},
handler, port_);
}
template<BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void(error_code))
WaitHandler BOOST_PROCESS_V2_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(WaitHandler, void (error_code))
async_wait_all(
WaitHandler &&handler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type))
{
return BOOST_PROCESS_V2_ASIO_NAMESPACE::async_compose<
WaitHandler,
void (error_code)>(
wait_all_op{this},
handler, port_);
}
using executor_type = Executor;
executor_type get_executor() {return port_.get_executor();}
void wait_all(error_code &ec)
{
if (job_object_is_empty(job_object_.get(), ec))
return ;
DWORD completion_code;
ULONG_PTR completion_key;
LPOVERLAPPED overlapped;
int res;
while (!!(res = GetQueuedCompletionStatus(
port_.native_handle(),
&completion_code,
&completion_key,
&overlapped,
INFINITE)))
{
if (completion_code == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO)
return;
}
if (!res)
ec = detail::get_last_error();
}
void wait_one(DWORD & pid, int & exit_code, error_code &ec)
{
if (job_object_is_empty(job_object_.get(), ec))
{
ec = BOOST_PROCESS_V2_ASIO_NAMESPACE::error::broken_pipe;
return ;
}
DWORD completion_code;
ULONG_PTR completion_key;
LPOVERLAPPED overlapped;
int res;
while (!!(res = GetQueuedCompletionStatus(
port_.native_handle(),
&completion_code,
&completion_key,
&overlapped,
INFINITE)))
{
if (completion_code == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO)
{
ec = BOOST_PROCESS_V2_ASIO_NAMESPACE::error::broken_pipe;
return;
}
else if (completion_code == JOB_OBJECT_MSG_EXIT_PROCESS
|| completion_code == JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS)
{
pid = *reinterpret_cast<DWORD*>(&overlapped);
const auto p = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
if (p == INVALID_HANDLE_VALUE || p == nullptr)
{
ec = detail::get_last_error();
break;
}
DWORD code;
res = ::GetExitCodeProcess(p, &code);
exit_code = static_cast<int>(code);
break;
}
else if (job_object_is_empty(job_object_.get(), ec))
break;
}
if (!res)
ec = detail::get_last_error();
}
private:
BOOST_PROCESS_V2_ASIO_NAMESPACE::windows::basic_object_handle<Executor> port_;
struct wait_one_op
{
basic_group_impl * this_;
template<typename Self>
void operator()(Self && self)
{
printf("FOOBAR %d %p\n", __LINE__, this_);
BOOST_PROCESS_V2_ASIO_NAMESPACE::dispatch(this_->port_.get_executor(),
BOOST_PROCESS_V2_ASIO_NAMESPACE::append(std::move(self), error_code{}));
}
template<typename Self>
void operator()(Self && self, error_code ec)
{
using namespace BOOST_PROCESS_V2_ASIO_NAMESPACE;
single_process_exit res;
/*// we need to install our handler first, THEN check, THEN
using allocator_type = associated_allocator_t<typename std::decay<Handler::type>;
allocator_type allocator = get_associated_allocator(handler);
*/
printf("FOOBAR %d %p\n", __LINE__, this_);
if (ec)
return self.complete(ec, res);
auto exec = self.get_executor();
printf("FOOBAR %d %p\n", __LINE__, this_);
if (job_object_is_empty(this_->job_object_.get(), ec))
return self.complete(BOOST_PROCESS_V2_ASIO_NAMESPACE::error::broken_pipe, res);
printf("FOOBAR %d %p\n", __LINE__, this_);
//check if done
if (this->poll_one(ec, res))
return self.complete(ec, res);
else
this_->port_.async_wait(std::move(self));
printf("FOOBAR %d %p\n", __LINE__, this_);
}
bool poll_one(error_code & ec, single_process_exit & se)
{
DWORD completion_code;
ULONG_PTR completion_key;
LPOVERLAPPED overlapped;
auto res = GetQueuedCompletionStatus(
this_->port_.native_handle(),
&completion_code,
&completion_key,
&overlapped,
0u);
if (!res && ::GetLastError() == ERROR_ABANDONED_WAIT_0)
return false;
if (completion_code == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO)
{
ec = BOOST_PROCESS_V2_ASIO_NAMESPACE::error::broken_pipe;
return true;
}
else if (completion_code == JOB_OBJECT_MSG_EXIT_PROCESS
|| completion_code == JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS)
{
se.pid = *reinterpret_cast<DWORD*>(&overlapped);
const auto p = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, se.pid);
if (p == INVALID_HANDLE_VALUE || p == nullptr)
{
ec = detail::get_last_error();
return true;
}
DWORD code;
res = ::GetExitCodeProcess(p, &code);
se.exit_code = static_cast<int>(code);
return true;
}
else if (job_object_is_empty(this_->job_object_.get(), ec))
{
ec = BOOST_PROCESS_V2_ASIO_NAMESPACE::error::broken_pipe;
return true;
}
return false;
}
};
struct wait_all_op
{
basic_group_impl * this_;
template<typename Self>
void operator()(Self && self)
{
/*
using namespace BOOST_PROCESS_V2_ASIO_NAMESPACE;
using allocator_type = associated_allocator_t<typename std::decay<Handler::type>;
allocator_type allocator = get_associated_allocator(handler);
*/
}
};
};
#if !defined(BOOST_PROCESS_V2_HEADER_ONLY)
extern template struct basic_group_impl<BOOST_PROCESS_V2_ASIO_NAMESPACE::any_io_executor>;
#endif
}
BOOST_PROCESS_V2_END_NAMESPACE
#if defined(BOOST_PROCESS_V2_HEADER_ONLY)
#include <boost/process/v2/detail/impl/group_impl_windows.ipp>
#endif
#endif //BOOST_PROCESS_V2_GROUP_IMPL_HPP

View File

@@ -0,0 +1,79 @@
//
// boost/process/v2/windows/impl/job_object_service.ipp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net)
//
// 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)
//
#ifndef BOOST_PROCESS_V2_DETAIL_IMPL_GROUP_IMPL_WINDOWS_IPP
#define BOOST_PROCESS_V2_DETAIL_IMPL_GROUP_IMPL_WINDOWS_IPP
#include <boost/process/v2/detail/config.hpp>
#include <boost/process/v2/detail/last_error.hpp>
#include <boost/process/v2/detail/group_impl_windows.hpp>
#include <windows.h>
BOOST_PROCESS_V2_BEGIN_NAMESPACE
namespace detail
{
bool job_object_is_empty(HANDLE job_object, error_code & ec)
{
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION info;
if (!QueryInformationJobObject(job_object,
JobObjectBasicAccountingInformation,
&info, sizeof(info), nullptr))
ec = detail::get_last_error();
return info.ActiveProcesses == 0u;
}
#if !defined(BOOST_PROCESS_V2_HEADER_ONLY)
template struct basic_group_impl<BOOST_PROCESS_V2_ASIO_NAMESPACE::any_io_executor>;
#endif
void basic_group_impl_base::add(DWORD pid, HANDLE handle, error_code & ec)
{
if (!AssignProcessToJobObject(job_object_.get(), handle))
ec = detail::get_last_error();
}
void basic_group_impl_base::terminate(error_code & ec)
{
if (!::TerminateJobObject(job_object_.get(), 255u))
ec = detail::get_last_error();
}
bool basic_group_impl_base::contains(DWORD pid, error_code & ec)
{
BOOL res = FALSE;
//
struct del
{
void operator()(HANDLE h)
{
if (h != nullptr && h != INVALID_HANDLE_VALUE)
::CloseHandle(h);
}
};
std::unique_ptr<void, del> proc{::OpenProcess(PROCESS_QUERY_INFORMATION , FALSE, pid)};
if (proc.get() != INVALID_HANDLE_VALUE &&
!IsProcessInJob(proc.get(), job_object_.get(), &res))
ec = detail::get_last_error();
return res == TRUE;
}
}
BOOST_PROCESS_V2_END_NAMESPACE
#endif //BOOST_PROCESS_V2_DETAIL_IMPL_GROUP_IMPL_WINDOWS_IPP

View File

@@ -9,7 +9,7 @@
#include <boost/process/v2/detail/last_error.hpp>
#if defined(BOOST_PROCESS_V2_WINDOWS)
#include <windows.h>
#include <Windows.h>
#else
#include <cerrno>
#endif

View File

@@ -12,6 +12,7 @@
#define BOOST_PROCESS_V2_EXIT_CODE_HPP
#include <boost/process/v2/detail/config.hpp>
#include <boost/process/v2/pid.hpp>
#if defined(BOOST_PROCESS_V2_POSIX)
#include <sys/wait.h>
@@ -19,6 +20,15 @@
BOOST_PROCESS_V2_BEGIN_NAMESPACE
/// Result of a single process exiting.
struct single_process_exit
{
/// The pid of the process that exited
pid_type pid;
/// The exit code of the code
int exit_code;
};
#if defined(GENERATING_DOCUMENTATION)
/// The native exit-code type, usually an integral value

View File

@@ -0,0 +1,321 @@
// Copyright (c) 2022 Klemens D. Morgenstern
//
// 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)
#ifndef BOOST_PROCESS_V2_GROUP_HPP
#define BOOST_PROCESS_V2_GROUP_HPP
#include <boost/process/v2/detail/config.hpp>
#include <boost/process/v2/default_launcher.hpp>
#include <boost/process/v2/exit_code.hpp>
#include <boost/process/v2/process_handle.hpp>
#if defined(BOOST_PROCESS_V2_WINDOWS)
#include <boost/process/v2/detail/group_impl_windows.hpp>
#else
#include <boost/process/v2/detail/group_impl_posix.hpp>
#endif
BOOST_PROCESS_V2_BEGIN_NAMESPACE
/** A process handle is an unmanaged version of a process.
* This means it does not terminate the proces on destruction and
* will not keep track of the exit-code.
*
* Note that the exit code might be discovered early, during a call to `running`.
* Thus it can only be discovered that process has exited already.
*/
template<typename Executor = BOOST_PROCESS_V2_ASIO_NAMESPACE::any_io_executor,
typename Launcher = default_process_launcher>
struct basic_group
{
/// The native handle of the process.
/** This might be undefined on posix systems that only support signals */
#if defined(GENERATING_DOCUMENTATION)
using native_handle_type = implementation_defined;
#else
using native_handle_type = typename detail::basic_group_impl<Executor>::native_handle_type;
#endif
/// The executor_type of the group
using executor_type = Executor;
/// The launcher used by the group for emplacing
using launcher_type = Launcher;
// Get the native group handle
native_handle_type native_handle() {return impl_.native_handle();}
/// Getter for the executor
executor_type get_executor() { return impl_.get_executor();}
/// Rebinds the group to another executor.
template<typename Executor1>
struct rebind_executor
{
/// The socket type when rebound to the specified executor.
typedef basic_group<Executor1, Launcher> other;
};
/// Construct a basic_group from an execution_context.
/**
* @tparam ExecutionContext The context must fulfill the asio::execution_context requirements
*/
template<typename ExecutionContext>
basic_group(ExecutionContext &context,
typename std::enable_if<
std::is_convertible<ExecutionContext&,
BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value,
launcher_type>::type launcher = launcher_type())
: basic_group(context.get_executor(), std::move(launcher))
{}
/// Construct an empty group from an executor.
basic_group(executor_type executor, launcher_type launcher = launcher_type())
: impl_(std::move(executor)), launcher_(std::move(launcher)) {}
/// Construct an empty group from an executor and bind it to a pid and the native-handle
/** On some non-linux posix systems this overload is not present.
*/
basic_group(executor_type executor, native_handle_type group,
launcher_type launcher = launcher_type())
: impl_(std::move(executor), group), , launcher_(std::move(launcher)) {}
/// Move construct and rebind the executor.
template<typename Executor1>
basic_group(basic_group<Executor1> &&handle)
: procs_(std::move(handle.procs_)), impl_(std::move(handle.impl_)),
launcher_(std::move(handle.launcher_)) {}
/// wait for one process to exit and store the exit code in exit_status.
single_process_exit wait_one(error_code &ec)
{
single_process_exit res;
impl_.wait_one(res.pid, res.exit_code, ec);
return res;
}
/// Throwing @overload wait_one(error_code & ec)
single_process_exit wait_one()
{
error_code ec;
auto res = wait_one(ec);
detail::throw_error(ec, "wait_one");
return res;
}
/// wait for all processes to exit. Returns the number of processes exited
void wait_all(error_code &ec)
{
impl_.wait_all(ec);
}
/// Throwing @overload wait_all(error_code & ec)
void wait_all()
{
error_code ec;
wait_all(ec);
detail::throw_error(ec, "wait_all");
}
/// Sends the processes a signal to ask for an interrupt, which the process may interpret as a shutdown.
/** Maybe be ignored by the subprocess. */
void interrupt(error_code &ec)
{
impl_.interrupt(ec);
}
/// Throwing @overload void interrupt()
void interrupt()
{
error_code ec;
interrupt(ec);
detail::throw_error(ec, "interrupt");
}
/// Sends the processes a signal to ask for a graceful shutdown. Maybe be ignored by the subprocess.
void request_exit(error_code &ec)
{
impl_.request_exit(ec);
}
/// Throwing @overload void request_exit(error_code & ec)
void request_exit()
{
error_code ec;
auto res = request_exit(ec);
detail::throw_error(ec, "request_exit");
}
/// Unconditionally terminates the processes and stores the exit code in exit_status.
void terminate(error_code &ec)
{
impl_.terminate(ec);
}
/// Throwing @overload void terminate(native_exit_code_type &exit_code, error_code & ec)
void terminate()
{
error_code ec;
terminate(ec);
detail::throw_error(ec, "terminate");
}
/// Check if the process handle is referring to an existing process.
bool is_open() const
{
return impl_.is_open();
}
/// Asynchronously wait for one process to exit and deliver the exit-code in the completion handler.
template<BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void(error_code, single_process_exit))
WaitHandler BOOST_PROCESS_V2_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(WaitHandler, void (error_code, single_process_exit))
async_wait_one(WaitHandler &&handler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type))
{
return impl_.async_wait_one(std::forward<WaitHandler>(handler));
}
/// Asynchronously wait for all processes to exit and the gorup to be empty.
template<BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void(error_code))
WaitHandler BOOST_PROCESS_V2_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(WaitHandler, void (error_code))
async_wait_all(WaitHandler &&handler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type))
{
return impl_.async_wait_all(std::forward<WaitHandler>(handler));
}
/// Throwing overload of @overload contains(pid_type pid, error_code & ec)
bool contains(pid_type pid)
{
error_code ec;
auto res = contains(ec);
detail::throw_error(ec, "contains");
return res;
}
/// Check if the pid is in the group
bool contains(pid_type pid, error_code & ec)
{
impl_.contains(std::move(proc), ec);
}
template<typename Executor_>
void add(basic_process_handle<Executor_> proc)
{
error_code ec;
add(proc.id(), proc.native_handle(), ec);
detail::throw_error(ec, "add");
}
template<typename Executor_>
void add(basic_process_handle<Executor_> proc, error_code & ec)
{
impl_.add(std::move(proc), ec);
}
template<typename ... Inits>
pid_type emplace(
const filesystem::path& exe,
std::initializer_list<string_view> args,
Inits&&... inits)
{
error_code ec;
auto ph = launcher_(impl_.get_executor(), exe, std::move(args),
std::forward<Inits>(inits)..., impl_.get_initializer(ec));
if (ec)
detail::throw_error(ec);
return ph.detach().id();
}
template<typename ... Inits>
pid_type emplace(
const filesystem::path& exe,
std::initializer_list<wstring_view> args,
Inits&&... inits)
{
error_code ec;
auto ph = launcher_(impl_.get_executor(), exe, std::move(args),
std::forward<Inits>(inits)..., impl_.get_initializer(ec));
if (ec)
detail::throw_error(ec);
return ph.detach().id();
}
template<typename Args, typename ... Inits>
pid_type emplace(
const filesystem::path& exe,
Args && args,
Inits&&... inits)
{
error_code ec;
auto ph = launcher_(impl_.get_executor(), exe, std::move(args),
std::forward<Inits>(inits)..., impl_.get_initializer(ec));
if (ec)
detail::throw_error(ec);
return ph.detach().id();
}
template<typename ... Inits>
pid_type emplace(
error_code & ec,
const filesystem::path& exe,
std::initializer_list<string_view> args,
Inits&&... inits)
{
auto ph = launcher_(impl_.get_executor(), ec, exe, std::move(args),
std::forward<Inits>(inits)..., impl_.get_initializer(ec));
if (ec)
return pid_type{-1};
return ph.detach().id();
}
template<typename ... Inits>
pid_type emplace(
error_code & ec,
const filesystem::path& exe,
std::initializer_list<wstring_view> args,
Inits&&... inits)
{
auto ph = launcher_(impl_.get_executor(), ec, exe, std::move(args),
std::forward<Inits>(inits)..., impl_.get_initializer(ec));
if (ec)
return pid_type{-1};
return ph.detach().id();
}
template<typename Args, typename ... Inits>
pid_type emplace(
error_code & ec,
const filesystem::path& exe,
Args && args,
Inits&&... inits)
{
auto ph = launcher_(impl_.get_executor(), ec, exe, std::move(args),
std::forward<Inits>(inits)..., impl_.get_initializer(ec));
if (ec)
return pid_type{-1};
return ph.detach().id();
}
void detach() { impl_.detach(); }
private:
detail::basic_group_impl<Executor> impl_;
launcher_type launcher_;
};
/// Process group with the default executor.
using group = basic_group<>;
#if !defined(BOOST_PROCESS_V2_HEADER_ONLY)
extern template struct basic_group<BOOST_PROCESS_V2_ASIO_NAMESPACE::any_io_executor, default_process_launcher>;
#endif
BOOST_PROCESS_V2_END_NAMESPACE
#if defined(BOOST_PROCESS_V2_HEADER_ONLY)
#include <boost/process/v2/impl/group.ipp>
#endif
#endif //BOOST_PROCESS_V2_GROUP_HPP

View File

@@ -0,0 +1,25 @@
//
// boost/process/v2/windows/impl/job_object_service.ipp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net)
//
// 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)
//
#ifndef BOOST_PROCESS_V2_IMPL_GROUP_IPP
#define BOOST_PROCESS_V2_IMPL_GROUP_IPP
#include <boost/process/v2/detail/config.hpp>
#include <boost/process/v2/group.hpp>
BOOST_PROCESS_V2_BEGIN_NAMESPACE
#if !defined(BOOST_PROCESS_V2_HEADER_ONLY)
template struct basic_group_impl<BOOST_PROCESS_V2_ASIO_NAMESPACE::any_io_executor, default_process_launcher>;
#endif
BOOST_PROCESS_V2_END_NAMESPACE
#endif //BOOST_PROCESS_V2_IMPL_GROUP_IPP

View File

@@ -24,4 +24,8 @@
#include <boost/process/v2/impl/process_handle.ipp>
#include <boost/process/v2/impl/shell.ipp>
#if defined(BOOST_PROCESS_V2_WINDOWS)
#include <boost/process/v2/detail/impl/group_impl_windows.ipp>
#endif
#endif //BOOST_PROCESS_V2_SRC_HPP

View File

@@ -11,8 +11,9 @@
#ifndef BOOST_PROCESS_V2_WINDOWS_DEFAULT_LAUNCHER_HPP
#define BOOST_PROCESS_V2_WINDOWS_DEFAULT_LAUNCHER_HPP
#include <boost/process/v2/cstring_ref.hpp>
#include <boost/process/v2/detail/config.hpp>
#include <boost/process/v2/cstring_ref.hpp>
#include <boost/process/v2/detail/last_error.hpp>
#include <boost/process/v2/detail/utf8.hpp>
#include <boost/process/v2/error.hpp>

View File

@@ -0,0 +1,23 @@
//
// boost/process/v2/windows/impl/job_object_service.ipp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2022 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net)
//
// 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)
//
#ifndef BOOST_PROCESS_V2_WINDOWS_IMPL_JOB_OBJECT_SERVICE_IPP
#define BOOST_PROCESS_V2_WINDOWS_IMPL_JOB_OBJECT_SERVICE_IPP
#include <boost/process/v2/detail/config.hpp>
#include <boost/process/v2/windows/job_object_service.hpp>
BOOST_PROCESS_V2_BEGIN_NAMESPACE
namespace windows
{
}
BOOST_PROCESS_V2_END_NAMESPACE
#endif //BOOST_PROCESS_V2_WINDOWS_IMPL_JOB_OBJECT_SERVICE_IPP

View File

@@ -4,6 +4,9 @@ add_library(boost_process_v2_test_impl test_impl.cpp)
target_link_libraries(boost_process_v2_test_impl Boost::process Boost::unit_test_framework Boost::process)
target_compile_definitions(boost_process_v2_test_impl PUBLIC -DBOOST_PROCESS_V2_SEPARATE_COMPILATION=1)
if (WIN32)
target_compile_definitions(boost_process_v2_test_impl PUBLIC WIN32_LEAN_AND_MEAN=1)
endif()
function(boost_process_v2_standalone_test name)
add_executable(boost_process_v2_${name} ${name}.cpp)
@@ -30,4 +33,5 @@ function(boost_process_v2_test_with_target name)
-- $<TARGET_FILE:boost_process_v2_test_target>)
endfunction()
boost_process_v2_test_with_target(process)
boost_process_v2_test_with_target(process)
boost_process_v2_test_with_target(group)

View File

@@ -59,5 +59,6 @@ test-suite standalone :
test-suite with_target :
[ run process.cpp test_impl : --log_level=all --catch_system_errors=no -- : target ]
[ run windows.cpp test_impl : --log_level=all --catch_system_errors=no -- : target : <build>no <target-os>windows:<build>yes <target-os>windows:<source>Advapi32 ]
[ run group.cpp test_impl : --log_level=all --catch_system_errors=no -- : target ]
;

179
test/v2/group.cpp Normal file
View File

@@ -0,0 +1,179 @@
// Copyright (c) 2022 Klemens D. Morgenstern
//
// 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)
// Disable autolinking for unit tests.
#if !defined(BOOST_ALL_NO_LIB)
#define BOOST_ALL_NO_LIB 1
#endif // !defined(BOOST_ALL_NO_LIB)
// Test that header file is self-contained.
#include <boost/test/unit_test.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/process/v2/group.hpp>
#include <boost/process/v2.hpp>
namespace bp2 = boost::process::v2;
namespace bpw = boost::process::v2::windows;
namespace bpd = boost::process::v2::detail;
namespace asio = boost::asio;
BOOST_AUTO_TEST_CASE(wait_one)
{
using boost::unit_test::framework::master_test_suite;
asio::io_context ctx;
const auto pth = master_test_suite().argv[1];
bp2::error_code ec;
bp2::group grp{ctx};
BOOST_CHECK_MESSAGE(!ec, ec.message());
auto pid1 = grp.emplace(pth, {"sleep", "100"});
auto pid2 = grp.emplace(pth, {"sleep", "300"});
auto pid3 = grp.emplace(pth, {"sleep", "500"});
auto res1 = grp.wait_one(ec); BOOST_CHECK_MESSAGE(!ec, ec.message());
auto res2 = grp.wait_one(ec); BOOST_CHECK_MESSAGE(!ec, ec.message());
auto res3 = grp.wait_one(ec); BOOST_CHECK_MESSAGE(!ec, ec.message());
grp.wait_all(); //
BOOST_CHECK_EQUAL(res1.exit_code, 0);
BOOST_CHECK_EQUAL(res2.exit_code, 0);
BOOST_CHECK_EQUAL(res3.exit_code, 0);
BOOST_CHECK_EQUAL(res1.pid, pid1);
BOOST_CHECK_EQUAL(res2.pid, pid2);
BOOST_CHECK_EQUAL(res3.pid, pid3);
BOOST_CHECK(grp.is_open());
BOOST_CHECK_MESSAGE(!ec, ec.message());
}
BOOST_AUTO_TEST_CASE(wait_all)
{
using boost::unit_test::framework::master_test_suite;
asio::io_context ctx;
const auto pth = master_test_suite().argv[1];
bp2::group grp{ctx};
auto pid1 = grp.emplace(pth, {"sleep", "10"});
auto pid2 = grp.emplace(pth, {"sleep", "30"});
auto pid3 = grp.emplace(pth, {"sleep", "50"});
grp.wait_all(); //
BOOST_CHECK(grp.is_open());
}
BOOST_AUTO_TEST_CASE(terminate_)
{
using boost::unit_test::framework::master_test_suite;
asio::io_context ctx;
const auto pth = master_test_suite().argv[1];
bp2::error_code ec;
bp2::group grp{ctx};
BOOST_CHECK_MESSAGE(!ec, ec.message());
const auto pid1 = grp.emplace(pth, {"sleep", "10000000"});
const auto pid2 = grp.emplace(pth, {"sleep", "10000000"});
const auto pid3 = grp.emplace(pth, {"sleep", "10000000"});
const auto start = std::chrono::steady_clock::now();
grp.terminate();
grp.wait_all();
const auto end = std::chrono::steady_clock::now();
BOOST_CHECK((start + std::chrono::milliseconds(5000)) > end);
BOOST_CHECK(grp.is_open());
BOOST_CHECK_MESSAGE(!ec, ec.message());
}
BOOST_AUTO_TEST_CASE(async_wait_one)
{
using boost::unit_test::framework::master_test_suite;
asio::io_context ctx;
const auto pth = master_test_suite().argv[1];
bp2::error_code ec;
bp2::group grp{ctx};
BOOST_CHECK_MESSAGE(!ec, ec.message());
auto pid1 = grp.emplace(pth, {"sleep", "100"});
auto pid2 = grp.emplace(pth, {"sleep", "300"});
auto pid3 = grp.emplace(pth, {"sleep", "500"});
std::vector<bp2::pid_type> res;
grp.async_wait_one(
[&](bp2::error_code ec, bp2::single_process_exit sp)
{
res.push_back(sp.pid);
BOOST_CHECK_EQUAL(sp.exit_code, 0u);
BOOST_CHECK_MESSAGE(!ec, ec.message());
});
grp.async_wait_one(
[&](bp2::error_code ec, bp2::single_process_exit sp)
{
res.push_back(sp.pid);
BOOST_CHECK_EQUAL(sp.exit_code, 0u);
BOOST_CHECK_MESSAGE(!ec, ec.message());
});
grp.async_wait_one(
[&](bp2::error_code ec, bp2::single_process_exit sp)
{
res.push_back(sp.pid);
BOOST_CHECK_EQUAL(sp.exit_code, 0u);
BOOST_CHECK_MESSAGE(!ec, ec.message());
});
BOOST_CHECK_GE(ctx.run(), 0u);
BOOST_CHECK_EQUAL(res.at(0), pid1);
BOOST_CHECK_EQUAL(res.at(1), pid2);
BOOST_CHECK_EQUAL(res.at(2), pid3);
BOOST_CHECK(grp.is_open());
BOOST_CHECK_MESSAGE(!ec, ec.message());
}
BOOST_AUTO_TEST_CASE(async_wait_all)
{
using boost::unit_test::framework::master_test_suite;
asio::io_context ctx;
const auto pth = master_test_suite().argv[1];
bp2::error_code ec;
bp2::group grp{ctx};
BOOST_CHECK_MESSAGE(!ec, ec.message());
auto pid1 = grp.emplace(pth, {"sleep", "10"});
auto pid2 = grp.emplace(pth, {"sleep", "30"});
auto pid3 = grp.emplace(pth, {"sleep", "50"});
grp.async_wait_all(
[](bp2::error_code ec)
{
BOOST_CHECK_MESSAGE(!ec, ec.message());
});
ctx.run();
BOOST_CHECK(grp.is_open());
BOOST_CHECK_MESSAGE(!ec, ec.message());
}