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] Implement server abort #200

Merged
merged 1 commit into from
Jun 18, 2024
Merged

Conversation

masad-frost
Copy link
Member

@masad-frost masad-frost commented Jun 14, 2024

Why

Server-side implementation for #175

What changed

For tests, recommend disabling whitespace diff as i nested tests in more describe blocks

Mostly abort implementation which is symmetrical with the client implementation. The handler context has mechanisms to deal with abort.

  • Handler context interface renamed from ServiceContextWithTransportInfo is now ProcedureHandlerContext.
  • Added doc comments to context
  • Removed from and to from context, they can be retrieved from session
  • Removed streamId from context as it's an internal thing
  • Added abortController to context, this is the controller for which the handler can decide to abort the request
  • Added clientAbortSignal to context, this is a signal that is triggered when the client sends an abort, or when the client disconnects.

I'm honestly not super happy with the interface, i think it can be confusing to have a controller and a signal that are separate, ideas welcome. One idea I had is to pass an abort function to the context, and an abort signal that is triggered when any of happens:

  • Abort by the client.
  • Abort by the procedure handler.
  • Request completed / normal closure.
  • Client disconnected.

with the abort reason indicating what happened. Updating the interface should be easy, but this PR gets us to a place where we can iterate on the interface.

@masad-frost masad-frost requested a review from a team as a code owner June 14, 2024 19:20
@masad-frost masad-frost requested review from Monkatraz and jackyzha0 and removed request for a team June 14, 2024 19:20
@masad-frost masad-frost mentioned this pull request Jun 14, 2024
32 tasks
Copy link
Contributor

@Monkatraz Monkatraz left a comment

Choose a reason for hiding this comment

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

lgtm, probably wanna wait for Jacky.

and yeah I think there probably is something more intuitive for the abort props, maybe making the two props feel "symmetrical" would help, like with reader and writer

router/context.ts Show resolved Hide resolved
Comment on lines +451 to +452
// TODO handle never resolving after cleanup/full close
// which would lead to us holding on to the closure forever
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you even avoid the memory leak if the handler never resolves? You could have a timeout condition (where the timeout only starts after everything is closed) but the handler would still have a reference to a lot of things in the context which may force it to stick around. Still would be better than stalling forever though

Copy link
Member Author

Choose a reason for hiding this comment

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

we just need to make sure we avoid capturing things that are not needed in a closure, but i think there's no way about it. I think we should just have a log "procedure X didn't exit after Y seconds of stream end" or something.

* Aborts are not the same as closing streams gracefully, please refer to
* the river documentation to understand the difference between the two concepts.
*/
abortController: AbortController;
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if making this a function named something like abortProcedureUngracefully would encourage the right practices here (not really a throw, though)

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought about using a function here, but i wanted to give people a way to track their own aborts, so that they can go abortController.signal. I think we'll have to see some people use this stuff and iterate

]);
expect(clientOutputReader.isClosed());
expect(clientInputWriter.isClosed());
});
});
Copy link
Member

Choose a reason for hiding this comment

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

we can probably simplify this test code into only the e2e path

Copy link
Member Author

Choose a reason for hiding this comment

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

🫡

* handshake metadata with some auth mechanism to identify the
* the request sender.
*/
session: Session<Connection>; // TODO: only expose a subset interface of session
Copy link
Member

Choose a reason for hiding this comment

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

we can probably also just use Omit<Session<Connection>, ...> here so we do it at the type level, idt it matters if we actually strip stuff

Base automatically changed from fm-abort-client-side to protocolv2 June 18, 2024 00:15
@masad-frost masad-frost merged commit 62429d0 into protocolv2 Jun 18, 2024
3 checks passed
@masad-frost masad-frost deleted the fm-abort-server-side branch June 18, 2024 00:41
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 <j.zhao2k19@gmail.com>
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.

3 participants