doc: Add internal design section #111

pull TheCharlatan wants to merge 1 commits into bitcoin-core:master from TheCharlatan:internalDocs changing 1 files +14 −0
  1. TheCharlatan commented at 8:53 PM on September 9, 2024: collaborator

    I am not sure if the descriptions here are correct, or even all that useful, but I think some of the finer points are useful to document to serve as an anchor for future developers reading the code. Maybe I also missed some documentation in the headers and the other docs that already describe these things better.

    ~What could potentially also be useful are some diagrams to show the wrapping hierarchy as done below (again, not sure if actually correct, and given the polymorphism and templating going on it is also questionable if such a hierarchy can accurately be depicted as a diagram).~

    Given the points made by ryanofsky below, I don't think anymore than an object diagram will be particularly helpful.

    A sequence diagram that follows a call from its c++ callsite to the client invocation to the server receiving and processing it, the client resolving it and finally returning a c++ type again would probably be more useful.

  2. ryanofsky commented at 4:18 PM on September 10, 2024: collaborator

    Thanks, this all seems helpful and correct reading it at a first pass. I think I would just tweak a few things and probably add a few additional details. It think it's especailly helpful this notes that libmultiprocess is just acting as a wrapper to make it easier to write and call cap'nproto interfaces from c++. But it is completely possible to call or implement the same interfaces without using libmultiprocess (especially from other languages like rust python and javascript).

    One thing that is a little off in 86415b456bb3307afae406c2b1b5cbc5d0c143b3 is that it makes it sound like ProxyClient and ProxyServer objects wrap ends of socket, when it would be more accurate just to say that they communicate over sockets. Specifically when there is a socket connection there is not just one ProxyClient and one ProxyServer object sitting at either end of the socket, but one ProxyClient and one ProxyServer for each instantiated interface, and probably dozens or hundreds of ProxyClient and ProxyService objects all using the same socket.

    If you want to mention the class that does sit at either end of a socket, that would just be the mp::Connection class defined in mp/proxy-io.h

    Similarly, in diagram from PR description it could give wrong impression to show AsyncioContext containing AsyncIoStream, because the same context can be used to communicate across many streams. For example, if there is a bitcoin-node process, it may spawn a bitcoin-wallet and communicate across one stream with it, with but also accept bitcoin-mine connections and use other streams to communicate with them. All these streams will use the same EventLoop and AsyncioContext.

    It is true in both of these 1:N relationships though, that not all of the N are equal. Even though there will be many ProxyClient/ProxyServer clients using the same socket, and many AsyncIoStreams using the same AsyncioContext and EventLoop, users are not undifferentiated and identical. While the EventLoop uses many AsyncIoStreams it does have one special wait_stream that is used to receive requests posted to the event loop. Similarly, when a connection is first established, there is a initial ProxyClient and ProxyServer pair wrapping an Init interface, which is special because it is the first interface, and it what is used to create all other interfaces, and ProxyClient and ProxyServer objects wrapping the interfaces that are created.

    Anyway these changes look good, and I'm happy to merge them anytime you want.

  3. TheCharlatan force-pushed on Sep 11, 2024
  4. TheCharlatan commented at 1:11 PM on September 11, 2024: collaborator

    Thank you for the feedback, I tweaked the docs a bit to make the relationship between connections, proxy objects and capnp objects a bit clearer. One thing I have been wondering about is the naming and usage of Field. I am guessing this comes from capnp proto naming scheme, but is that always entirely appropriate here?

  5. in doc/design.md:18 in 4b8a128050 outdated
      10 | @@ -11,6 +11,16 @@ If the wrapped C++ object inherits from an abstract base class declaring virtual
      11 |  
      12 |  There is also optional support for thread mapping, so each thread making interprocess calls can have a dedicated thread processing requests from it, and callbacks from processing threads are executed on corresponding request threads (so recursive mutexes and thread names function as expected in callbacks).
      13 |  
      14 | +Libmultiprocess acts as a pure wrapper or layer over the underlying protocol. Clients and servers written in other languages, but using a shared capnproto schema can communicate with interprocess counterparties using libmultiprocess without having to use libmultiprocess themselves or having to know about the implementation details of libmultiprocess.
      15 | +
      16 | +### Internals
      17 | +
      18 | +The `ProxyClient` and `ProxyServer` generated classes are not directly exposed to the user, as described in [usage.md](usage.md). Instead, they wrap c++ interfaces and appear to the user as pointers to an interface. They are first instantiated when calling `ConnectStream` and `ServeStream` respectively for creating the `InitInterface`. These methods setup a `Connection` through a socket and wrap a `capnp::RpcSystem` configured for client and server mode respectively. Once more interfaces are instantiated from the `InitInterface`, they communicate over the same connection. Both `ConnectStream` and `ServerStream` also require an instantiation of the `EventLoop`. The `EventLoop` owns pending requests, notifies on request dispatch, allows clients from multiple threads to make synchronous calls, and handles some cleanup routines on exit. It must be run in a separate thread to allow calls being issued from multiple distinct threads.
    


    ryanofsky commented at 5:53 PM on September 11, 2024:

    In commit "doc: Add internal design section" (86415b456bb3307afae406c2b1b5cbc5d0c143b3)

    This looks good as is, but few things come to mind reading this:

    • "These methods setup a Connection" could be clearer since technically there are 2 Connection objects for each connection (ConnectStream creates one when it is called on the connecting side, and ServeStream creates one each time there is an incoming connection on the side receiving connections). Would maybe change to "These methods establish connections, internally creating Connection objects, with each Connection wrapping a capnp::RpcSystem

    • May clarify "Once more interfaces are instantiated from the InitInterface" as "The InitInterface interface will typically have methods which return other interfaces, giving the connecting process the ability to call other functions in the serving process. Interfaces can also have methods accepting other interfaces as parameters, giving serving processes ability to call back and invoke functions in connecting processes. Creating new interfaces does not create new connections, and typically many interface objects will share the same connection."

    • Would maybe change "It must be run in a separate thread to allow calls..." to "It must run in a separate thread so it is always active and can process incoming requests from local clients and remote connections."

  6. in doc/design.md:20 in 4b8a128050 outdated
      15 | +
      16 | +### Internals
      17 | +
      18 | +The `ProxyClient` and `ProxyServer` generated classes are not directly exposed to the user, as described in [usage.md](usage.md). Instead, they wrap c++ interfaces and appear to the user as pointers to an interface. They are first instantiated when calling `ConnectStream` and `ServeStream` respectively for creating the `InitInterface`. These methods setup a `Connection` through a socket and wrap a `capnp::RpcSystem` configured for client and server mode respectively. Once more interfaces are instantiated from the `InitInterface`, they communicate over the same connection. Both `ConnectStream` and `ServerStream` also require an instantiation of the `EventLoop`. The `EventLoop` owns pending requests, notifies on request dispatch, allows clients from multiple threads to make synchronous calls, and handles some cleanup routines on exit. It must be run in a separate thread to allow calls being issued from multiple distinct threads.
      19 | +
      20 | +When a generated method on the `ProxyClient` is called, it calls `clientInvoke` with the capnp-translated types. `clientInvoke` creates a self-executing promise (`kj::TaskSet`) that drives the execution of the request and gives ownership of it to the `EventLoop`.
    


    ryanofsky commented at 6:04 PM on September 11, 2024:

    In commit "doc: Add internal design section" (86415b456bb3307afae406c2b1b5cbc5d0c143b3)

    Might be worth adding that after clientInvoke sends the request, it blocks waiting until a response is received, or until there is a call from the server that needs to run on the same client thread, using a Waiter object.

  7. in doc/design.md:22 in 4b8a128050 outdated
      17 | +
      18 | +The `ProxyClient` and `ProxyServer` generated classes are not directly exposed to the user, as described in [usage.md](usage.md). Instead, they wrap c++ interfaces and appear to the user as pointers to an interface. They are first instantiated when calling `ConnectStream` and `ServeStream` respectively for creating the `InitInterface`. These methods setup a `Connection` through a socket and wrap a `capnp::RpcSystem` configured for client and server mode respectively. Once more interfaces are instantiated from the `InitInterface`, they communicate over the same connection. Both `ConnectStream` and `ServerStream` also require an instantiation of the `EventLoop`. The `EventLoop` owns pending requests, notifies on request dispatch, allows clients from multiple threads to make synchronous calls, and handles some cleanup routines on exit. It must be run in a separate thread to allow calls being issued from multiple distinct threads.
      19 | +
      20 | +When a generated method on the `ProxyClient` is called, it calls `clientInvoke` with the capnp-translated types. `clientInvoke` creates a self-executing promise (`kj::TaskSet`) that drives the execution of the request and gives ownership of it to the `EventLoop`.
      21 | +
      22 | +On the server side, the `capnp::RpcSystem` receives the capnp request and invokes the corresponding c++ method through the corresponding `ProxyServer` and the heavily templated `serverInvoke` triggering a `ServerCall`. Its result is then converted back by invoking `ServerRet`. The two are connected through `ServerField`. The main method driving execution of a request is `PassField`, which is invoked through `ServerField`. Instantiated interfaces, or capabilities in capnp speak, are tracked and owned by the server's `capnp::RpcSystem`.
    


    ryanofsky commented at 6:15 PM on September 11, 2024:

    In commit "doc: Add internal design section" (86415b456bb3307afae406c2b1b5cbc5d0c143b3)

    Could expand "Its result is then converted back by invoking ServerRet." Maybe say "Return values from the c++ methods are copied into Cap'nProto responses by ServerRet and exceptions are caught and copied by ServerExcept".

    Also from this description it sounds like ServerField should probably be renamed to ServerArg or ServerParam. But none of these names are very good, they should probably be named more like ServerMethodArgumentHandler ServerMethodReturnValueHandler ServerMethodExceptionHandler


    TheCharlatan commented at 9:17 PM on September 11, 2024:

    These suggested names sound good to me, More verbose, but they convey the meaning more clearly.

  8. ryanofsky approved
  9. ryanofsky commented at 6:18 PM on September 11, 2024: collaborator

    ACK 86415b456bb3307afae406c2b1b5cbc5d0c143b3

    I had some ideas reading this so left a bunch of suggestions, but I'm happy to merge it as, just let me know. I think everything you wrote is accurate and this is definitely an improvement

  10. TheCharlatan commented at 7:43 PM on September 11, 2024: collaborator

    Thanks for the comments, will pour over this some more and hopefully come up with more improvements.

  11. doc: Add internal design section 1fa2ca7cd8
  12. TheCharlatan force-pushed on Sep 11, 2024
  13. TheCharlatan commented at 9:25 PM on September 11, 2024: collaborator

    Took your improvements @ryanofsky, and added some minor corrections.

  14. ryanofsky commented at 2:32 PM on September 12, 2024: collaborator

    ACK 1fa2ca7cd804d097d02eb7ad0927832e40c3f4a5. Looks good, and nice job incorporating the extra information, especially about the waiter object. I'll go ahead and merge this now but feel free to make more changes or suggest improvements.

  15. ryanofsky merged this on Sep 12, 2024
  16. ryanofsky closed this on Sep 12, 2024

  17. Sjors referenced this in commit fd53ceb460 on Oct 1, 2024
  18. Sjors referenced this in commit 2a4c0e8dad on Oct 4, 2024
  19. Sjors referenced this in commit 7ad85d40e8 on Oct 4, 2024
  20. Sjors referenced this in commit a5ea996cdf on Oct 5, 2024
  21. Sjors referenced this in commit 9b1bdabe9c on Oct 7, 2024
  22. ryanofsky referenced this in commit 965838b65f on Oct 8, 2024
  23. ryanofsky referenced this in commit 41d94fa0c3 on Oct 8, 2024
  24. ryanofsky referenced this in commit 90b405516f on Oct 16, 2024
  25. fanquake referenced this in commit 563c4d2926 on Oct 21, 2024
  26. janus referenced this in commit eb4d842678 on Jan 19, 2025
  27. bitcoin-core locked this on Sep 12, 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: 2026-04-20 19:30 UTC

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