@@ -45,6 +45,7 @@ class TestSetup
4545 std::function<void ()> client_disconnect;
4646 std::promise<std::unique_ptr<ProxyClient<messages::FooInterface>>> client_promise;
4747 std::unique_ptr<ProxyClient<messages::FooInterface>> client;
48+ ProxyServer<messages::FooInterface>* server{nullptr };
4849
4950 TestSetup (bool client_owns_connection = true )
5051 : thread{[&] {
@@ -58,6 +59,7 @@ class TestSetup
5859 std::make_unique<Connection>(loop, kj::mv (pipe.ends [0 ]), [&](Connection& connection) {
5960 auto server_proxy = kj::heap<ProxyServer<messages::FooInterface>>(
6061 std::make_shared<FooImplementation>(), connection);
62+ server = server_proxy;
6163 return capnp::Capability::Client (kj::mv (server_proxy));
6264 });
6365 server_disconnect = [&] { loop.sync ([&] { server_connection.reset (); }); };
@@ -215,5 +217,69 @@ KJ_TEST("Calling IPC method after server connection is closed")
215217 KJ_EXPECT (disconnected);
216218}
217219
220+ KJ_TEST (" Calling IPC method and disconnecting during the call" )
221+ {
222+ TestSetup setup{/* client_owns_connection=*/ false };
223+ ProxyClient<messages::FooInterface>* foo = setup.client .get ();
224+ KJ_EXPECT (foo->add (1 , 2 ) == 3 );
225+
226+ // Set m_fn to initiate client disconnect when server is in the middle of
227+ // handling the callFn call to make sure this case is handled cleanly.
228+ setup.server ->m_impl ->m_fn = setup.client_disconnect ;
229+
230+ bool disconnected{false };
231+ try {
232+ foo->callFn ();
233+ } catch (const std::runtime_error& e) {
234+ KJ_EXPECT (std::string_view{e.what ()} == " IPC client method call interrupted by disconnect." );
235+ disconnected = true ;
236+ }
237+ KJ_EXPECT (disconnected);
238+ }
239+
240+ KJ_TEST (" Calling IPC method, disconnecting and blocking during the call" )
241+ {
242+ // This test is similar to last test, except that instead of letting the IPC
243+ // call return immediately after triggering a disconnect, make it disconnect
244+ // & wait so server is forced to deal with having a disconnection and call
245+ // in flight at the same time.
246+ //
247+ // Test uses callFnAsync() instead of callFn() to implement this. Both of
248+ // these methods have the same implementation, but the callFnAsync() capnp
249+ // method declaration takes an mp.Context argument so the method executes on
250+ // an asynchronous thread instead of executing in the event loop thread, so
251+ // it is able to block without deadlocking the event lock thread.
252+ //
253+ // This test adds important coverage because it causes the server Connection
254+ // object to be destroyed before ProxyServer object, which is not a
255+ // condition that usually happens because the m_rpc_system.reset() call in
256+ // the ~Connection destructor usually would immediately free all remaing
257+ // ProxyServer objects associated with the connection. Having an in-progress
258+ // RPC call requires keeping the ProxyServer longer.
259+
260+ TestSetup setup{/* client_owns_connection=*/ false };
261+ ProxyClient<messages::FooInterface>* foo = setup.client .get ();
262+ KJ_EXPECT (foo->add (1 , 2 ) == 3 );
263+
264+ foo->initThreadMap ();
265+ std::promise<void > signal;
266+ setup.server ->m_impl ->m_fn = [&] {
267+ EventLoopRef loop{*setup.server ->m_context .loop };
268+ setup.client_disconnect ();
269+ signal.get_future ().get ();
270+ };
271+
272+ bool disconnected{false };
273+ try {
274+ foo->callFnAsync ();
275+ } catch (const std::runtime_error& e) {
276+ KJ_EXPECT (std::string_view{e.what ()} == " IPC client method call interrupted by disconnect." );
277+ disconnected = true ;
278+ }
279+ KJ_EXPECT (disconnected);
280+
281+ signal.set_value ();
282+ }
283+
218284} // namespace test
219285} // namespace mp
0 commit comments