process.hpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. // Copyright (c) 2021 Klemens D. Morgenstern (klemens dot morgenstern at gmx dot net)
  2. //
  3. // Distributed under the Boost Software License, Version 1.0. (See accompanying
  4. // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  5. //
  6. //
  7. // process.hpp
  8. // ~~~~~~~~~~~~~~
  9. //
  10. #ifndef BOOST_PROCESS_V2_PROCESS_HPP
  11. #define BOOST_PROCESS_V2_PROCESS_HPP
  12. #include <boost/process/v2/detail/config.hpp>
  13. #include <boost/process/v2/default_launcher.hpp>
  14. #include <boost/process/v2/exit_code.hpp>
  15. #include <boost/process/v2/pid.hpp>
  16. #include <boost/process/v2/ext/exe.hpp>
  17. #include <boost/process/v2/process_handle.hpp>
  18. #if defined(BOOST_PROCESS_V2_STANDALONE)
  19. #include <asio/any_io_executor.hpp>
  20. #include <asio/post.hpp>
  21. #include <utility>
  22. #else
  23. #include <boost/asio/any_io_executor.hpp>
  24. #include <boost/asio/post.hpp>
  25. #include <boost/core/exchange.hpp>
  26. #endif
  27. BOOST_PROCESS_V2_BEGIN_NAMESPACE
  28. /// A class managing a subprocess
  29. /* A `basic_process` object manages a subprocess; it tracks the status and exit-code,
  30. * and will terminate the process on destruction if `detach` was not called.
  31. */
  32. template<typename Executor = BOOST_PROCESS_V2_ASIO_NAMESPACE::any_io_executor>
  33. struct basic_process
  34. {
  35. /// The executor of the process
  36. using executor_type = Executor;
  37. /// Get the executor of the process
  38. executor_type get_executor() {return process_handle_.get_executor();}
  39. /// The non-closing handle type
  40. using handle_type = basic_process_handle<executor_type>;
  41. /// Get the underlying non-closing handle
  42. handle_type & handle() { return process_handle_; }
  43. /// Get the underlying non-closing handle
  44. const handle_type & handle() const { return process_handle_; }
  45. /// Provides access to underlying operating system facilities
  46. using native_handle_type = typename handle_type::native_handle_type;
  47. /// Rebinds the process_handle to another executor.
  48. template <typename Executor1>
  49. struct rebind_executor
  50. {
  51. /// The socket type when rebound to the specified executor.
  52. typedef basic_process<Executor1> other;
  53. };
  54. /** An empty process is similar to a default constructed thread. It holds an empty
  55. handle and is a place holder for a process that is to be launched later. */
  56. basic_process() = default;
  57. basic_process(const basic_process&) = delete;
  58. basic_process& operator=(const basic_process&) = delete;
  59. /// Move construct the process. It will be detached from `lhs`.
  60. basic_process(basic_process&& lhs) = default;
  61. /// Move assign a process. It will be detached from `lhs`.
  62. basic_process& operator=(basic_process&& lhs) = default;
  63. /// Move construct and rebind the executor.
  64. template<typename Executor1>
  65. basic_process(basic_process<Executor1>&& lhs)
  66. : process_handle_(std::move(lhs.process_handle_)),
  67. exit_status_{lhs.exit_status_}
  68. {
  69. }
  70. /// Construct a child from a property list and launch it using the default launcher..
  71. template<typename ... Inits>
  72. explicit basic_process(
  73. executor_type executor,
  74. const filesystem::path& exe,
  75. std::initializer_list<string_view> args,
  76. Inits&&... inits)
  77. : basic_process(default_process_launcher()(std::move(executor), exe, args, std::forward<Inits>(inits)...))
  78. {
  79. }
  80. /// Construct a child from a property list and launch it using the default launcher..
  81. template<typename Args, typename ... Inits>
  82. explicit basic_process(
  83. executor_type executor,
  84. const filesystem::path& exe,
  85. Args&& args, Inits&&... inits)
  86. : basic_process(default_process_launcher()(std::move(executor), exe,
  87. std::forward<Args>(args), std::forward<Inits>(inits)...))
  88. {
  89. }
  90. /// Construct a child from a property list and launch it using the default launcher..
  91. template<typename ExecutionContext, typename ... Inits>
  92. explicit basic_process(
  93. ExecutionContext & context,
  94. typename std::enable_if<
  95. std::is_convertible<ExecutionContext&,
  96. BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value,
  97. const filesystem::path&>::type exe,
  98. std::initializer_list<string_view> args,
  99. Inits&&... inits)
  100. : basic_process(default_process_launcher()(executor_type(context.get_executor()),
  101. exe, args, std::forward<Inits>(inits)...))
  102. {
  103. }
  104. /// Construct a child from a property list and launch it using the default launcher.
  105. template<typename ExecutionContext, typename Args, typename ... Inits>
  106. explicit basic_process(
  107. ExecutionContext & context,
  108. typename std::enable_if<
  109. std::is_convertible<ExecutionContext&,
  110. BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value,
  111. const filesystem::path&>::type exe,
  112. Args&& args, Inits&&... inits)
  113. : basic_process(default_process_launcher()(executor_type(context.get_executor()),
  114. exe, std::forward<Args>(args), std::forward<Inits>(inits)...))
  115. {
  116. }
  117. /// Attach to an existing process
  118. explicit basic_process(executor_type exec, pid_type pid) : process_handle_(std::move(exec), pid) {}
  119. /// Attach to an existing process and the internal handle
  120. explicit basic_process(executor_type exec, pid_type pid, native_handle_type native_handle)
  121. : process_handle_(std::move(exec), pid, native_handle) {}
  122. /// Create an invalid handle
  123. explicit basic_process(executor_type exec) : process_handle_{std::move(exec)} {}
  124. /// Attach to an existing process
  125. template <typename ExecutionContext>
  126. explicit basic_process(ExecutionContext & context, pid_type pid,
  127. typename std::enable_if<
  128. std::is_convertible<ExecutionContext&,
  129. BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value, void *>::type = nullptr)
  130. : process_handle_(context, pid) {}
  131. /// Attach to an existing process and the internal handle
  132. template <typename ExecutionContext>
  133. explicit basic_process(ExecutionContext & context, pid_type pid, native_handle_type native_handle,
  134. typename std::enable_if<
  135. std::is_convertible<ExecutionContext&,
  136. BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value, void *>::type = nullptr)
  137. : process_handle_(context.get_executor(), pid, native_handle) {}
  138. /// Create an invalid handle
  139. template <typename ExecutionContext>
  140. explicit basic_process(ExecutionContext & context,
  141. typename std::enable_if<
  142. is_convertible<ExecutionContext&,
  143. BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value, void *>::type = nullptr)
  144. : process_handle_(context.get_executor()) {}
  145. /// Destruct the handle and terminate the process if it wasn't detached.
  146. ~basic_process()
  147. {
  148. process_handle_.terminate_if_running();
  149. }
  150. /// Sends the process a signal to ask for an interrupt, which the process may interpret as a shutdown.
  151. /** Maybe be ignored by the subprocess. */
  152. void interrupt()
  153. {
  154. error_code ec;
  155. interrupt(ec);
  156. if (ec)
  157. throw system_error(ec, "interrupt failed");
  158. }
  159. /// Throwing @overload void interrupt()
  160. void interrupt(error_code & ec)
  161. {
  162. process_handle_.interrupt(ec);
  163. }
  164. /// Throwing @overload void request_exit(error_code & ec)
  165. void request_exit()
  166. {
  167. error_code ec;
  168. request_exit(ec);
  169. if (ec)
  170. throw system_error(ec, "request_exit failed");
  171. }
  172. /// Sends the process a signal to ask for a graceful shutdown. Maybe be ignored by the subprocess.
  173. void request_exit(error_code & ec)
  174. {
  175. process_handle_.request_exit(ec);
  176. }
  177. /// Send the process a signal requesting it to stop. This may rely on undocumented functions.
  178. void suspend(error_code &ec)
  179. {
  180. process_handle_.suspend(ec);
  181. }
  182. /// Send the process a signal requesting it to stop. This may rely on undocumented functions.
  183. void suspend()
  184. {
  185. error_code ec;
  186. suspend(ec);
  187. if (ec)
  188. detail::throw_error(ec, "suspend");
  189. }
  190. /// Send the process a signal requesting it to resume. This may rely on undocumented functions.
  191. void resume(error_code &ec)
  192. {
  193. process_handle_.resume(ec);
  194. }
  195. /// Send the process a signal requesting it to resume. This may rely on undocumented functions.
  196. void resume()
  197. {
  198. error_code ec;
  199. suspend(ec);
  200. if (ec)
  201. detail::throw_error(ec, "resume");
  202. }
  203. /// Throwing @overload void terminate(native_exit_code_type &exit_code, error_code & ec)
  204. void terminate()
  205. {
  206. error_code ec;
  207. terminate(ec);
  208. if (ec)
  209. detail::throw_error(ec, "terminate failed");
  210. }
  211. /// Unconditionally terminates the process and stores the exit code in exit_status.
  212. void terminate(error_code & ec)
  213. {
  214. process_handle_.terminate(exit_status_, ec);
  215. }
  216. /// Throwing @overload wait(error_code & ec)
  217. int wait()
  218. {
  219. error_code ec;
  220. if (running(ec))
  221. process_handle_.wait(exit_status_, ec);
  222. if (ec)
  223. detail::throw_error(ec, "wait failed");
  224. return exit_code();
  225. }
  226. /// Waits for the process to exit, store the exit code internally and return it.
  227. int wait(error_code & ec)
  228. {
  229. if (running(ec))
  230. process_handle_.wait(exit_status_, ec);
  231. return exit_code();
  232. }
  233. /// Detach the process.
  234. handle_type detach()
  235. {
  236. #if defined(BOOST_PROCESS_V2_STANDALONE)
  237. return std::exchange(process_handle_, get_executor());
  238. #else
  239. return boost::exchange(process_handle_, get_executor());
  240. #endif
  241. }
  242. /// Get the native
  243. native_handle_type native_handle() {return process_handle_.native_handle(); }
  244. /// Return the evaluated exit_code.
  245. int exit_code() const
  246. {
  247. return evaluate_exit_code(exit_status_);
  248. }
  249. /// Get the id of the process;
  250. pid_type id() const {return process_handle_.id();}
  251. /// The native handle of the process.
  252. /** This might be undefined on posix systems that only support signals */
  253. native_exit_code_type native_exit_code() const
  254. {
  255. return exit_status_;
  256. }
  257. /// Checks if the current process is running.
  258. /** If it has already completed the exit code will be stored internally
  259. * and can be obtained by calling `exit_code.
  260. */
  261. bool running()
  262. {
  263. error_code ec;
  264. native_exit_code_type exit_code{};
  265. auto r = process_handle_.running(exit_code, ec);
  266. if (!ec && !r)
  267. exit_status_ = exit_code;
  268. else
  269. detail::throw_error(ec, "running failed");
  270. return r;
  271. }
  272. /// Throwing @overload bool running(error_code & ec)
  273. bool running(error_code & ec) noexcept
  274. {
  275. native_exit_code_type exit_code{};
  276. auto r = process_handle_.running(exit_code, ec);
  277. if (!ec && !r)
  278. exit_status_ = exit_code;
  279. return r;
  280. }
  281. /// Check if the process is referring to an existing process.
  282. /** Note that this might be a process that already exited.*/
  283. bool is_open() const { return process_handle_.is_open(); }
  284. /// Asynchronously wait for the process to exit and deliver the native exit-code in the completion handler.
  285. template <BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void (error_code, int))
  286. WaitHandler BOOST_PROCESS_V2_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
  287. BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(WaitHandler, void (error_code, int))
  288. async_wait(WaitHandler && handler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type))
  289. {
  290. return BOOST_PROCESS_V2_ASIO_NAMESPACE::async_compose<WaitHandler, void (error_code, int)>(
  291. async_wait_op_{process_handle_, exit_status_}, handler, process_handle_);
  292. }
  293. private:
  294. template<typename Executor1>
  295. friend struct basic_process;
  296. basic_process_handle<Executor> process_handle_;
  297. native_exit_code_type exit_status_{detail::still_active};
  298. struct async_wait_op_
  299. {
  300. basic_process_handle<Executor> & handle;
  301. native_exit_code_type & res;
  302. template<typename Self>
  303. void operator()(Self && self)
  304. {
  305. if (!process_is_running(res))
  306. {
  307. struct completer
  308. {
  309. int code;
  310. typename std::decay<Self>::type self;
  311. void operator()()
  312. {
  313. self.complete(error_code{}, evaluate_exit_code(code));
  314. }
  315. };
  316. BOOST_PROCESS_V2_ASIO_NAMESPACE::post(handle.get_executor(),
  317. completer{static_cast<int>(res), std::move(self)});
  318. }
  319. else
  320. handle.async_wait(std::move(self));
  321. }
  322. template<typename Self>
  323. void operator()(Self && self, error_code ec, native_exit_code_type code)
  324. {
  325. if (!ec && process_is_running(code))
  326. handle.async_wait(std::move(self));
  327. else
  328. {
  329. if (!ec)
  330. res = code;
  331. std::move(self).complete(ec, evaluate_exit_code(code));
  332. }
  333. }
  334. };
  335. };
  336. /// Process with the default executor.
  337. typedef basic_process<> process;
  338. BOOST_PROCESS_V2_END_NAMESPACE
  339. #endif //BOOST_PROCESS_V2_PROCESS_HPP