@@ -395,7 +395,10 @@ ProxyClientBase<Interface, Impl>::ProxyClientBase(typename Interface::Client cli
395
395
// this object after it's already destroyed.
396
396
m_context.connection ->removeSyncCleanup (cleanup);
397
397
398
- // Destroy remote object, waiting for it to be deleted server side.
398
+ // If the capnp interface defines a destroy method, call it to destroy
399
+ // the remote object, waiting for it to be deleted server side. If the
400
+ // capnp interface does not define a destroy method, this will just call
401
+ // an empty stub defined in the ProxyClientBase class and do nothing.
399
402
self ().destroy ();
400
403
401
404
// FIXME: Could just invoke removed addCleanup fn here instead of duplicating code
@@ -436,26 +439,60 @@ ProxyServerBase<Interface, Impl>::ProxyServerBase(std::shared_ptr<Impl> impl, Co
436
439
m_context.connection ->m_loop .addClient (lock);
437
440
}
438
441
442
+ // ! ProxyServer destructor, called from the EventLoop thread by Cap'n Proto
443
+ // ! garbage collection code after there are no more references to this object.
439
444
template <typename Interface, typename Impl>
440
445
ProxyServerBase<Interface, Impl>::~ProxyServerBase ()
441
446
{
442
447
if (m_impl) {
443
- // If impl is non-null, it means client was not destroyed cleanly (was
444
- // killed or disconnected). Since client isn't providing thread to run
445
- // destructor on, run asynchronously. Do not run destructor on current
446
- // (event loop) thread since destructors could be making IPC calls or
447
- // doing expensive cleanup.
448
+ // If impl is non-null at this point, it means no client is waiting for
449
+ // the m_impl server object to be destroyed synchronously. This can
450
+ // happen either if the interface did not define a "destroy" method (see
451
+ // invokeDestroy method below), or if a destroy method was defined, but
452
+ // the connection was broken before it could be called.
453
+ //
454
+ // In either case, be conservative and run the cleanup on an
455
+ // asynchronous thread, to avoid destructors or cleanup functions
456
+ // blocking or deadlocking the current EventLoop thread, since they
457
+ // could be making IPC calls.
458
+ //
459
+ // Technically this is a little too conservative since if the interface
460
+ // defines a "destroy" method, but the destroy method does not accept a
461
+ // Context parameter specifying a worker thread, the cleanup method
462
+ // would run on the EventLoop thread normally (when connection is
463
+ // unbroken), but will not run on the EventLoop thread now (when
464
+ // connection is broken). Probably some refactoring of the destructor
465
+ // and invokeDestroy function is possible to make this cleaner and more
466
+ // consistent.
448
467
m_context.connection ->addAsyncCleanup ([impl=std::move (m_impl), c=std::move (m_context.cleanup )]() mutable {
449
468
impl.reset ();
450
469
for (auto & cleanup : c) {
451
470
cleanup ();
452
471
}
453
472
});
454
473
}
474
+ assert (m_context.cleanup .size () == 0 );
455
475
std::unique_lock<std::mutex> lock (m_context.connection ->m_loop .m_mutex );
456
476
m_context.connection ->m_loop .removeClient (lock);
457
477
}
458
478
479
+ // ! If the capnp interface defined a special "destroy" method, as described the
480
+ // ! ProxyClientBase class, this method will be called and synchronously destroy
481
+ // ! m_impl before returning to the client.
482
+ // !
483
+ // ! If the capnp interface does not define a "destroy" method, this will never
484
+ // ! be called and the ~ProxyServerBase destructor will be responsible for
485
+ // ! deleting m_impl asynchronously, whenever the ProxyServer object gets garbage
486
+ // ! collected by Cap'n Proto.
487
+ // !
488
+ // ! This method is called in the same way other proxy server methods are called,
489
+ // ! via the serverInvoke function. Basically serverInvoke just calls this as a
490
+ // ! substitute for a non-existent m_impl->destroy() method. If the destroy
491
+ // ! method has any parameters or return values they will be handled in the
492
+ // ! normal way by PassField/ReadField/BuildField functions. Particularly if a
493
+ // ! Context.thread parameter was passed, this method will run on the worker
494
+ // ! thread specified by the client. Otherwise it will run on the EventLoop
495
+ // ! thread, like other server methods without an assigned thread.
459
496
template <typename Interface, typename Impl>
460
497
void ProxyServerBase<Interface, Impl>::invokeDestroy()
461
498
{
0 commit comments