process.hpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  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 ... Inits>
  82. explicit basic_process(
  83. executor_type executor,
  84. const filesystem::path& exe,
  85. std::initializer_list<wstring_view> args,
  86. Inits&&... inits)
  87. : basic_process(default_process_launcher()(std::move(executor), exe, 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 Args, typename ... Inits>
  92. explicit basic_process(
  93. executor_type executor,
  94. const filesystem::path& exe,
  95. Args&& args, Inits&&... inits)
  96. : basic_process(default_process_launcher()(std::move(executor), exe,
  97. std::forward<Args>(args), std::forward<Inits>(inits)...))
  98. {
  99. }
  100. /// Construct a child from a property list and launch it using the default launcher..
  101. template<typename ExecutionContext, typename ... Inits>
  102. explicit basic_process(
  103. ExecutionContext & context,
  104. typename std::enable_if<
  105. std::is_convertible<ExecutionContext&,
  106. BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value,
  107. const filesystem::path&>::type exe,
  108. std::initializer_list<string_view> args,
  109. Inits&&... inits)
  110. : basic_process(default_process_launcher()(executor_type(context.get_executor()),
  111. exe, args, std::forward<Inits>(inits)...))
  112. {
  113. }
  114. /// Construct a child from a property list and launch it using the default launcher.
  115. template<typename ExecutionContext, typename Args, typename ... Inits>
  116. explicit basic_process(
  117. ExecutionContext & context,
  118. typename std::enable_if<
  119. std::is_convertible<ExecutionContext&,
  120. BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value,
  121. const filesystem::path&>::type exe,
  122. Args&& args, Inits&&... inits)
  123. : basic_process(default_process_launcher()(executor_type(context.get_executor()),
  124. exe, std::forward<Args>(args), std::forward<Inits>(inits)...))
  125. {
  126. }
  127. /// Attach to an existing process
  128. explicit basic_process(executor_type exec, pid_type pid) : process_handle_(std::move(exec), pid) {}
  129. /// Attach to an existing process and the internal handle
  130. explicit basic_process(executor_type exec, pid_type pid, native_handle_type native_handle)
  131. : process_handle_(std::move(exec), pid, native_handle) {}
  132. /// Create an invalid handle
  133. explicit basic_process(executor_type exec) : process_handle_{std::move(exec)} {}
  134. /// Attach to an existing process
  135. template <typename ExecutionContext>
  136. explicit basic_process(ExecutionContext & context, pid_type pid,
  137. typename std::enable_if<
  138. std::is_convertible<ExecutionContext&,
  139. BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value, void *>::type = nullptr)
  140. : process_handle_(context, pid) {}
  141. /// Attach to an existing process and the internal handle
  142. template <typename ExecutionContext>
  143. explicit basic_process(ExecutionContext & context, pid_type pid, native_handle_type native_handle,
  144. typename std::enable_if<
  145. std::is_convertible<ExecutionContext&,
  146. BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value, void *>::type = nullptr)
  147. : process_handle_(context.get_executor(), pid, native_handle) {}
  148. /// Create an invalid handle
  149. template <typename ExecutionContext>
  150. explicit basic_process(ExecutionContext & context,
  151. typename std::enable_if<
  152. is_convertible<ExecutionContext&,
  153. BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value, void *>::type = nullptr)
  154. : process_handle_(context.get_executor()) {}
  155. /// Destruct the handle and terminate the process if it wasn't detached.
  156. ~basic_process()
  157. {
  158. process_handle_.terminate_if_running();
  159. }
  160. /// Sends the process a signal to ask for an interrupt, which the process may interpret as a shutdown.
  161. /** Maybe be ignored by the subprocess. */
  162. void interrupt()
  163. {
  164. error_code ec;
  165. interrupt(ec);
  166. if (ec)
  167. throw system_error(ec, "interrupt failed");
  168. }
  169. /// Throwing @overload void interrupt()
  170. void interrupt(error_code & ec)
  171. {
  172. process_handle_.interrupt(ec);
  173. }
  174. /// Throwing @overload void request_exit(error_code & ec)
  175. void request_exit()
  176. {
  177. error_code ec;
  178. request_exit(ec);
  179. if (ec)
  180. throw system_error(ec, "request_exit failed");
  181. }
  182. /// Sends the process a signal to ask for a graceful shutdown. Maybe be ignored by the subprocess.
  183. void request_exit(error_code & ec)
  184. {
  185. process_handle_.request_exit(ec);
  186. }
  187. /// Send the process a signal requesting it to stop. This may rely on undocumented functions.
  188. void suspend(error_code &ec)
  189. {
  190. process_handle_.suspend(ec);
  191. }
  192. /// Send the process a signal requesting it to stop. This may rely on undocumented functions.
  193. void suspend()
  194. {
  195. error_code ec;
  196. suspend(ec);
  197. if (ec)
  198. detail::throw_error(ec, "suspend");
  199. }
  200. /// Send the process a signal requesting it to resume. This may rely on undocumented functions.
  201. void resume(error_code &ec)
  202. {
  203. process_handle_.resume(ec);
  204. }
  205. /// Send the process a signal requesting it to resume. This may rely on undocumented functions.
  206. void resume()
  207. {
  208. error_code ec;
  209. suspend(ec);
  210. if (ec)
  211. detail::throw_error(ec, "resume");
  212. }
  213. /// Throwing @overload void terminate(native_exit_code_type &exit_code, error_code & ec)
  214. void terminate()
  215. {
  216. error_code ec;
  217. terminate(ec);
  218. if (ec)
  219. detail::throw_error(ec, "terminate failed");
  220. }
  221. /// Unconditionally terminates the process and stores the exit code in exit_status.
  222. void terminate(error_code & ec)
  223. {
  224. process_handle_.terminate(exit_status_, ec);
  225. }
  226. /// Throwing @overload wait(error_code & ec)
  227. int wait()
  228. {
  229. error_code ec;
  230. if (running(ec))
  231. wait(ec);
  232. if (ec)
  233. detail::throw_error(ec, "wait failed");
  234. return exit_code();
  235. }
  236. /// Waits for the process to exit, store the exit code internally and return it.
  237. int wait(error_code & ec)
  238. {
  239. if (running(ec))
  240. process_handle_.wait(exit_status_, ec);
  241. return exit_code();
  242. }
  243. /// Detach the process.
  244. handle_type detach()
  245. {
  246. #if defined(BOOST_PROCESS_V2_STANDALONE)
  247. return std::exchange(process_handle_, get_executor());
  248. #else
  249. return boost::exchange(process_handle_, get_executor());
  250. #endif
  251. }
  252. // Get the native
  253. native_handle_type native_handle() {return process_handle_.native_handle(); }
  254. int exit_code() const
  255. {
  256. return evaluate_exit_code(exit_status_);
  257. }
  258. /// Get the id of the process;
  259. pid_type id() const {return process_handle_.id();}
  260. /// The native handle of the process.
  261. /** This might be undefined on posix systems that only support signals */
  262. native_exit_code_type native_exit_code() const
  263. {
  264. return exit_status_;
  265. }
  266. /// Checks if the current process is running.
  267. /** If it has already completed the exit code will be stored internally
  268. * and can be obtained by calling `exit_code.
  269. */
  270. bool running()
  271. {
  272. error_code ec;
  273. native_exit_code_type exit_code{};
  274. auto r = process_handle_.running(exit_code, ec);
  275. if (!ec && !r)
  276. exit_status_ = exit_code;
  277. else
  278. detail::throw_error(ec, "running failed");
  279. return r;
  280. }
  281. /// Throwing @overload bool running(error_code & ec)
  282. bool running(error_code & ec) noexcept
  283. {
  284. native_exit_code_type exit_code{};
  285. auto r = process_handle_.running(exit_code, ec);
  286. if (!ec && !r)
  287. exit_status_ = exit_code;
  288. return r;
  289. }
  290. /// Check if the process is referring to an existing process.
  291. /** Note that this might be a process that already exited.*/
  292. bool is_open() const { return process_handle_.is_open(); }
  293. /// Asynchronously wait for the process to exit and deliver the portable exit-code in the completion handler.
  294. template <BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void (error_code, int))
  295. WaitHandler BOOST_PROCESS_V2_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
  296. BOOST_PROCESS_V2_INITFN_AUTO_RESULT_TYPE(WaitHandler, void (error_code, int))
  297. async_wait(WaitHandler && handler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type))
  298. {
  299. return BOOST_PROCESS_V2_ASIO_NAMESPACE::async_compose<WaitHandler, void (error_code, int)>(
  300. async_wait_op_{process_handle_, exit_status_}, handler, process_handle_);
  301. }
  302. private:
  303. template<typename Executor1>
  304. friend struct basic_process;
  305. basic_process_handle<Executor> process_handle_;
  306. native_exit_code_type exit_status_{detail::still_active};
  307. struct async_wait_op_
  308. {
  309. basic_process_handle<Executor> & handle;
  310. native_exit_code_type & res;
  311. template<typename Self>
  312. void operator()(Self && self)
  313. {
  314. if (!process_is_running(res))
  315. {
  316. struct completer
  317. {
  318. int code;
  319. typename std::decay<Self>::type self;
  320. void operator()()
  321. {
  322. self.complete(error_code{}, evaluate_exit_code(code));
  323. }
  324. };
  325. BOOST_PROCESS_V2_ASIO_NAMESPACE::post(handle.get_executor(),
  326. completer{static_cast<int>(res), std::move(self)});
  327. }
  328. else
  329. handle.async_wait(std::move(self));
  330. }
  331. template<typename Self>
  332. void operator()(Self && self, error_code ec, native_exit_code_type code)
  333. {
  334. if (!ec && process_is_running(code))
  335. handle.async_wait(std::move(self));
  336. else
  337. {
  338. if (!ec)
  339. res = code;
  340. std::move(self).complete(ec, evaluate_exit_code(code));
  341. }
  342. }
  343. };
  344. };
  345. /// Process with the default executor.
  346. typedef basic_process<> process;
  347. BOOST_PROCESS_V2_END_NAMESPACE
  348. #endif //BOOST_PROCESS_V2_PROCESS_HPP