diff --git a/doc/usage.md b/doc/usage.md index df103e9f..6b294ce1 100644 --- a/doc/usage.md +++ b/doc/usage.md @@ -12,6 +12,12 @@ The `*.capnp` data definition files are consumed by the _libmultiprocess_ code g The `ProxyServer` objects help translate IPC requests from a socket to method calls on a local object. The `ProxyServer` objects are just used internally by the `mp::ServeStream(loop, socket, wrapped_object)` and `mp::ListenConnections(loop, socket, wrapped_object[, max_connections])` functions, and aren't exposed externally. The `ProxyClient` classes are exposed, and returned from the `mp::ConnectStream(loop, socket)` function and meant to be used directly. The classes implement methods described in `.capnp` definitions, and whenever any method is called, a request with the method arguments is sent over the associated IPC connection, and the corresponding `wrapped_object` method on the other end of the connection is called, with the `ProxyClient` method blocking until it returns and forwarding back any return value to the `ProxyClient` method caller. +## Event loop + +Making connections and IPC calls requires an `mp::EventLoop` instance running in its own dedicated thread. The event loop is reference-counted, and `EventLoop::loop()` returns when the last `mp::EventLoopRef` is released. Therefore, any code that needs to use the loop across multiple calls should maintain its own `EventLoopRef`. Connection objects and proxy objects automatically hold a reference for as long as they exist. + +For a typical setup, see [example.cpp](../example/example.cpp). + ## Example A simple interface description can be found at [test/mp/test/foo.capnp](../test/mp/test/foo.capnp), implementation in [test/mp/test/foo.h](../test/mp/test/foo.h), and usage in [test/mp/test/test.cpp](../test/mp/test/test.cpp). diff --git a/example/example.cpp b/example/example.cpp index c6b32f0d..0f072a9a 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -24,7 +25,7 @@ namespace fs = std::filesystem; -static auto Spawn(mp::EventLoop& loop, const std::string& process_argv0, const std::string& new_exe_name) +static auto Spawn(const mp::EventLoopRef& loop_ref, const std::string& process_argv0, const std::string& new_exe_name) { const auto [pid, socket] = mp::SpawnProcess([&](mp::SpawnConnectInfo info) -> std::vector { fs::path path = process_argv0; @@ -32,6 +33,7 @@ static auto Spawn(mp::EventLoop& loop, const std::string& process_argv0, const s path.append(new_exe_name); return {path.string(), std::move(info)}; }); + mp::EventLoop& loop = *loop_ref; return std::make_tuple(mp::ConnectStream(loop, mp::MakeStream(loop, socket)), pid); } @@ -48,16 +50,16 @@ int main(int argc, char** argv) return 1; } - std::promise promise; + std::promise promise; std::thread loop_thread([&] { mp::EventLoop loop("mpexample", LogPrint); - promise.set_value(&loop); + promise.set_value(mp::EventLoopRef(loop)); loop.loop(); }); - mp::EventLoop* loop = promise.get_future().get(); + mp::EventLoopRef loop_ref = promise.get_future().get(); - auto [printer_init, printer_pid] = Spawn(*loop, argv[0], "mpprinter"); - auto [calc_init, calc_pid] = Spawn(*loop, argv[0], "mpcalculator"); + auto [printer_init, printer_pid] = Spawn(loop_ref, argv[0], "mpprinter"); + auto [calc_init, calc_pid] = Spawn(loop_ref, argv[0], "mpcalculator"); auto calc = calc_init->makeCalculator(printer_init->makePrinter()); while (true) { std::string eqn; @@ -71,6 +73,7 @@ int main(int argc, char** argv) mp::WaitProcess(calc_pid); printer_init.reset(); mp::WaitProcess(printer_pid); + loop_ref.reset(); loop_thread.join(); std::cout << "Bye!" << std::endl; return 0; diff --git a/include/mp/proxy.h b/include/mp/proxy.h index 8d63e9aa..2144b571 100644 --- a/include/mp/proxy.h +++ b/include/mp/proxy.h @@ -44,7 +44,9 @@ inline void CleanupRun(CleanupList& fns) { } } -//! Event loop smart pointer automatically managing m_num_refs. +//! Event loop smart pointer automatically managing m_num_refs. Hold one +//! reference to keep the loop running, once the last reference is released +//! EventLoop::loop() will exit. //! If a lock pointer argument is passed, the specified lock will be used, //! otherwise EventLoop::m_mutex will be locked when needed. class EventLoopRef