Skip to content

boost::process::async_system in composed asio operations #231

@ceggers-arri

Description

@ceggers-arri

Boost version: 1.72.0

I would like to use boost::process::async_system() within a composed operation (example taken from composed_6.cpp).

The helper boost::asio::async_compose() creates a temporary object (boost::asio::detail::composed_op<>) which contains all necessary variables/states during the whole composed operation. Unfortunately my program (below) doesn't compile. The (lengthy) compiler message looks like boost::process::async_system() tries to make another copy of the completion object (self). But boost::asio::detail::composed_op<> is not copyable.

Is it possible to avoid making an extra copy of the completion object in boost::process::async_system() (like the other boost::asio::async_xxx functions behave)?

regards
Christian

#include <cerrno>
#include <memory>
#include <iostream>
#include <string>
#include <utility>

#include <boost/asio/buffer.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/local/stream_protocol.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/process/args.hpp>
#include <boost/process/async_system.hpp>
#include <boost/process/env.hpp>
#include <boost/process/environment.hpp>
#include <boost/process/exe.hpp>
#include <boost/process/extend.hpp>
#include <boost/process/handles.hpp>
#include <boost/process/io.hpp>
#include <boost/process/posix.hpp>
#include <boost/system/error_code.hpp>


static constexpr char const *gpsdExecutable = "/usr/sbin/gpsd";           // always absolute, also on host
static constexpr char const *gpsdControlSocket = "/run/gpsd.sock";  // always absolute, also on host

static void prepareGpsdSockets(
		boost::asio::local::stream_protocol::acceptor &controlSocket,
		boost::asio::ip::tcp::acceptor &exportSocket,
		boost::asio::ip::tcp::resolver::results_type const &endpoints,
		boost::system::error_code &ec)
{
	/* control socket */
	boost::filesystem::remove(gpsdControlSocket, ec);  // ignore errors
	boost::asio::local::stream_protocol::endpoint controlSocketEp(gpsdControlSocket);
	controlSocket.open(controlSocketEp.protocol(), ec);
	if (ec)
		return;
	controlSocket.bind(controlSocketEp, ec);
	if (ec)
		return;
	controlSocket.listen(boost::asio::socket_base::max_listen_connections, ec);
	if (ec)
		return;

	/* export socket TCPv4 */
	boost::asio::ip::tcp::endpoint const &exportSocketEp = *endpoints.begin();
	exportSocket.open(exportSocketEp.protocol(), ec);
	if (ec)
		return;
	exportSocket.set_option(boost::asio::socket_base::reuse_address(true), ec);
	if (ec)
		return;
	exportSocket.bind(exportSocketEp, ec);
	if (ec)
		return;
	exportSocket.listen(boost::asio::socket_base::max_listen_connections, ec);
	if (ec)
		return;
}

template<typename CompletionToken>
inline auto async_startGpsd(boost::asio::io_context &ioContext, CompletionToken &&token)
{
	enum class State { starting, resolving, forking };

	return boost::asio::async_compose<CompletionToken, void(boost::system::error_code)>(
		[
			state         = State::starting,
			&ioContext,
			/* Note: std::make_shared() doesn't help here. */
			controlSocket = std::make_unique<boost::asio::local::stream_protocol::acceptor>(ioContext),
			resolver      = std::make_unique<boost::asio::ip::tcp::resolver>(ioContext),
			exportSocket  = std::make_unique<boost::asio::ip::tcp::acceptor>(ioContext)
		]
		(
			auto &self,   // reference to intermediate completion handle (provided by async_compose)
			boost::system::error_code ec = {},
			boost::asio::ip::tcp::resolver::results_type endpoints = {},
			int exitCode = {}
		) mutable
		{
			if (ec)
			{
				self.complete(ec);
				return;
			}

			switch (state)
			{
				case State::starting:
				{
					state = State::resolving;

					/* lookup gpsd port number from /etc/services */
					resolver->async_resolve(boost::asio::ip::tcp::v4(), "", "gpsd", boost::asio::ip::resolver_base::flags::passive, std::move(self));
					return;  // Composed operation not yet complete.
				}

				case State::resolving:
				{
					state = State::forking;
					prepareGpsdSockets(*controlSocket, *exportSocket, endpoints, ec);
					if (ec)
						break;

					auto controlHandle = controlSocket->native_handle();
					auto exportHandle = exportSocket->native_handle();
					boost::process::async_system(
						ioContext,
						[self=std::move(self)]
						(boost::system::error_code ec, int exitCode)
						mutable
						{
							self(ec, boost::asio::ip::tcp::resolver::results_type(), exitCode);
						},
						boost::process::exe = gpsdExecutable,
						boost::process::args = {
							"--listenany",
						},
						/* the order of bind() and close() is important */
						boost::process::std_in  < stdin,  // don't close std handles
						boost::process::std_out > stdout,
						boost::process::std_err > stderr,
						boost::process::posix::fd.bind(3, controlHandle),
						boost::process::posix::fd.bind(4, exportHandle),
						boost::process::posix::fd.close(controlHandle),
						boost::process::posix::fd.close(exportHandle),
						boost::process::limit_handles,
						boost::process::extend::on_exec_setup = [](auto &executor)
						{
							auto env = boost::this_process::environment();
							env["LISTEN_PID"] = std::to_string(boost::this_process::native_handle());
							env["LISTEN_FDS"] = "2";  // number of sockets

							/* use latest ::environ for ::execve() */
							executor.env = env.native_handle();
						}
					);
					return;  // Composed operation not yet complete.
				}

				case State::forking:
					if (exitCode)  // error code from gpsd
					{
						ec.assign(ESRCH /* No such process */, boost::system::system_category());
					}
					break; // Composed operation complete, continue below.
			}

			// Call the user-supplied handler with the result of the operation.
			self.complete(ec);
		},
		token, ioContext);
}

int main(void)
{
	boost::asio::io_context ioContext;

	async_startGpsd(ioContext,
		[](boost::system::error_code const &ec)
		{
			std::cout << "async_startGpsd() finished with ec = \n" << ec;
		});
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions