-
Notifications
You must be signed in to change notification settings - Fork 224
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
Conversation
Signed-off-by: Thane Thomson <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this 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 👍
Signed-off-by: Thane Thomson <[email protected]>
Signed-off-by: Thane Thomson <[email protected]>
I too very much like the interface 👍 I think it could be useful to sketch out the |
There was a problem hiding this 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.
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 |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
Co-authored-by: Alexander Simmerl <[email protected]>
Signed-off-by: Thane Thomson <[email protected]>
Signed-off-by: Thane Thomson <[email protected]>
Signed-off-by: Thane Thomson <[email protected]>
Signed-off-by: Thane Thomson <[email protected]>
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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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. | ||
|
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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:
- 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 toPending
subscriptions. - The client may, while waiting for a response, receive events for a different subscription (made earlier than (1)).
- 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.
There was a problem hiding this comment.
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?
Signed-off-by: Thane Thomson <[email protected]>
Signed-off-by: Thane Thomson <[email protected]>
Signed-off-by: Thane Thomson <[email protected]>
Signed-off-by: Thane Thomson <[email protected]>
…n detail) Signed-off-by: Thane Thomson <[email protected]>
Signed-off-by: Thane Thomson <[email protected]>
Signed-off-by: Thane Thomson <[email protected]>
Signed-off-by: Thane Thomson <[email protected]>
Signed-off-by: Thane Thomson <[email protected]>
Signed-off-by: Thane Thomson <[email protected]>
Signed-off-by: Thane Thomson <[email protected]>
There was a problem hiding this 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
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