Allow request cancellation for wrapped C++ methods - #342
Conversation
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ReviewsSee the guideline and AI policy for information on the review process.
If your review is incorrectly listed, please copy-paste ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
3300995 to
0986a13
Compare
|
CI failures seem unrelated |
|
Nice work! I've had a number of ideas about this feature over the years so it's really interesting to see it implemented. I will just give some quick thoughts for now so I don't get nerdsniped and wind up spending all day or more on this. Thoughts:
EDIT: Added |
|
Thanks for your feedback @ryanofsky. Sorry for the late response. I took some time to fill some C++ gaps (mostly to understand whether client-side detection of the
This makes complete sense. I believe I now grasp the original idea. This is cleaner for consumers and non-libmultiprocess clients, which don't need a
Yes, you're correct here as well.
This is interesting. I think we could combine this with
I prefer this over the dedicated hooks. However, I don't fully understand how it would handle the client side. For So I think the hook can tell
Yes, definitely we can cover both sides in this PR. I need to think about this more though, together with the mpgen issue above.
Ok perfect. I'll drop it.
Thanks, I really thought it wasn't caused by this PR, but I guess I had it wrong. I have some WIP rework locally, so these errors may be different the next time I push. |
Hmm yeah. Actually when I wrote this I wasn't thinking about the need to unregister, and the
You're right, I wasn't thinking about the client side of this very clearly. I was thinking as the client code was processing parameters it could use So I think a capnproto method annotation of some kind might be necessary. I was thinking of adding annotations anyway to support more flexible mapping of c++ parameters to capnproto parameters like: in context of #282. But something less general could also work well here. |
The vector of callbacks exists because another callback (pre-existing) is registered in
So during a cancellable request, there are two registrations: one internal, and another the wrapped method itself registers. But I do agree it looks a bit awkward. The raw pointers are there so
Good, this settles the client side then. I'd like to start with something minimal that we can scale up later into something more general. I was thinking of a method annotation (your waitValue @7 (context :Proxy.Context, timeoutMs :Int32) -> (result :Int32) $Proxy.extraParam("cancel"); |
0986a13 to
5b6dc15
Compare
Add a `$Proxy.extraParam` method annotation that declares an extra C++-only parameter in the generated method signature. The parameter has no corresponding capnp parameter and is not serialized or sent over RPC. The annotation value names the parameter in generated C++ code. Client behavior: - If a matching `CustomBuildExtraParam(TypeList<T>, ClientInvokeContext&, T&&)` overload exists, the parameter is passed to it. - Otherwise, the parameter is discarded before the RPC message is dispatched. Server behavior: - A matching `CustomReadExtraParam(TypeList<T>, ServerContext&)` overload MUST be implemented. No data arrives for this parameter so this overload reconstructs the parameter value on the server side. Constraints: - Only one extra parameter is allowed per method. - The extra parameter is expected to be the last parameter in the C++ method signature. The test checks the value the client passes is discarded and the one the server reconstructs arrives instead.
…`request_mutex` The mutex guards the request's params and results structs, not the cancellation itself. The old names would be confusing next to the CancelState class added in the following commits. Pure rename, no behavior change.
Add `CancelState` to share request-cancellation state between an executing IPC method and the event loop thread (which dispatch cancelations). It stores the cancellation flag and a callback that may be registered through cancel arguments. Replace `CancelMonitor`'s `m_canceled` member and `ServerInvokeContext`'s `request_canceled` member with a `cancel_state` pointer and a `request_canceled()` helper that reads it. Behavior is unchanged. This prepares for later commits where methods with a cancellation extra parameter register a callback on the state.
4c8b3db to
4cfc251
Compare
4cfc251 to
4821d99
Compare
Add `ClientCancelState` and `RequestCanceler`. `ClientCancelState` is created by `clientInvoke`, tracks whether the call was canceled, and can cancel the request promise from any thread. `RequestCanceler` inherits from `kj::Canceler`, wraps the request promise, and is attached to it. Canceling rejects the wrapped promise, wakes the blocked client thread through the exception path, and makes the call throw `InterruptException`. The next commit adds the `CustomBuildExtraParam` overload that lets callers cancel the call. Nothing triggers cancellation yet.
Add type-cancel.h, which defines the cancellation argument types and their extra-parameter overloads. - On the client side, any `std::function<void(std::function<void()>)>` declared with `$Proxy.extraParam` receives a function that cancels the request. - On the server side, it registers a callback to run when cancellation is detected.
4821d99 to
4731f5d
Compare
The `$Cxx.allowCancellation` annotation used by the cancellation tests does not exist in older versions. Remove the configure-time checks that only covered them, and move the olddeps CI config to 1.0.0.
- One test cancels an in-flight `ProxyClient` call from another thread. - Another drops the response promise mid-execution, imitating non-libmultiprocess clients.
4731f5d to
f6944b2
Compare
|
@ryanofsky Just addressed your comments, rebased with master, and pushed. Added 9422b97, which supports the Updated the description too. Thanks for your feedback :) |
|
Thanks for the updates! And approach ACK f6944b2. I think it's good to add client and server cancellation support together in the same PR. Quickly skimming changes, the It would also be great to see this put to use in bitcoin core by dropping the |
Currently, libmultiprocess requests are blocking, with no way to cancel them from either the client or server side. Downstream code works around this by pairing each blocking method with a dedicated
interruptX()method whose only purpose is to wake it up. For example, bitcoin/bitcoin#33676 introducedBlockTemplate::interruptWait()specifically to wake an in-progressBlockTemplate::waitNext()call.Cap'n Proto provides a useful cancellation mechanism that libmultiprocess can use to support cancellation from both non-libmultiprocess and libmultiprocess clients. When a promise is dropped, it sends a cancellation request. The server may either cancel immediately or ignore the signal.
This PR implements approach 4 from bitcoin/bitcoin#33575, adding cancellation support on both sides:
ProxyClientmethod calls to be canceled from another thread through a cancel function. This is backed by akj::Cancelerthat wraps the request promise and is attached to it. A canceled call throwsInterruptException.The PR also adds a new capnp annotation,
$Proxy.extraParam, which declares C++-only parameters that are not sent through RPC. These parameters are handled byCustomBuildExtraParamon the client side andCustomReadExtraParamon the server side. The first user of this mechanism is the cancellation parameter typestd::function<void(std::function<void()>)>.For example, this capnp schema method:
maps to a C++ method with a trailing cancellation argument, without requiring any libmultiprocess type in the interface header:
On the client, the argument receives a function that can cancel the call:
On the server, the implementation registers a callback that interrupts its wait:
Detecting a dropped promise on the server requires the
$Cxx.allowCancellationannotation on the method, file, or interface. Without it, Cap'n Proto runs the abandoned call to completion. The annotation requires Cap'n Proto 1.0 (see the "Breaking change" section in https://capnproto.org/news/2023-07-28-capnproto-1.0.html), which this PR also sets as the minimum supported version.