diff --git a/doc/coroutines.qbk b/doc/coroutines.qbk new file mode 100644 index 00000000..7aa26af2 --- /dev/null +++ b/doc/coroutines.qbk @@ -0,0 +1,82 @@ +[section:coro Coroutines] +[section:stackless Stackless Coroutines] + +Stackless coroutines can be implemented rather easily, so there is no need to +implement extra functionality concerning boost.process. + +``` +struct stackless_t : boost::asio::coroutine +{ + child c; + + boost::asio::io_service & ios; + + stackless_t(boost::asio::io_service & ios) : ios(ios) {} + + void operator()( + boost::system::error_code ec = boost::system::error_code(), + std::size_t n = 0) + { + if (!ec) reenter (this) + { + c = child("my_program", ios, + bp::on_exit= + [this](int, const std::error_code&) + { + (*this)(); //this is the reentry for the coroutine + }); + yield; //yield the thing. + } + } +}; +///post the coroutine to a io-service and run it +int main() +{ + boost::asio::io_service ios; + ios.post(stackless_t(ios)); + ios.run(); + return 0; +} +``` + +[endsect] + +[section:stackful Stackful Coroutines] + +For stackful coroutines this is not as simple, because the members of +`boost::asio::yield_context` are not documented. Using a +stackful coroutine could be implemented like this: + +``` +void cr(boost::asio::yield_context yield_) //my coroutine +{ + auto coro = yield_.coro.lock(); + child c("my-program", + on_exit= + [coro](int, const std::error_code & ec) + { + auto &c = *coro; //renter the coroutine. + if (c) + c(); + }); + + yield_.ca(); //yield and return when the process is finished. +} +``` + +This example still has a problem: when using async-io the pipe buffer might not +be complete yet, which is why we should posted to the io-service. +In order to simplify this problem, boost.process provides a simple way to use +stackful coroutines, which looks as follows: + +``` +void cr(boost::asio::yield_context yield_) +{ + system("my-program", yield_); +} +``` + +This will automatically suspend the coroutine until the program is finished. + +[endsect] +[endsect] \ No newline at end of file diff --git a/doc/handbook.qbk b/doc/handbook.qbk index bd4de65a..a70b41b3 100644 --- a/doc/handbook.qbk +++ b/doc/handbook.qbk @@ -12,5 +12,6 @@ Boost.Process is a header-only library. It comes with a convenience header file [include this_process.qbk] [include properties.qbk] [include launching.qbk] +[include coroutines.qbk] [endsect] \ No newline at end of file diff --git a/doc/launching.qbk b/doc/launching.qbk index d8aa49c8..4a356195 100644 --- a/doc/launching.qbk +++ b/doc/launching.qbk @@ -22,6 +22,11 @@ When using this function with an asynchronous properties and NOT passing an io_s the system function will create one and run it. When the io_service is passed to the function, the system function will check if it is active, and call the io_service::run function if not. + +[tip This function also allows to get a `boost::asio::yield_context` passed to use coroutines, +which will cause the stackful coroutine to yield and return when the process is finished. +See [link boost_process.handbook.coro.stackful this section] for an example. ] + [endsect] [section Spawn] diff --git a/include/boost/process/detail/async_handler.hpp b/include/boost/process/detail/async_handler.hpp index e37ee32a..cb7d8526 100644 --- a/include/boost/process/detail/async_handler.hpp +++ b/include/boost/process/detail/async_handler.hpp @@ -76,6 +76,41 @@ struct has_async_handler typedef typename is_async_handler::type type; }; + +template +struct is_yield_context +{ + typedef std::false_type type; +}; + +template +struct is_yield_context<::boost::asio::basic_yield_context> +{ + typedef std::true_type type; +}; + +template +struct has_yield_context; + +template +struct has_yield_context +{ + typedef typename has_yield_context::type next; + typedef typename is_yield_context< + typename std::remove_reference::type>::type is_ios; + typedef typename std::conditional::type type; +}; + +template +struct has_yield_context +{ + typedef typename is_yield_context< + typename std::remove_reference::type>::type type; +}; + + template boost::asio::io_service &get_io_service_var(boost::asio::io_service & f, Args&...args) { diff --git a/include/boost/process/detail/posix/asio_fwd.hpp b/include/boost/process/detail/posix/asio_fwd.hpp index d1aa2310..06d5c192 100644 --- a/include/boost/process/detail/posix/asio_fwd.hpp +++ b/include/boost/process/detail/posix/asio_fwd.hpp @@ -29,7 +29,8 @@ template class basic_signal_set; typedef basic_signal_set signal_set; - +template +class basic_yield_context; namespace posix { diff --git a/include/boost/process/detail/windows/asio_fwd.hpp b/include/boost/process/detail/windows/asio_fwd.hpp index 81c3a8e3..f7667db1 100644 --- a/include/boost/process/detail/windows/asio_fwd.hpp +++ b/include/boost/process/detail/windows/asio_fwd.hpp @@ -21,6 +21,9 @@ class basic_streambuf; typedef basic_streambuf> streambuf; class io_service; +template +class basic_yield_context; + namespace windows { class stream_handle_service; diff --git a/include/boost/process/system.hpp b/include/boost/process/system.hpp index 5b671e94..99731c68 100644 --- a/include/boost/process/system.hpp +++ b/include/boost/process/system.hpp @@ -41,6 +41,7 @@ template inline int system_impl( std::true_type, /*has async */ std::true_type, /*has io_service*/ + std::false_type, /* has_yield */ Args && ...args) { IoService & ios = ::boost::process::detail::get_io_service_var(args...); @@ -84,6 +85,7 @@ template inline int system_impl( std::true_type, /*has async */ std::false_type, /*has io_service*/ + std::false_type, /* has_yield */ Args && ...args) { IoService ios; @@ -100,6 +102,7 @@ template inline int system_impl( std::false_type, /*has async */ std::true_type, /*has io_service*/ + std::false_type, /* has_yield */ Args && ...args) { child c(std::forward(args)...); @@ -113,6 +116,7 @@ template inline int system_impl( std::false_type, /*has async */ std::false_type, /*has io_service*/ + std::false_type, /* has_yield */ Args && ...args) { child c(std::forward(args)...); @@ -122,6 +126,127 @@ inline int system_impl( return c.exit_code(); } +template +constexpr T& remove_yield(T& t) noexcept +{ + return t; +} + +template +constexpr T&& remove_yield(T&& t) noexcept +{ + return static_cast(t); +} + +template +constexpr ::boost::process::detail::handler + remove_yield(::boost::asio::basic_yield_context & yield_) noexcept +{ + return {}; //essentially nop. +} + +template +constexpr ::boost::process::detail::handler + remove_yield(::boost::asio::basic_yield_context && yield_) noexcept +{ + return {}; //essentially nop. +} + +template +struct get_yield_t; + +template +struct get_yield_t +{ + typedef typename get_yield_t::type type; +}; + +template +struct get_yield_t, Args...> +{ + typedef boost::asio::basic_yield_context type; +}; + +template +struct get_yield_t> +{ + typedef boost::asio::basic_yield_context type; +}; + +template +auto get_yield(boost::asio::basic_yield_context &&yield_) -> + boost::asio::basic_yield_context & +{ + return yield_; +} + +template +auto get_yield(boost::asio::basic_yield_context &yield_) -> + boost::asio::basic_yield_context & +{ + return yield_; +} + + +template +auto get_yield(First &&f, Args&&...args) -> + typename get_yield_t::type...>::type & +{ + return get_yield(args...); +} + +template +auto get_yield(boost::asio::basic_yield_context &&yield_, Args&&...args) -> + typename get_yield_t::type...>::type & +{ + return yield_; +} + +template +auto get_yield(boost::asio::basic_yield_context &yield_, Args&&...args) -> + typename get_yield_t::type...>::type & +{ + return yield_; +} + + +template +inline int system_impl( + T1, /*has async */ + T2, /*has io_service*/ + std::true_type, /* has_yield */ + Args && ...args) +{ + + auto &yield_ = get_yield(args...); + auto coro = yield_.coro_.lock(); + auto & ios = yield_.handler_.dispatcher_.get_io_service(); + BOOST_ASSERT(coro); + + int ret = -1; + auto l = [coro, &ret, &ios](int ret_, const std::error_code& ec_) + { + ret = ret_; + ios.post( + [coro] + { + auto & c = *coro; + if (c) + c(); + }); + }; + + child c(remove_yield(std::forward(args))..., + ios, + boost::process::on_exit=l); + if (!c.valid()) + return -1; + + yield_.ca_(); + + return ret; +} + } /** Launches a process and waits for its exit. Similar to std::system. */ @@ -132,9 +257,11 @@ inline int system(Args && ...args) has_async; typedef typename ::boost::process::detail::has_io_service::type has_ios; + typedef typename ::boost::process::detail::has_yield_context::type + has_yield; return ::boost::process::detail::system_impl( - has_async(), has_ios(), + has_async(), has_ios(), has_yield(), #if defined(BOOST_POSIX_API) ::boost::process::posix::sig.dfl(), #endif diff --git a/test/Jamfile.jam b/test/Jamfile.jam index 83cd51a2..fcf22c40 100644 --- a/test/Jamfile.jam +++ b/test/Jamfile.jam @@ -18,11 +18,12 @@ searched-lib shell32 ; import testing ; -alias program_options : /boost//program_options ; -alias filesystem : /boost//filesystem ; -alias iostreams : /boost//iostreams ; -alias system : /boost//system ; -alias thread : /boost//thread ; +alias program_options : /boost//program_options ; +alias filesystem : /boost//filesystem ; +alias iostreams : /boost//iostreams ; +alias system : /boost//system ; +alias thread : /boost//thread ; +alias coroutine : /boost//coroutine : static ; exe sparring_partner : sparring_partner.cpp program_options system filesystem iostreams : off @@ -37,14 +38,15 @@ test-suite ts : [ run async_fut.cpp system thread filesystem : : sparring_partner ] [ run args_cmd.cpp system filesystem : : sparring_partner ] [ run bind_stderr.cpp filesystem : : sparring_partner ] - [ run bind_stdin.cpp system filesystem : : sparring_partner ] + [ run bind_stdin.cpp system filesystem : : sparring_partner ] [ run bind_stdin_stdout.cpp system filesystem : : sparring_partner ] - [ run bind_stdout.cpp system filesystem : : sparring_partner ] + [ run bind_stdout.cpp system filesystem : : sparring_partner ] [ run bind_stdout_stderr.cpp system filesystem : : sparring_partner ] [ run pipe_fwd.cpp system filesystem : : sparring_partner ] [ run close_stderr.cpp system filesystem : : sparring_partner ] [ run close_stdin.cpp system filesystem : : sparring_partner ] [ run close_stdout.cpp system filesystem : : sparring_partner ] + [ run coroutine_test.cpp system filesystem coroutine : : sparring_partner : static ] [ run error.cpp system filesystem : : sparring_partner ] [ run exit_code.cpp program_options system filesystem : : sparring_partner ] [ run extensions.cpp system filesystem : : sparring_partner ] @@ -62,7 +64,7 @@ test-suite ts : [ run start_dir.cpp filesystem system : : sparring_partner ] [ run terminate.cpp system filesystem : : sparring_partner ] [ run throw_on_error.cpp system filesystem : : sparring_partner ] -# [ run vfork.cpp system filesystem : : sparring_partner : no linux:yes ] + [ run vfork.cpp system filesystem : : sparring_partner : no linux:yes ] [ run wait.cpp system filesystem : : sparring_partner ] [ compile-fail spawn_fail.cpp ] ; diff --git a/test/coroutine_test.cpp b/test/coroutine_test.cpp new file mode 100644 index 00000000..d0a80bb2 --- /dev/null +++ b/test/coroutine_test.cpp @@ -0,0 +1,202 @@ +// Copyright (c) 2016 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) + +#define BOOST_TEST_MAIN +#define BOOST_TEST_IGNORE_SIGCHLD +#include + +//check I did not use them incorrectly. +#ifndef reenter +# define reenter(c) BOOST_ASIO_CORO_REENTER(c) +#endif + +#ifndef yield +# define yield BOOST_ASIO_CORO_YIELD +#endif + +#ifndef fork +# define fork BOOST_ASIO_CORO_FORK +#endif + +#include +#include +#include +#include +#include + +#undef reenter +#undef yield +#undef fork + +#include +#include +#include +#include +#include + +#include +#include + +namespace bp = boost::process; + +BOOST_AUTO_TEST_CASE(cr_test, *boost::unit_test::timeout(5)) +{ + //ok, here I check if I got the coroutine stuff right + using boost::unit_test::framework::master_test_suite; + + std::vector vec; + + boost::asio::io_service ios; + auto stackful = + [&](boost::asio::yield_context yield_) + { + vec.push_back(0); + ios.post(yield_); + vec.push_back(2); + ios.post(yield_); + vec.push_back(4); + }; + + struct stackless_t : boost::asio::coroutine + { + std::vector &vec; + boost::asio::io_service & ios; + stackless_t(std::vector & vec_,boost::asio::io_service & ios_) : vec(vec_), ios(ios_) {} + void operator()( + boost::system::error_code ec = boost::system::error_code(), + std::size_t n = 0) + { + if (!ec) reenter (this) + { + vec.push_back(1); + yield ios.post(*this); + vec.push_back(3); + yield ios.post(*this); + vec.push_back(5); + } + } + } stackless{vec, ios}; + + boost::asio::spawn(ios, stackful); + ios.post(stackless); + + ios.run(); + + std::array cmp = {0,1,2,3,4,5}; + + BOOST_CHECK_EQUAL_COLLECTIONS( + vec.begin(), vec.end(), + cmp.begin(), cmp.end()); +} + +BOOST_AUTO_TEST_CASE(stackful, *boost::unit_test::timeout(5)) +{ + using boost::unit_test::framework::master_test_suite; + + bool did_something_else = false; + + boost::asio::io_service ios; + auto stackful = + [&](boost::asio::yield_context yield_) + { + std::error_code ec; + auto ret = + bp::system( + master_test_suite().argv[1], + "test", "--exit-code", "123", + ec, + yield_); + + BOOST_CHECK(!ec); + BOOST_CHECK_EQUAL(ret, 123); + BOOST_CHECK(did_something_else); + }; + + boost::asio::spawn(ios, stackful); + ios.post([&]{did_something_else = true;}); + + ios.run(); + BOOST_CHECK(did_something_else); +} + +BOOST_AUTO_TEST_CASE(stackful_error, *boost::unit_test::timeout(5)) +{ + using boost::unit_test::framework::master_test_suite; + + bool did_something_else = false; + + boost::asio::io_service ios; + auto stackful = + [&](boost::asio::yield_context yield_) + { + std::error_code ec; + auto ret = + bp::system("none-existing-exe", + ec, + yield_); + + BOOST_CHECK(ec); + BOOST_CHECK_EQUAL(ret, -1); + BOOST_CHECK(!did_something_else); + }; + + boost::asio::spawn(ios, stackful); + ios.post([&]{did_something_else = true;}); + + ios.run(); + + BOOST_CHECK(did_something_else); +} + + +BOOST_AUTO_TEST_CASE(stackless, *boost::unit_test::timeout(5)) +{ + using boost::unit_test::framework::master_test_suite; + + boost::asio::io_service ios; + + bool did_something_else = false; + + struct stackless_t : boost::asio::coroutine + { + boost::asio::io_service & ios; + bool & did_something_else; + + bp::child c; + stackless_t(boost::asio::io_service & ios_, + bool & did_something_else) + : ios(ios_), did_something_else(did_something_else) {} + void operator()( + boost::system::error_code ec = boost::system::error_code(), + std::size_t n = 0) + { + if (!ec) reenter (this) + { + c = bp::child(master_test_suite().argv[1], + "test", "--exit-code", "321", + ios, + bp::on_exit= + [this](int, const std::error_code&) + { + (*this)(); + }); + yield; + + BOOST_CHECK(!c.running()); + BOOST_CHECK_EQUAL(c.exit_code(), 321); + BOOST_CHECK(did_something_else); + } + } + } stackless{ios, did_something_else}; + + ios.post([&]{stackless();}); + ios.post([&]{did_something_else = true;}); + + ios.run(); + + BOOST_CHECK(did_something_else); +} + +