Skip to content

Commit 53ee9fa

Browse files
committed
Merge #100: doc: Add various code comments and documentation
537c645 doc: Improve ProxyServerCustom class documentation (Ryan Ofsky) d4d9f93 doc: Document FunctionTraits/ProxyMethodTraits classes (Ryan Ofsky) 78c7dd0 doc: Document ProxyClient construct/destroy methods (Ryan Ofsky) e99c0b7 doc: Document clientInvoke/serverInvoke functions (Ryan Ofsky) 2098ae1 doc: Add comment on serverInvoke ReplaceVoid usage (Ryan Ofsky) a1dfb0b doc: Add comments to mp.Context PassField function on updating g_thread_context (Ryan Ofsky) e49a925 doc: Add comments to mp.Context PassField function on mp.Context.thread lookup (Ryan Ofsky) Pull request description: Add code comments and documentation. Most of these comments were originally added as part of #94 but are being moved to a separate PR so they can be merged sooner Top commit has no ACKs. Tree-SHA512: 284325513d3244313a4ff54ed20648061a6142ce89f4449bb046938780f1d74c7691dba5fdff5f478f585e95e5ea0987af2fde2e67c049adc8a6b7db21371341
2 parents e45c482 + 537c645 commit 53ee9fa

File tree

2 files changed

+142
-18
lines changed

2 files changed

+142
-18
lines changed

include/mp/proxy-types.h

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,23 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn&
126126
Context::Reader context_arg = Accessor::get(params);
127127
ServerContext server_context{server, call_context, req};
128128
{
129+
// Before invoking the function, store a reference to the
130+
// callbackThread provided by the client in the
131+
// thread_local.request_threads map. This way, if this
132+
// server thread needs to execute any RPCs that call back to
133+
// the client, they will happen on the same client thread
134+
// that is waiting for this function, just like what would
135+
// happen if this were a normal function call made on the
136+
// local stack.
137+
//
138+
// If the request_threads map already has an entry for this
139+
// connection, it will be left unchanged, and it indicates
140+
// that the current thread is an RPC client thread which is
141+
// in the middle of an RPC call, and the current RPC call is
142+
// a nested call from the remote thread handling that RPC
143+
// call. In this case, the callbackThread value should point
144+
// to the same thread already in the map, so there is no
145+
// need to update the map.
129146
auto& request_threads = g_thread_context.request_threads;
130147
auto request_thread = request_threads.find(server.m_context.connection);
131148
if (request_thread == request_threads.end()) {
@@ -136,8 +153,11 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn&
136153
/* destroy_connection= */ false))
137154
.first;
138155
} else {
139-
// If recursive call, avoid remove request_threads map
140-
// entry in KJ_DEFER below.
156+
// The requests_threads map already has an entry for
157+
// this connection, so this must be a recursive call.
158+
// Avoid modifying the map in this case by resetting the
159+
// request_thread iterator, so the KJ_DEFER statement
160+
// below doesn't do anything.
141161
request_thread = request_threads.end();
142162
}
143163
KJ_DEFER(if (request_thread != request_threads.end()) request_threads.erase(request_thread));
@@ -157,9 +177,15 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn&
157177
}
158178
});
159179

180+
// Lookup Thread object specified by the client. The specified thread should
181+
// be a local Thread::Server object, but it needs to be looked up
182+
// asynchronously with getLocalServer().
160183
auto thread_client = context_arg.getThread();
161184
return JoinPromises(server.m_context.connection->m_threads.getLocalServer(thread_client)
162185
.then([&server, invoke, req](const kj::Maybe<Thread::Server&>& perhaps) {
186+
// Assuming the thread object is found, pass it a pointer to the
187+
// `invoke` lambda above which will invoke the function on that
188+
// thread.
163189
KJ_IF_MAYBE(thread_server, perhaps)
164190
{
165191
const auto& thread = static_cast<ProxyServer<Thread>&>(*thread_server);
@@ -174,6 +200,7 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn&
174200
throw std::runtime_error("invalid thread handle");
175201
}
176202
}),
203+
// Wait for the invocation to finish before returning to the caller.
177204
kj::mv(future.promise));
178205
}
179206

@@ -1423,6 +1450,15 @@ void serverDestroy(Server& server)
14231450
server.m_context.connection->m_loop.log() << "IPC server destroy " << typeid(server).name();
14241451
}
14251452

