Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[protocolv2] Half-close client #162

Merged
merged 1 commit into from
Jun 3, 2024
Merged

Conversation

masad-frost
Copy link
Member

Why

Starting to implement half-close semantics, this targets the client side. Please read Protocol.md in #127 to understand what's going on.

What changed

  • Some API changes
    • No more exposing "cleanup"/"close" functions, clients can only close inputWriter streams or request the outputWriter on the server to close.
    • procedure calls shape changes
      • stream returns an [inputWriter, outputReader] tuple
      • upload returns an [inputWriter, finalizerFn] tuple, where finalizerFn returns Promise<OutputResult>
      • subscription returns outputReader
      • rpc returns Promise<OutputResult>
  • I extracted procedure function types to make it easy to type check our proxy's methods
  • Replaced the various proc handlers with one main one (similar to how createProcStream works on the server), and then construct the return interface.
    • Bit of an aside: This makes sense since the underlying streams for all procedures should be the same. Even things like rpc sends CloseBit and OpenBit as part of init is an optional thing, and would work for other types of procs if we want. In theory, with rpc you can send init with an OpenBit only and then send a CloseControl message as a follow. In a more extreme world, your stream can send CloseBit and OpenBit as part of init which instantly closes the input (client-to-server) stream. We might want to update Protocol.md to indicate that all types of procedures work the same way, and things like rpc sending open and close in one message is just an optimization.
    • Subscription now sends a CloseBit with init, since we don't need the input stream.

@masad-frost masad-frost requested a review from a team as a code owner May 31, 2024 21:26
@masad-frost masad-frost requested review from jackyzha0 and removed request for a team May 31, 2024 21:26
@masad-frost
Copy link
Member Author

Test will fail miserably as I deferred fixing them to the server implementation

import { Value } from '@sinclair/typebox/value';
import { PayloadType, ValidProcType } from './procedures';

type RPCFn<
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
type RPCFn<
type RpcFn<

rust brain moment

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HTML or Html, URL or Url? 🤷

lol i'll change it on the base branch

router/client.ts Show resolved Hide resolved
}

if (!Value.Check(ControlMessageCloseSchema, msg.payload)) {
if (Value.Check(AnyResultSchema, msg.payload)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

completely unrelated to this pr but could be cool to do compiled versions of common schema checks:

https://github.com/sinclairzx81/typebox#typecheck-typecompiler

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah great idea.

infer __UploadInputMessage,
infer UploadOutputMessage,
(...args: never) => Promise<infer UploadOutputMessage>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

o damn nice

router/result.ts Show resolved Hide resolved
: never
: Procedure extends object & { stream: infer StreamHandler extends Fn }
? Awaited<ReturnType<StreamHandler>> extends [
? ReturnType<StreamHandler> extends [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still weird that some procedure constructors are async and others aren't but maybe thats fine 🤷

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just Rpc that returns a promise

@masad-frost masad-frost merged commit f9c6e25 into protocolv2 Jun 3, 2024
0 of 3 checks passed
@masad-frost masad-frost deleted the fm-half-close-client branch June 3, 2024 19:46
masad-frost added a commit that referenced this pull request Aug 16, 2024
All the changes are documented in `Protocol.md` but here's a summary:
- Handle invalid client requests by sending a close with an error back
- This was the main motivation for the change. While we could sort-of
implement this error response without the other changes, things are
setup in such a way where it is very hard to implement correctly without
deeper changes in how we handle closing.
- Add more robust closing mechanics
  - Half-close states
  - Close signals from read end of the pipes
  - Abort full-closure (for errors and cancellation)
- Switch from `Pushable` and `AsyncIterator` APIs to a `ReadStream` and
`WriteStream`
- All procedures have `init` and some have `input`

While the changes are not strictly backwards compatible, hence the major
protocol bump, the system can still operate across versions to some
extent.

See PRs linked below for more information on the above

# TODOs
- [x] Define protocol and update doc #111 
- [x] Design stream abstractions #118 
  - [x] Redsigned in #249 
- [x] Implement stream abstractions
  - [x] ReadStream #130
  - [x] WriteStream #132
- [x] All streams have init, some have input.
  - [x] Protocol change documented in #153
  - [x] Implementation change #159  
- [x] Use stream abstractions & implement protocol closing semantics
  - [x] Protocol: Implement close requests from readers #165 
  - [x] Protocol: Implement half-close
    - [x] Client #162 
    - [x] Server #163 
  - [x] Simple s/Pushable/Stream replacement
    - [x] Client #136 
    - [x] Server #137 
- [x] Make `Input` iterator on the server use `Result` so we can signal
stream closes, client disconnects, and aborts #172
- [x] Add Abort mechanism
  - [x] Docs update #175
  - [x] Implement abort
    - [x] Client #193
    - [x] Server #200
  - [x] Add `INVALID_REQUEST` to schema #107
- [x] Handle/send back `INVALID_REQUEST` errors with an abort bit #203
  - [x] Handle/send back `INTERNAL_RIVER_ERROR` with an abort bit #203 
  - [x] Send abort bit with `UNCAUGHT_ERROR` #201 
  - [x] Abort tombstones #204

- [ ] Try to find uncovered areas to test
   - [ ] `undefined` value for `init`, `input`, & `output`. 
- [ ] Update docs
- [ ] Changelog

---------

Co-authored-by: Jacky Zhao <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants