Skip to content

Commit 2c66dd5

Browse files
committed
doc: Document shutdown sequences better
Improve comments describing proxy server and proxy client object shutdown sequences. This also adds a new assert in ~ProxyServerBase()
1 parent 8da0524 commit 2c66dd5

File tree

2 files changed

+46
-6
lines changed

2 files changed

+46
-6
lines changed

include/mp/proxy-io.h

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,10 @@ ProxyClientBase<Interface, Impl>::ProxyClientBase(typename Interface::Client cli
395395
// this object after it's already destroyed.
396396
m_context.connection->removeSyncCleanup(cleanup);
397397

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.
399402
self().destroy();
400403

401404
// 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
436439
m_context.connection->m_loop.addClient(lock);
437440
}
438441

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.
439444
template <typename Interface, typename Impl>
440445
ProxyServerBase<Interface, Impl>::~ProxyServerBase()
441446
{
442447
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.
448467
m_context.connection->addAsyncCleanup([impl=std::move(m_impl), c=std::move(m_context.cleanup)]() mutable {
449468
impl.reset();
450469
for (auto& cleanup : c) {
451470
cleanup();
452471
}
453472
});
454473
}
474+
assert(m_context.cleanup.size() == 0);
455475
std::unique_lock<std::mutex> lock(m_context.connection->m_loop.m_mutex);
456476
m_context.connection->m_loop.removeClient(lock);
457477
}
458478

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.
459496
template <typename Interface, typename Impl>
460497
void ProxyServerBase<Interface, Impl>::invokeDestroy()
461498
{

include/mp/proxy-types.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1434,6 +1434,9 @@ struct CapRequestTraits<::capnp::Request<_Params, _Results>>
14341434
using Results = _Results;
14351435
};
14361436

1437+
//! Entry point called by all generated ProxyClient destructors. This only logs
1438+
//! the object destruction. The actual cleanup happens in the ProxyClient base
1439+
//! destructor.
14371440
template <typename Client>
14381441
void clientDestroy(Client& client)
14391442
{

0 commit comments

Comments
 (0)