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"); |
4c8b3db to
4cfc251
Compare
4821d99 to
4731f5d
Compare
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 |
54fa318 to
d2014d9
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.
d2014d9 to
22000b4
Compare
Looking over updated version of this (22000b4) I see that server side But I think the reference counting could be avoided by defining class CancelFn
{
virtual ~CancelFn() = default;
virtual void operator()() = 0;
};and I think doing this would also be good for type-safety. The current implementation of |
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.
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.
22000b4 to
0d7a2ff
Compare
The
Note that an argument is treated as a cancellation argument only if the method also declares it with Also, an application can still declare its own overloads over stricter types and have access to |
Yeah to be sure I'm just saying I think there may be an opportunity to simplify here, but the current approach seems fine. I do plan to review this, and if you are planning on working more on this I'd agree it's not very important to iterate on the exact types definitions here or avoid use of In case you are looking for followup work, it'd be interesting to see a Bitcoin core PR taking advantage of this, for example replacing the interrupt/interruptWait methods as suggested above and allowing native cancellation of mining methods that wait. (I think you could even use the existing |
Yes, sure. I’ve had Claude continuously testing the feature against Downstream source. I have some its changes locally, I just need to review and polish them before opening a PR. |
Done at bitcoin/bitcoin#36097 |
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.