@@ -45,6 +45,7 @@ class TestSetup
45
45
std::function<void ()> client_disconnect;
46
46
std::promise<std::unique_ptr<ProxyClient<messages::FooInterface>>> client_promise;
47
47
std::unique_ptr<ProxyClient<messages::FooInterface>> client;
48
+ ProxyServer<messages::FooInterface>* server{nullptr };
48
49
49
50
TestSetup (bool client_owns_connection = true )
50
51
: thread{[&] {
@@ -58,6 +59,7 @@ class TestSetup
58
59
std::make_unique<Connection>(loop, kj::mv (pipe.ends [0 ]), [&](Connection& connection) {
59
60
auto server_proxy = kj::heap<ProxyServer<messages::FooInterface>>(
60
61
std::make_shared<FooImplementation>(), connection);
62
+ server = server_proxy;
61
63
return capnp::Capability::Client (kj::mv (server_proxy));
62
64
});
63
65
server_disconnect = [&] { loop.sync ([&] { server_connection.reset (); }); };
@@ -215,5 +217,69 @@ KJ_TEST("Calling IPC method after server connection is closed")
215
217
KJ_EXPECT (disconnected);
216
218
}
217
219
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
+
218
284
} // namespace test
219
285
} // namespace mp
0 commit comments