Allow wrapped C++ methods to detect request cancellations - #342
Allow wrapped C++ methods to detect request cancellations#342xyzconstant wants to merge 6 commits into
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. |
Methods can declare a `cancel :Proxy.Cancel` parameter, which maps to a C++ `CancelToken` argument and lets the method detect when its request has been abandoned by the client, either because the client disconnected or because the promise was dropped. This commit only defines the schema type and makes `mpgen` reject a `Cancel` parameter if no preceding `Context` parameter exists. Later commits will enable cancellation at capnp layer and introduce the `CancelToken` type.
…ameter By default, the RPC system runs calls to completion even after the client abandons them. This commit overrides `dispatchCall` in `ProxyServerBase` to allow cancellation of calls to methods that declare an `mp.Cancel` parameter, detected from the interface schema. The lookup mirrors capnp's own dynamic dispatch: https://github.com/capnproto/capnproto/blob/7dbb95989721016f8b590245ec7528c6ff03d1fe/c++/src/capnp/dynamic-capability.c++#L61-L67 On Cap'n Proto v1.0+, this sets the `allowCancellation` flag on the dispatch result, older versions use `CallContext::allowCancellation()` (see the "Breaking change" note in https://capnproto.org/news/2023-07-28-capnproto-1.0.html). This is the automatic equivalent of the `$Cxx.allowCancellation` annotation, without requiring a schema annotation.
`CancelState` holds a cancellation flag and callback registry shared between an executing IPC method and the thread canceling its request. `CancelToken` is the handle a method polls to detect cancellation, and `OnCancel` registers an RAII callback that can wake a method blocked in a wait. The three classes have the same semantics as `std::stop_source`, `std::stop_token`, and `std::stop_callback`, which are not available on all supported platforms.
…`request_mutex` The mutex guards the request's params and results structs, not the cancellation itself. The old names predate the CancelState class added in the previous commit and would be confusing next to it. Pure rename, no behavior change.
Replace `CancelMonitor`'s `m_canceled` and `m_on_cancel` members with a `CancelState` 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 the next commit, where methods with an `mp.Cancel` parameter observe the same state through a `CancelToken`.
Add type-cancel.h with serialization overloads for `Cancel` parameters: - The client-side overload sends an empty field and logs a warning if a live token is passed. - The server-side overload passes the method a `CancelToken` observing the request's cancellation state. Additionally, add a test that checks dropping the client promise cancels an executing method, and document the feature in design.md.
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:
|
Currently, a server method runs to completion regardless of what the client does, even if the client disconnects. If the client wants to stop waiting there is no way to tell the server. Downstream works around this by coupling each blocking method with an
interruptX()method designed solely to wake it up. For example, bitcoin/bitcoin#33676 introducedBlockTemplate::interruptWait()specifically to wake aBlockTemplate::waitNext()that is in progress.A non-C++ client would expect the server to stop when it drops the call, since that is how cancellation normally works in Cap'n Proto but the method keeps running to completion anyway. What's missing is a way for wrapped C++ methods to detect the cancellation.
This PR implements approach 4 suggested in bitcoin/bitcoin#33575. It introduces a new
Cancelproxy parameter that maps to aCancelTokenC++ parameter, so methods can stop long-running jobs as soon as the promise executing the request is abandoned. For instance, the following capnp schema method:Could be defined in C++ this way, blocking in a
std::condition_variable::wait()until either the job finishes or the request is canceled:Notice that
Cancelmust be declared after aContextparameter. Otherwise, the method will run on the event loop thread, where cancellations are delivered, and will run until completion with a token that will never fire. This rule is enforced by mpgen.Some implementation details:
ProxyServerBasenow overridesdispatchCall, the capnp-generated entry point the RPC system uses to route incoming requests. This override inspects each method's parameters and allows Cap'n Proto to cancel calls to methods that declareCancel. This is equivalent to$Cxx.allowCancellationbut is derived from the schema rather than an annotation. Older capnp versions (<1.0) also benefit from this (see proxy-io.h).CancelStateto extendCancelMonitor's singlem_on_cancelcallback,CancelTokento report cancellations to wrapped C++ methods, andOnCancelto install callbacks via RAII. Together, they mirrorstd::stop_source/std::stop_token/std::stop_callback.Please note, this work is server-side only. When you call a
ProxyClient's method, it will still block atclientInvokeand wait for the promise to resolve. If needed, we could consider makingclientInvokecancellable in the future.