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

rpc: ADR for client event subscription mechanism #476

Merged
merged 19 commits into from
Aug 28, 2020
Merged

Conversation

thanethomson
Copy link
Contributor

@thanethomson thanethomson commented Jul 23, 2020

In partial fulfillment of #313 (implementation will close it).

I've gone through several mental iterations of what the various touchpoints for the event pub/sub interface could look like and I think I've pared it down to the simplest form I can come up with.

More scrutiny would be greatly appreciated! 😁

Rendered


  • Referenced an issue explaining the need for the change
  • Updated all relevant documentation in docs
  • Updated all code comments where relevant
  • Wrote tests
  • Updated CHANGES.md

Signed-off-by: Thane Thomson <[email protected]>
Copy link
Member

@liamsi liamsi left a comment

Choose a reason for hiding this comment

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

docs/architecture/adr-008-event-subscription.md Outdated Show resolved Hide resolved
Copy link
Member

@liamsi liamsi left a comment

Choose a reason for hiding this comment

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

Simple and ergonomic interface 👍 I was first wondering if fn subscribe shouldn't directly return the stream (and unsubscribe would then take a Query too) but it seems having a Subscription type is actually more readable. Looking forward to the implementation :-)

Thanks for taking this over 👍

@romac
Copy link
Member

romac commented Jul 27, 2020

I too very much like the interface 👍

I think it could be useful to sketch out the Subscription type to see how easily we can mock out the Client interface to construct a Subscription which emits a static list of events.

xla
xla previously requested changes Aug 4, 2020
Copy link
Contributor

@xla xla left a comment

Choose a reason for hiding this comment

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

Great write-up! The direction is sound, left some open questions from my side inline. I agree that we need to expand the design of the Subscription before we move forward.

docs/architecture/adr-008-event-subscription.md Outdated Show resolved Hide resolved
subscriptions' event streams without blocking other RPC operations.
2. Offer the ability to **unsubscribe** from specific queries (i.e. to
terminate specific subscriptions).
2. A pub/sub architecture (with an appropriate concurrency model) that
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a bit vague, could we expand on this?

Since the underlying events may not be produced at the same cadence as that of
the `Subscription` owner's processing, it is proposed here to make use of an
appropriate type of buffered channel to realize this. The size of this buffer
must be configurable through the RPC Client.
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs further detailing as to what the desired issue we like to address with buffered channels. Furthermore the behaviour needs specifying as to how the implementation should behave when it encounters a filled up/blocked channel.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The current proposal (and implementation) now makes exclusive use of unbounded channels. This doesn't do away with the need for discussion around handling blocked/full channels, but it buys us a bit of time.


### RPC Pub/Sub

The RPC Client must asynchronously spawn a component `PubSub` whose
Copy link
Contributor

Choose a reason for hiding this comment

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

It ought to be the responsibility of the user/caller to spawn this "run loop". Otherwise we take away control about lifetime. Instead of having the client silently spawn things in the background it should be made explicit through its interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've implemented a handle/driver architecture for most of the parts of the system that need to operate in two async contexts. The "handle" part allows for total control of the "driver" part.

Unfortunately, carelessly dropping the handle part right now with the current architecture still leaves the driver dangling (which will hopefully be more easily addressed when async destructors are available in Rust).

docs/architecture/adr-008-event-subscription.md Outdated Show resolved Hide resolved
docs/architecture/adr-008-event-subscription.md Outdated Show resolved Hide resolved
docs/architecture/adr-008-event-subscription.md Outdated Show resolved Hide resolved
docs/architecture/adr-008-event-subscription.md Outdated Show resolved Hide resolved
docs/architecture/adr-008-event-subscription.md Outdated Show resolved Hide resolved
docs/architecture/adr-008-event-subscription.md Outdated Show resolved Hide resolved
@xla xla changed the title rpc: Add ADR for RPC client event subscription mechanism rpc: ADR for client event subscription mechanism Aug 12, 2020
from multiple subscriptions' event streams concurrently without
interfering with/blocking each other.
5. It must be possible to, from outside of the RPC client, handle
subscriptions' event streams without blocking other RPC operations.
Copy link
Member

Choose a reason for hiding this comment

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

What about the ability to receive all subscriptions on a single stream? eg. if I want to subscribe to a bunch of distinct event types and receive them in order?

And would it make sense / simplify things for the base subscription system to be like that, ie. a single stream with all subscriptions, and have the multi streams (I guess the Router) be an optional layer on top?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My understanding of the initial reasoning around having Subscription implement the Stream trait is that it opens up possibilities around the use of more general futures functionality like select_all, which would allow your code to combine multiple subscription streams into a single one and iterate through them as one stream.

Copy link
Member

Choose a reason for hiding this comment

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

Interesting, and that would preserve order from the underlying socket? Does that make more sense than an approach where all events on the websocket itself are a single Stream and clients have option of partitioning that up into possibly multiple streams per subscription?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The docs for select_all claim that elements from the underlying streams are returned while preserving the order in which they are produced by the underlying streams. I can add some tests into #516 that verify this, because I haven't actually tested it yet.

In theory it should work like this. The WebSocket driver I've implemented in #516 pushes events out to subscriptions (into their internal tokio::mpsc buffers) immediately as they're received before trying to read another event from the WebSocket connection, so this approach should preserve ordering when using select_all.

allows the transport layer to operate independently of consumers of
subscriptions. This is so that consumers don't block transport layer
activities and vice-versa.

Copy link
Member

Choose a reason for hiding this comment

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

Would be helpful if we also had context on what currently exists in the code base and what the issues are.


1. Immediate subscribe/unsubscribe request fulfilment.
2. Two-stage subscribe/unsubscribe request management (where a
subscription/unsubscribe request can first be created in a "pending" state,
Copy link
Member

Choose a reason for hiding this comment

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

What's the motivation for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since the client multiplexes multiple subscriptions on a single WebSocket connection, there are no request/reply mechanics. You send messages and you continuously receive them in any order (i.e. subscription request responses could be mixed in with incoming events related to a different subscription). For example:

  1. The client sends a subscription request via the WebSocket connection. This creates a subscription in a Pending state, but it's not yet active (since the RPC can still respond with an error). We don't publish events to Pending subscriptions.
  2. The client may, while waiting for a response, receive events for a different subscription (made earlier than (1)).
  3. When a response comes back whose ID matches the request from (1), if it's successful, the subscription is marked as Active, and if it failed we cancel and remove it.

Similar dynamics happen when unsubscribing, so unsubscribing is also currently a two-stage process.

Copy link
Member

Choose a reason for hiding this comment

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

So when/how can we ever not use the two-stage subscription? ie when can we use the immediate one?

@thanethomson thanethomson marked this pull request as ready for review August 27, 2020 22:10
Copy link
Member

@ebuchman ebuchman left a comment

Choose a reason for hiding this comment

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

Let's merge this and follow up as necessary

@ebuchman ebuchman merged commit a6187fc into master Aug 28, 2020
@ebuchman ebuchman deleted the adr/subscription branch August 28, 2020 20:01
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.

5 participants