BuildField, ReadField and PassField. It explains how Callbacks, ThreadMaps and Async processing work.
BuildField, ReadField and PassField. It explains how Callbacks, ThreadMaps and Async processing work.
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
See the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.
Possible typos and grammar issues:
drahtbot_id_5_m
Concept ACK on expanding documentation, and thanks for adding these graphics.
Reviewer hint, switch to the rich diff:
42+
43+ Note over clientInvoke,serverInvoke: Input Parameter Flow
44+ clientInvoke->>BuildField: BuildField(input_arg)
45+ BuildField->>Socket: Serialize input
46+ Socket->>ReadField: Cap'n Proto message
47+ ReadField->>serverInvoke: Deserialize input
In commit “Design doc update” (459a730fe1689ebbad827d066f3180ffbbf7bd57)
I think this is reasonable but one thing that’s a little off about this is it makes it seem like serverInvoke call is happening after the ReadField call, while the serverInvoke call is really the initial entry point on the server, and serverInvoke calls ReadField to read method inputs, then executes the method, then calls then BuildField to send method outputs. I think to fix this, I’d tweak participant list in the diagram to look like:
Moving serverInvoke participant before ReadField and replacing single Socket column with separate Request and Response columns.
Other fixes could be possible too, this is just what comes to mind.
57+
58+Parameters are represented as Fields that must be set on Cap'n Proto Builder objects (for sending) and read from Reader objects (for receiving).
59+
60+#### Building Fields
61+
62+`BuildField` uses a `StructField` (identifying which field by index) and a parameter `Accessor` to set the appropriate field in the Cap'n Proto Builder object.
In commit “Design doc update” (459a730fe1689ebbad827d066f3180ffbbf7bd57)
I think this section is basically right but it should drop the reference to StructField and just show BuildField using the Accessor directly, rather than going through StructField.
The StructField class is not actually relevant in most cases and is only used to help serialize certain structs like BlockRef which are using $Proxy.wrap annotation to automatically copy between C++ structs and Cap’n Proto structs that have the same fields.
Such structs are used in a few places, but are not very common. The use of Proxy.wrap for serializing structs is documented here
83+
84+The same process is used for building results on the server side with `Results::Builder`.
85+
86+#### Reading Fields
87+
88+`ReadField` uses a `StructField` (identifying which field by index) and a parameter `Accessor` to read the appropriate field from the Cap'n Proto Reader object and reconstruct C++ parameters.
In commit “Design doc update” (459a730fe1689ebbad827d066f3180ffbbf7bd57)
Again this should drop the reference to StructField and just show ReadField calling the Accessor directly rather than going through StructField
61+
62+`BuildField` uses a `StructField` (identifying which field by index) and a parameter `Accessor` to set the appropriate field in the Cap'n Proto Builder object.
63+
64+```mermaid
65+sequenceDiagram
66+ participant clientInvoke
In commit “Design doc update” (459a730fe1689ebbad827d066f3180ffbbf7bd57)
I think this would more accurately say “clientInvoke or serverInvoke” instead of just “clientInvoke”.
BuildField is used by clientInvoke on the client side to build Cap’n Proto request messages from C++ function parameters. But it is also used by serverInvoke on the server side to build Cap’n Proto response messages from C++ return values and output parameters and exceptions.
87+
88+`ReadField` uses a `StructField` (identifying which field by index) and a parameter `Accessor` to read the appropriate field from the Cap'n Proto Reader object and reconstruct C++ parameters.
89+
90+```mermaid
91+sequenceDiagram
92+ participant serverInvoke
In commit “Design doc update” (459a730fe1689ebbad827d066f3180ffbbf7bd57)
Again this could say “clientInvoke or serverInvoke” instead of just “serverInvoke” since ReadField is used by the serverInvoke to read C++ input parameters from the request message, but it is also used by clientInvoke to read C++ return values and output parameters and exception information from the response message.
88+`ReadField` uses a `StructField` (identifying which field by index) and a parameter `Accessor` to read the appropriate field from the Cap'n Proto Reader object and reconstruct C++ parameters.
89+
90+```mermaid
91+sequenceDiagram
92+ participant serverInvoke
93+ participant PassField
In commit “Design doc update” (459a730fe1689ebbad827d066f3180ffbbf7bd57)
I think I would drop reference to PassField column from this diagram and just show ReadField being called directly here by clientInvoke and serverInvoke.
The PassField function is mostly an implementation detail of the serverInvoke function and is really just wrapper for ReadField and BuildField called for each C++ parameter, and is responsible for skipping the BuildField call for input-only parameters, and skipping the ReadField call for output-only parameters. PassField is actually covered pretty well in the next section (“Server-Side Request Processing”), so it seems fine not to include in this section.
Reason I think ReadField and BuildField are more useful to mention than PassField is that while it is possible for libmultiprocess applications to provide CustomPassField functions like they can provide CustomReadField and CustomBuildField functions for custom parameter handling, this is rarely needed and overriding CustomReadField and CustomBuildField is more useful and more common.
175+Thread mapping enables each client thread to have a dedicated server thread processing its requests, preserving thread-local state and allowing recursive mutex usage across process boundaries.
176+
177+Thread mapping is initialized by defining an interface method with a `ThreadMap` parameter and/or response, such as:
178+
179+```capnp
180+initThreadMap @4 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap);
In commit “Design doc update” (459a730fe1689ebbad827d066f3180ffbbf7bd57)
I think I’d tweak this example a little to replace initThreadMap [@4](/bitcoin-core-multiprocess/contributor/4/) with construct [@0](/bitcoin-core-multiprocess/contributor/0/) because while it is possible to pass ThreadMap references in an manual method call like this, it usually makes sense to pass them automatically, as soon as the connection is created, by passing them in the init interface construct method which is called automatically by the libmultiprocess c++ client when the client proxy object is constructed.
The current unit tests do use a manual initThreadMap method for some manual testing, but if you look at the example code or bitcoin core code, they use Init.construct methods instead, so ThreadMaps are exchanged automatically:
200+
201+When a method has a `Context` parameter:
202+
203+**Client side** (`CustomBuildField`):
204+1. Creates a local `Thread::Server` object for the current thread (stored in `callback_threads` map)
205+2. Calls `connection.m_thread_map.makeThreadRequest()` to request a dedicated worker thread on the server (stored in `request_threads` map)
In commit “Design doc update” (459a730fe1689ebbad827d066f3180ffbbf7bd57)
It would be good to be more explicit that a new server thread is created only when the current client thread is making an IPC call to this server for the very first time.
If this client thread has made any IPC calls to the server previously, it will reuse the thread that it first created and stored in the request_threads map.
It would also be good to say that the remote thread object is set in the Context.thread field sent in the request.
199+```
200+
201+When a method has a `Context` parameter:
202+
203+**Client side** (`CustomBuildField`):
204+1. Creates a local `Thread::Server` object for the current thread (stored in `callback_threads` map)
In commit “Design doc update” (459a730fe1689ebbad827d066f3180ffbbf7bd57)
Few suggestions:
Would probably switch order of (1) and (2) in this list since primary purpose of the context struct is to allow cilent to specify threads for the server to execute calls on by setting the Context.thread field. The Context.callbackThread field is also useful to let the server call back into the calling thread, so things like recursive mutexes work as expected, but this functionality is not used as much.
Would be nice to mention the local Thread::Server reference is saved in the Context.callbackThread field of the context struct passed to the server, in addition to being saved in the callback_threads maps.
Would be nice to mention a new Thread::Server is only created the first time this thread makes an IPC call to the server. After that the callback_threads map value is reused.
190+
191+When both parameter and response include ThreadMap, both processes end up with `ThreadMap::Client` capabilities pointing to each other's `ThreadMap::Server`, allowing both sides to create threads on the other process.
192+
193+### Async Processing with Context
194+
195+By adding a `Context` parameter to a method in the capnp interface file, you enable async processing where the client tells the server to execute the request in a separate worker thread. For example:
In commit “Design doc update” (459a730fe1689ebbad827d066f3180ffbbf7bd57)
Would be good to say that if a method does not have a Context parameter, then libmultiprocess will execute IPC requests invoking that method on the I/O event loop thread.
This is ok if the method is fast and non-blocking, but should be avoided if the method is slow or blocks waiting for resources, or makes any IPC calls (including callbacks to the client), since as long as the method is executing, the Cap’n Proto event loop will not be able to perform any I/O.