1453+
//! Entry point called by generated client code that looks like:
1454+
//!
1455+
//! ProxyClient<ClassName>::M0::Result ProxyClient<ClassName>::methodName(M0::Param<0> arg0, M0::Param<1> arg1) {
1456+
//! typename M0::Result result;
1457+
//! clientInvoke(*this, &InterfaceName::Client::methodNameRequest, MakeClientParam<...>(arg0), MakeClientParam<...>(arg1), MakeClientParam<...>(result));
1458+
//! return result;
1459+
//! }
1460+
//!
1461+
//! Ellipses above are where generated Accessor<> type declarations are inserted.
14261462
template <typename ProxyClient, typename GetRequest, typename... FieldObjs>
14271463
void clientInvoke(ProxyClient& proxy_client, const GetRequest& get_request, FieldObjs&&... fields)
14281464
{
@@ -1511,6 +1547,13 @@ auto ReplaceVoid(Fn&& fn, Ret&& ret) ->
15111547

15121548
extern std::atomic<int> server_reqs;
15131549

1550+
//! Entry point called by generated server code that looks like:
1551+
//!
1552+
//! kj::Promise<void> ProxyServer<InterfaceName>::methodName(CallContext call_context) {
1553+
//! return serverInvoke(*this, call_context, MakeServerField<0, ...>(MakeServerField<1, ...>(Make<ServerRet, ...>(ServerCall()))));
1554+
//! }
1555+
//!
1556+
//! Ellipses above are where generated Accessor<> type declarations are inserted.
15141557
template <typename Server, typename CallContext, typename Fn>
15151558
kj::Promise<void> serverInvoke(Server& server, CallContext& call_context, Fn fn)
15161559
{
@@ -1526,6 +1569,13 @@ kj::Promise<void> serverInvoke(Server& server, CallContext& call_context, Fn fn)
15261569
using ServerContext = ServerInvokeContext<Server, CallContext>;
15271570
using ArgList = typename ProxyClientMethodTraits<typename Params::Reads>::Params;
15281571
ServerContext server_context{server, call_context, req};
1572+
// ReplaceVoid is used to support fn.invoke implementations that
1573+
// execute asynchronously and return promises, as well as
1574+
// implementations that execute synchronously and return void. The
1575+
// invoke function will be synchronous by default, but asynchronous if
1576+
// an mp.Context argument is passed, and the mp.Context PassField
1577+
// overload returns a promise executing the request in a worker thread
1578+
// and waiting for it to complete.
15291579
return ReplaceVoid([&]() { return fn.invoke(server_context, ArgList()); },
15301580
[&]() { return kj::Promise<CallContext>(kj::mv(call_context)); })
15311581
.then([&server, req](CallContext call_context) {

include/mp/proxy.h

Lines changed: 90 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,35 @@ class ProxyClientBase : public Impl_
6060
ProxyClientBase(typename Interface::Client client, Connection* connection, bool destroy_connection);
6161
~ProxyClientBase() noexcept;
6262

63-
// Methods called during client construction/destruction that can optionally
64-
// be defined in capnp interface to trigger the server.
63+
// construct/destroy methods called during client construction/destruction
64+
// that can optionally be defined in capnp interfaces to invoke code on the
65+
// server when proxy client objects are created and destroyed.
66+
//
67+
// The construct() method is not generally very useful, but can be used to
68+
// run custom code on the server automatically when a ProxyClient client is
69+
// constructed. The only current use is adding a construct method to Init
70+
// interfaces that is called automatically on construction, so client and
71+
// server exchange ThreadMap references and set Connection::m_thread_map
72+
// values as soon as the Init client is created.
73+
//
74+
// construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap: Proxy.ThreadMap);
75+
//
76+
// But construct() is not necessary for this, thread maps could be passed
77+
// through a normal method that is just called explicitly rather than
78+
// implicitly.
79+
//
80+
// The destroy() method is more generally useful than construct(), because
81+
// it ensures that the server object will be destroyed synchronously before
82+
// the client destructor returns, instead of asynchronously at some
83+
// unpredictable time after the client object is already destroyed and
84+
// client code has moved on. If the destroy method accepts a Context
85+
// parameter like:
86+
//
87+
// destroy @0 (context: Proxy.Context) -> ();
88+
//
89+
// then it will also ensure that the destructor runs on the same thread the
90+
// client used to make other RPC calls, instead of running on the server
91+
// EventLoop thread and possibly blocking it.
6592
void construct() {}
6693
void destroy() {}
6794

@@ -109,20 +136,52 @@ struct ProxyServerBase : public virtual Interface_::Server
109136
ProxyContext m_context;
110137
};
111138

112-
//! Customizable (through template specialization) base class used in generated ProxyServer implementations from
113-
//! proxy-codegen.cpp.
139+
//! Customizable (through template specialization) base class which ProxyServer
140+
//! classes produced by generated code will inherit from. The default
141+
//! specialization of this class just inherits from ProxyServerBase, but custom
142+
//! specializations can be defined to control ProxyServer behavior.
143+
//!
144+
//! Specifically, it can be useful to specialize this class to add additional
145+
//! state to ProxyServer classes, for example to cache state between IPC calls.
146+
//! If this is done, however, care should be taken to ensure that the extra
147+
//! state can be destroyed without blocking, because ProxyServer destructors are
148+
//! called from the EventLoop thread, and if they block, it could deadlock the
149+
//! program. One way to do avoid blocking is to clean up the state by pushing
150+
//! cleanup callbacks to the m_context.cleanup list, which run after the server
151+
//! m_impl object is destroyed on the same thread destroying it (which will
152+
//! either be an IPC worker thread if the ProxyServer is being explicitly
153+
//! destroyed by a client calling a destroy() method with a Context argument and
154+
//! Context.thread value set, or the temporary EventLoop::m_async_thread used to
155+
//! run destructors without blocking the event loop when no-longer used server
156+
//! objects are garbage collected by Cap'n Proto.) Alternately, if cleanup needs
157+
//! to run before m_impl is destroyed, the specialization can override
158+
//! invokeDestroy and destructor methods to do that.
114159
template <typename Interface, typename Impl>
115160
struct ProxyServerCustom : public ProxyServerBase<Interface, Impl>
116161
{
117162
using ProxyServerBase<Interface, Impl>::ProxyServerBase;
118163
};
119164

120-
//! Function traits class used to get method parameter and result types in generated ProxyClient implementations from
121-
//! proxy-codegen.cpp.
165+
//! Function traits class used to get method parameter and result types, used in
166+
//! generated ProxyClient and ProxyServer classes produced by gen.cpp to get C++
167+
//! method type information. The generated code accesses these traits via
168+
//! intermediate ProxyClientMethodTraits and ProxyServerMethodTraits classes,
169+
//! which it is possible to specialize to change the way method arguments and
170+
//! return values are handled.
171+
//!
172+
//! Fields of the trait class are:
173+
//!
174+
//! Params - TypeList of C++ ClassName::methodName parameter types
175+
//! Result - Return type of ClassName::method
176+
//! Param<N> - helper to access individual parameters by index number.
177+
//! Fields - helper alias that appends Result type to the Params typelist if
178+
//! it not void.
122179
template <class Fn>
123180
struct FunctionTraits;
124181

125-
//! Specialization of above to extract result and params types.
182+
//! Specialization of above extracting result and params types assuming the
183+
//! template argument is a pointer-to-method type,
184+
//! decltype(&ClassName::methodName)
126185
template <class _Class, class _Result, class... _Params>
127186
struct FunctionTraits<_Result (_Class::*const)(_Params...)>
128187
{
@@ -134,16 +193,20 @@ struct FunctionTraits<_Result (_Class::*const)(_Params...)>
134193
typename std::conditional<std::is_same<void, Result>::value, Params, TypeList<_Params..., _Result>>::type;
135194
};
136195

137-
//! Traits class for a method specialized by method parameters.
138-
//!
139-
//! Param and Result typedefs can be customized to adjust parameter and return types on client side.
196+
//! Traits class for a proxy method, providing the same
197+
//! Params/Result/Param/Fields described in the FunctionTraits class above, plus
198+
//! an additional invoke() method that calls the C++ method which is being
199+
//! proxied, forwarding any arguments.
140200
//!
141-
//! Invoke method customized to adjust parameter and return types on server side.
201+
//! The template argument should be the InterfaceName::MethodNameParams class
202+
//! (generated by Cap'n Proto) associated with the method.
142203
//!
143-
//! Normal method calls go through the ProxyMethodTraits struct specialization
144-
//! below, not this default struct, which is only used if there is no
145-
//! ProxyMethod::impl method pointer, which is only true for construct/destroy
146-
//! methods.
204+
//! Note: The class definition here is just the fallback definition used when
205+
//! the other specialization below doesn't match. The fallback is only used for
206+
//! capnp methods which do not have corresponding C++ methods, which in practice
207+
//! is just the two special construct() and destroy() methods described in \ref
208+
//! ProxyClientBase. These methods don't have any C++ parameters or return
209+
//! types, so the trait information below reflects that.
147210
template <typename MethodParams, typename Enable = void>
148211
struct ProxyMethodTraits
149212
{
@@ -157,7 +220,18 @@ struct ProxyMethodTraits
157220
}
158221
};
159222

160-
//! Specialization of above.
223+
//! Specialization of above for proxy methods that have a
224+
//! ProxyMethod<InterfaceName::MethodNameParams>::impl pointer-to-method
225+
//! constant defined by generated code. This includes all functions defined in
226+
//! the capnp interface except any construct() or destroy() methods, that are
227+
//! assumed not to correspond to real member functions in the C++ class, and
228+
//! will use the fallback traits definition above. The generated code this
229+
//! specialization relies on looks like:
230+
//!
231+
//! struct ProxyMethod<InterfaceName::MethodNameParams>
232+
//! {
233+
//! static constexpr auto impl = &ClassName::methodName;
234+
//! };
161235
template <typename MethodParams>
162236
struct ProxyMethodTraits<MethodParams, Require<decltype(ProxyMethod<MethodParams>::impl)>>
163237
: public FunctionTraits<decltype(ProxyMethod<MethodParams>::impl)>

0 commit comments

Comments
 (0)