-
Notifications
You must be signed in to change notification settings - Fork 274
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
Add a subscription manager #548
Add a subscription manager #548
Conversation
The idea is to use the `Subscriptions` struct from Substrate, which is used to drive subscription futures to completion, and modify it for "general" use.
This comment has been minimized.
This comment has been minimized.
pubsub/src/manager.rs
Outdated
|
||
/// Trait used to provide unique subscription ids. | ||
pub trait IdProvider { | ||
// TODO: Maybe have this impl Into<u64>? |
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 should be possible to have String
ids as well.
pubsub/src/manager.rs
Outdated
pub struct Manager<I: Default + IdProvider> { | ||
next_id: I, | ||
active_subscriptions: Arc<Mutex<HashMap<I::Id, oneshot::Sender<()>>>>, | ||
executor: TaskExecutor, // Make generic? |
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.
IMHO It's fine, in most workloads spawning subscription is not going to be a bottleneck so we can safely do a virtual call here.
pubsub/src/manager.rs
Outdated
} | ||
|
||
/// Trait used to drive subscription Futures to completion. | ||
pub trait SubscriptionManager { |
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'm not sure the trait is really needed, I'd rather have Manager
with a default generic parameter of u64
subscription IdProvider
. Mainly to avoid importing extra traits to call functions.
Do you have some use cases in mind where having it behind trait would be useful?
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.
Yeah, my reasoning kinda was that if people wanted a "default" implementation of a SubscriptionManager
they'd use the Manager
struct, and if they wanted to roll their own they just had to conform to the SubscriptionManager
trait. Is it reasonable to expect that people will want to use something other than the "default" implementation? If not, then yeah we don't need to the trait.
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 trait is not really used in the library at all I think a concrete implementation is enough. If someone is building their own solution and needs that abstraction they can introduce a trait themselves and implement it for the type from our library.
pubsub/src/types.rs
Outdated
pub enum SubscriptionId { | ||
/// U64 number | ||
Number(u64), | ||
pub enum SubscriptionId<N> { |
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.
Ah, I see what you meant with Into<u64>
in the earlier comment. Why not require Into<SubscriptionId>
instead? This together with some extra From
implementations that we would add here (for numeric types and String
as you did now) would allow us to keep that configurable without maiking SubscriptionId
generic.
Adds trait bounds that allow conversion between the two, removing the need for generics in SubscriptionId.
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.
Looks good, just needs some API tweaking and more docs.
pubsub/src/manager.rs
Outdated
@@ -0,0 +1,88 @@ | |||
//! Provides an executor for subscription Futures. |
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 think we should expand on this module description a bit. The subscription manager is opinionated way to handle processing subscriptions that are based on Stream
coming from the user codebase.
The manager takes care of:
- Assigning the ID
- Consuming a
Stream
of events (coming from user code) and transforming it into a subscription notifications to the client. - Spawning a resulting future to executor
- Cancelling the stream when the subscription is canceled.
pubsub/src/manager.rs
Outdated
executor: TaskExecutor, // Make generic? | ||
} | ||
|
||
impl<I: Default + IdProvider> SubscriptionManager<I> { |
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.
Default
should only be required for new
(not for add/executor/etc
) and there should be an extra method with_id_provider(TaskExecutor, IdProvider)
for non-default cases.
pubsub/src/manager.rs
Outdated
/// Takes care of assigning unique subscription ids and | ||
/// driving the sinks into completion. | ||
#[derive(Clone)] | ||
pub struct SubscriptionManager<I: Default + IdProvider> { |
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.
pub struct SubscriptionManager<I: Default + IdProvider> { | |
pub struct SubscriptionManager<I: IdProvider = RandomizedStringProvider> { |
We should implement two IdProviders
:
NumericIdProvider
RandomizedStringProvider
The first one would simply use AtomicU64
and would return increasing numeric values, the second one - which should also be a default to use if SubscriptionManager
does not include generic parameter - should return a randomized strings (guids).
The reason for using randomized strings is to make sure that subscription ids are not easy to guess, so that multiple clients can't cancel each-others subscriptions easily. The first one will be mostly usable for tests or cases where the aforementioned issue is not a problem.
} | ||
|
||
#[test] | ||
fn should_convert_from_string_to_subscription_id() { |
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.
Thanks for all the test ❤️
Adds two subscription ID providers which can be used by the SubscriptionManager. One provides a simple numeric ID, while the other provides a random string.
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.
Looks good! Few tiny things left.
let id: String = iter::repeat(()) | ||
.map(|()| rng.sample(Alphanumeric)) | ||
.take(self.len) | ||
.collect(); |
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.
Nice and elegant :)
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.
Unfortunately I can't take credit for it: https://rust-random.github.io/rand/rand/distributions/struct.Alphanumeric.html
pubsub/src/manager.rs
Outdated
active_subscriptions: Arc<Mutex<HashMap<I::Id, oneshot::Sender<()>>>>, | ||
pub struct SubscriptionManager<I: IdProvider = RandomStringIdProvider> { | ||
id_provider: I, | ||
active_subscriptions: ActiveSubscriptions, | ||
executor: TaskExecutor, // Make generic? |
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.
executor: TaskExecutor, // Make generic? | |
executor: TaskExecutor, |
pubsub/src/manager.rs
Outdated
/// Creates a new SubscriptionManager. | ||
pub fn new(executor: TaskExecutor) -> Self { | ||
pub fn new_with_id_provider(id_provider: I, executor: TaskExecutor) -> Self { |
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.
pub fn new_with_id_provider(id_provider: I, executor: TaskExecutor) -> Self { | |
pub fn with_id_provider(id_provider: I, executor: TaskExecutor) -> Self { |
AFAIK the overall convention is to skip new_
in with_
constructors.
pubsub/src/types.rs
Outdated
@@ -62,6 +62,14 @@ impl From<String> for SubscriptionId { | |||
} | |||
} | |||
|
|||
// TODO: Don't unwrap, and probably implement TryFrom instead of From | |||
use std::convert::TryInto; | |||
impl From<usize> for SubscriptionId { |
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 think instead of providing this From<usize>
implementation we should rather change the NumericIdProvider
to convert usize
to u64
internally. The reason is that it's highly unlikely for NumericIdProvider
to reach the case where this conversion might fail (given it runs on 128-bit processor anyway O.o) and here we are making stronger assumption that usize
can be safely converted to SubscriptionId
.
Instead of providing a guarantee that we can convert between `usize` and `u64` we make the assumptions that it's unlikely that we're running on an architecture larger than 64-bits and we use a `u64` directly.
I want to write some tests to make sure the |
Note: There's one test that doesn't pass yet which has to do with the `cancel()` function of the SubscriptionManager.
Looks like the Windows runner is failing due to an old |
Our Windows CI runner isn't up to date and thinks this is still a nightly feature
I was implementing some pubsub APIs in Substrate, outside of
sc-rpc
, and got to a point where I needed something to execute the subscription Futures. There's nothing provided out of the box for doing that, so after talking with @tomusdrw we figured that it might be useful to have the Substrate subscription manager be a part of thepubsub
crate.What I've done is I've stolen that code and changed it to try and be a little less opinionated. One concern I have though is that I've changed
SubscriptionId
to now take a generic parameter,N
, to avoid only working withu64
ids, and I think this will break people's code.I'm looking for feedback on whether the general approach is alright, and if the change to
SubscriptionId
is acceptable.