Design Documentation Update #229

pull Eunovo wants to merge 1 commits into bitcoin-core:master from Eunovo:design-doc changing 1 files +185 −2
  1. Eunovo commented at 7:42 am on October 22, 2025: contributor
    This PR contains an attempt to improve the design documentation to help new contributors to the repo. It adds more details about BuildField, ReadField and PassField. It explains how Callbacks, ThreadMaps and Async processing work.
  2. DrahtBot commented at 7:42 am on October 22, 2025: none

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK ryanofsky
    Concept ACK Sjors

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

    LLM Linter (✨ experimental)

    Possible typos and grammar issues:

    • resuse -> reuse [spelling error]
    • capabilites -> capabilities [spelling error]

    drahtbot_id_5_m

  3. Eunovo force-pushed on Oct 22, 2025
  4. Eunovo force-pushed on Oct 22, 2025
  5. ryanofsky commented at 3:39 pm on October 22, 2025: collaborator
    @theuni you may be interested in this, it describes things like how callbacks can be implemented
  6. enirox001 commented at 6:26 pm on October 23, 2025: none
    This is a helpful update. As someone getting familiar with this, the new details really helped clarify the design.
  7. Sjors commented at 10:10 am on October 24, 2025: member

    Concept ACK on expanding documentation, and thanks for adding these graphics.

    Reviewer hint, switch to the rich diff:

  8. in doc/design.md:46 in 459a730fe1
    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
    


    ryanofsky commented at 1:58 pm on October 27, 2025:

    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:

    • clientInvoke
    • BuildField (client)
    • ReadField (client)
    • Request message
    • serverInvoke
    • ReadField (server)
    • BuildField (server)
    • Response message

    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.

  9. in doc/design.md:61 in 459a730fe1
    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.
    


    ryanofsky commented at 2:09 pm on October 27, 2025:

    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

  10. in doc/design.md:87 in 459a730fe1
    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.
    


    ryanofsky commented at 2:18 pm on October 27, 2025:

    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

  11. in doc/design.md:65 in 459a730fe1
    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
    


    ryanofsky commented at 2:21 pm on October 27, 2025:

    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.

  12. in doc/design.md:91 in 459a730fe1
    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
    


    ryanofsky commented at 2:24 pm on October 27, 2025:

    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.

  13. in doc/design.md:92 in 459a730fe1
    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
    


    ryanofsky commented at 2:40 pm on October 27, 2025:

    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.

  14. in doc/design.md:179 in 459a730fe1
    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);
    


    ryanofsky commented at 2:56 pm on October 27, 2025:

    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:

    https://github.com/bitcoin-core/libmultiprocess/blob/a4f92969649018ca70f949a09148bccfeaecd99a/example/init.capnp#L17-L18

  15. in doc/design.md:204 in 459a730fe1
    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)
    


    ryanofsky commented at 3:01 pm on October 27, 2025:

    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.

  16. in doc/design.md:203 in 459a730fe1
    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)
    


    ryanofsky commented at 3:10 pm on October 27, 2025:

    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.

  17. in doc/design.md:185 in 459a730fe1 outdated
    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:
    


    ryanofsky commented at 3:19 pm on October 27, 2025:

    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.

  18. ryanofsky approved
  19. ryanofsky commented at 3:36 pm on October 27, 2025: collaborator
    Code review ACK 459a730fe1689ebbad827d066f3180ffbbf7bd57. These updates are very good, and I think information about callbacks and context parameters should be especially helpful. I left a number of suggestions below, which can be addressed here or in a followup. You can let me know which you prefer.
  20. Eunovo force-pushed on Oct 28, 2025
  21. Design doc update cc234be73a
  22. Eunovo force-pushed on Oct 28, 2025
  23. Eunovo commented at 8:08 pm on October 28, 2025: contributor
    Thanks for the review @ryanofsky . I made the suggested changes.
  24. ryanofsky approved
  25. ryanofsky commented at 12:48 pm on November 3, 2025: collaborator
    Code review ACK cc234be73a68bc6cbf4940742dbe924234810f5d. Thanks for the updates! All the descriptions seem accurate now, and hopefully the diagrams make it clear how the different classes fit together
  26. ryanofsky merged this on Nov 3, 2025
  27. ryanofsky closed this on Nov 3, 2025


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin-core/libmultiprocess. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2025-12-04 19:30 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me