Description
Specification
The cancel
parameter exposed in the handler actually cancels the underlying transport stream. This is needed so the middleware can cancel the stream without needing access to the ends of the stream. However, this is not strictly necessary for handlers, and the higher-level handlers should not have access to the underlying webstream implementation. Maybe ClientHandler
and DuplexHandler
should still provide a cancel
to the handler, which would abort the incoming stream from the client instead of closing the underlying webstream.
The return value for the client is all a Promise
. This means that the client can't really cancel the stream by doing pC.cancel()
. Even generating and passing in a new context will not work, as the contexts for client call and the server handler are isolated.
Actually, PromiseCancellable
implements exactly this, but it hasn't been properly incorporated into the project, as only RPCServer.ts
uses it, and it is not listed as an explicit dependency of the project. So, it needs to be listed as a dependency and be used in RPCClient.ts
in the return types of a caller.
To properly implement cancellation, the client can cancel the function call by using f.cancel()
and optionally provide a reason. The reason can be an error, in which case the error will get converted into a code which corresponds uniquely to that error. Then, that code can be sent over the transport layer to the server, which can then know which error was thrown based on the code. If the code has not been recognised, then a generic error can be thrown.
To build this new error codes, we can separate the error codes to transport errors and application errors. Transport errors happen when the transport layer itself encounters an error. Application errors should happen on the application layer itself. Strings can't be serialised as the error codes are 32-bit integers.
If this cancellation has been done from the client, the RPC should update the handler context and send it an abort signal. While the underlying stream would have been closed upon abortion, the handler would still be able to keep running to perform cleanup.
The behaviour of cancellation wasn't clear in the code, and it relied on either tests and existing knowledge of the code base. This is not good. The code should have documentation and it should also be mentioned in the README.
Other discussions
Another idea was explored to handle cancellation, where the context passed to the client caller should be passed through to the server itself, too. However, this solution had some issues, like timeout is modified depending on the server and client timeout values. Moreover, the contexts won't actually be connected on the client and handler, so an update message or an abort message needs to be sent to synchronise the contexts. However, unary handlers are designed to take in only a single message and not multiple messages, so this would result in a massive code overhaul and other issues, so it doesn't make much sense to implement this solution.
Additional context
- Summary of changes needed in this issue: polykey#846 (comment)
- Integer limit of error codes in quiche: (google/quiche) main:quiche/quic/core/quic_error_codes.h
- Related issue, and this might need to be resolved with this one: js-rpc#18
Tasks
- Add
async-cancellable
topackage.json
- Properly integrate
PromiseCancellable
in bothRPCServer
andRPCClient
- Remove
cancel
parameter from the high-level handler and the caller, keeping it for duplex and client streams if needed - Allow cancelling a function call from the client easily
- Improve documentation for cancellation in both the code and the README
- Update the tests to explicitly test cancellation properly