|
@@ -1,35 +1,41 @@
|
|
//! Subscription manager
|
|
//! Subscription manager
|
|
-use crate::{client, Error};
|
|
|
|
|
|
+use super::AllClients;
|
|
|
|
+use crate::{client, Client, Error};
|
|
|
|
+use chrono::Utc;
|
|
use futures::future::join_all;
|
|
use futures::future::join_all;
|
|
-use nostr_rs_subscription_manager::{
|
|
|
|
- ActiveSubscription as ActiveSubscriptionInner, SubscriptionManager,
|
|
|
|
-};
|
|
|
|
use nostr_rs_types::{
|
|
use nostr_rs_types::{
|
|
- client::subscribe::{self, is_all_events},
|
|
|
|
- relayer,
|
|
|
|
|
|
+ client::subscribe::{self},
|
|
types::SubscriptionId,
|
|
types::SubscriptionId,
|
|
Response,
|
|
Response,
|
|
};
|
|
};
|
|
-use std::sync::{
|
|
|
|
- atomic::{AtomicUsize, Ordering},
|
|
|
|
- Arc,
|
|
|
|
|
|
+use std::{
|
|
|
|
+ collections::{BTreeMap, VecDeque},
|
|
|
|
+ sync::{
|
|
|
|
+ atomic::{AtomicUsize, Ordering},
|
|
|
|
+ Arc,
|
|
|
|
+ },
|
|
};
|
|
};
|
|
-use tokio::sync::{mpsc, Mutex};
|
|
|
|
|
|
+use tokio::sync::{mpsc, RwLock, RwLockWriteGuard};
|
|
use url::Url;
|
|
use url::Url;
|
|
|
|
|
|
-use super::AllClients;
|
|
|
|
-
|
|
|
|
#[derive(Debug, Copy, Default, Eq, PartialEq, Clone)]
|
|
#[derive(Debug, Copy, Default, Eq, PartialEq, Clone)]
|
|
/// Subscription status
|
|
/// Subscription status
|
|
pub enum Status {
|
|
pub enum Status {
|
|
/// Subscription is awaiting to be subscribed
|
|
/// Subscription is awaiting to be subscribed
|
|
#[default]
|
|
#[default]
|
|
Queued,
|
|
Queued,
|
|
- /// Subscribed is active
|
|
|
|
|
|
+ /// Subscribed is active and it is fetching previous records and no EOD has
|
|
|
|
+ /// been received
|
|
|
|
+ Fetching,
|
|
|
|
+ /// Subscription is listening, an EOD has been received. This state can be
|
|
|
|
+ /// Requeued is their spot is needed for other subscriptions
|
|
Subscribed,
|
|
Subscribed,
|
|
- /// Technically unsubscribed, and fetching future events from the relayer
|
|
|
|
- /// from the All-Events meta subscription
|
|
|
|
- Stale,
|
|
|
|
|
|
+ /// Waiting to be subscribed again
|
|
|
|
+ Requeued,
|
|
|
|
+ /// Resubscribed, like subscribed but the EOD is ignored and nore relayed to
|
|
|
|
+ /// the listeners, since this is not the first the this subscription has
|
|
|
|
+ /// been created and it will be rotated soon
|
|
|
|
+ Resubscribed,
|
|
}
|
|
}
|
|
|
|
|
|
#[derive(Debug, Default)]
|
|
#[derive(Debug, Default)]
|
|
@@ -37,7 +43,7 @@ struct SubscriptionInner {
|
|
/// Active subscription (in the client side), when this is Drop all clients unsubscribes
|
|
/// Active subscription (in the client side), when this is Drop all clients unsubscribes
|
|
active_subscription: Option<Vec<client::ActiveSubscription>>,
|
|
active_subscription: Option<Vec<client::ActiveSubscription>>,
|
|
/// Subscription status
|
|
/// Subscription status
|
|
- status: Arc<Mutex<Status>>,
|
|
|
|
|
|
+ status: Status,
|
|
/// raw request
|
|
/// raw request
|
|
subscription_request: subscribe::Subscribe,
|
|
subscription_request: subscribe::Subscribe,
|
|
}
|
|
}
|
|
@@ -49,27 +55,14 @@ struct SubscriptionInner {
|
|
///
|
|
///
|
|
/// This must be dropped to unsubscribe from the subscription manager
|
|
/// This must be dropped to unsubscribe from the subscription manager
|
|
pub struct ActiveSubscription {
|
|
pub struct ActiveSubscription {
|
|
- _id: ActiveSubscriptionInner<PoolSubscriptionId, SubscriptionInner>,
|
|
|
|
- status: Arc<Mutex<Status>>,
|
|
|
|
- active_subscriptions: Arc<AtomicUsize>,
|
|
|
|
- queued_subscriptions: Arc<AtomicUsize>,
|
|
|
|
- stale_subscriptions: Arc<AtomicUsize>,
|
|
|
|
|
|
+ unsubscriber: Option<(PoolSubscriptionId, Arc<Scheduler>)>,
|
|
}
|
|
}
|
|
|
|
|
|
impl Drop for ActiveSubscription {
|
|
impl Drop for ActiveSubscription {
|
|
fn drop(&mut self) {
|
|
fn drop(&mut self) {
|
|
- let active_subscriptions = self.active_subscriptions.clone();
|
|
|
|
- let queued_subscriptions = self.queued_subscriptions.clone();
|
|
|
|
- let stale_subscriptions = self.stale_subscriptions.clone();
|
|
|
|
- let status = self.status.clone();
|
|
|
|
-
|
|
|
|
- tokio::spawn(async move {
|
|
|
|
- match *status.lock().await {
|
|
|
|
- Status::Subscribed => active_subscriptions.fetch_sub(1, Ordering::Relaxed),
|
|
|
|
- Status::Queued => queued_subscriptions.fetch_sub(1, Ordering::Relaxed),
|
|
|
|
- Status::Stale => stale_subscriptions.fetch_sub(1, Ordering::Relaxed),
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
|
|
+ if let Some((id, scheduler)) = self.unsubscriber.take() {
|
|
|
|
+ scheduler.remove(id);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -85,33 +78,58 @@ impl Default for PoolSubscriptionId {
|
|
|
|
|
|
/// Subscription manager
|
|
/// Subscription manager
|
|
///
|
|
///
|
|
-/// Clients who are added to the pool are automatically subscribed to all
|
|
|
|
-/// events, which is known as the All-Events subscription.
|
|
|
|
-///
|
|
|
|
-/// Subscriptions in the client pool are smarter than the raw subscriptions at
|
|
|
|
-/// the client level. These subscriptions will be active until an "End of
|
|
|
|
-/// stored" event is received; the client pool will unsubscribe at that point,
|
|
|
|
-/// and the All-Events subscriptions, filtered internally, will fetch future
|
|
|
|
-/// events. By doing so, only past events are queried, and there is a single
|
|
|
|
-/// stream of future events to be a good citizen with other relayers.
|
|
|
|
|
|
+/// Pools can have an unlimited number of active subscriptions, but this
|
|
|
|
+/// subscriptions will be scheduled internally.
|
|
#[derive(Default)]
|
|
#[derive(Default)]
|
|
-pub(crate) struct Manager {
|
|
|
|
- subscription_manager: Arc<SubscriptionManager<PoolSubscriptionId, SubscriptionInner>>,
|
|
|
|
|
|
+pub(crate) struct Scheduler {
|
|
|
|
+ subscriptions: RwLock<BTreeMap<PoolSubscriptionId, SubscriptionInner>>,
|
|
all_clients: AllClients,
|
|
all_clients: AllClients,
|
|
|
|
+ /// VecDec to have the list of subscritions
|
|
|
|
+ ///
|
|
|
|
+ /// This structure will pop the first elements and move them to be back
|
|
|
|
+ /// waiting their turn to be rescheduled.
|
|
|
|
+ subscription_queue: RwLock<VecDeque<PoolSubscriptionId>>,
|
|
active_subscriptions: Arc<AtomicUsize>,
|
|
active_subscriptions: Arc<AtomicUsize>,
|
|
- queued_subscriptions: Arc<AtomicUsize>,
|
|
|
|
- stale_subscriptions: Arc<AtomicUsize>,
|
|
|
|
|
|
+ total_subscriptions: Arc<AtomicUsize>,
|
|
}
|
|
}
|
|
|
|
|
|
/// Maximum number of subscriptions
|
|
/// Maximum number of subscriptions
|
|
pub const MAX_SUBSCRIPTIONS: usize = 50;
|
|
pub const MAX_SUBSCRIPTIONS: usize = 50;
|
|
|
|
|
|
-impl Manager {
|
|
|
|
|
|
+#[allow(warnings)]
|
|
|
|
+impl Scheduler {
|
|
|
|
+ /// Creates a new instance
|
|
|
|
+ pub fn new(all_clients: AllClients) -> Self {
|
|
|
|
+ Self {
|
|
|
|
+ all_clients,
|
|
|
|
+ ..Default::default()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// Register a new Client
|
|
|
|
+ pub async fn register_client(&self, client: &Client) {
|
|
|
|
+ let mut subscriptions = self.subscriptions.write().await;
|
|
|
|
+ for subscription in subscriptions.values_mut() {
|
|
|
|
+ if matches!(
|
|
|
|
+ subscription.status,
|
|
|
|
+ Status::Fetching | Status::Resubscribed | Status::Subscribed,
|
|
|
|
+ ) {
|
|
|
|
+ if let Ok(active_subscription) = client
|
|
|
|
+ .subscribe(subscription.subscription_request.clone())
|
|
|
|
+ .await
|
|
|
|
+ {
|
|
|
|
+ if let Some(active_subscriptions) = subscription.active_subscription.as_mut() {
|
|
|
|
+ active_subscriptions.push(active_subscription);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
/// Processes all messages from the client pools
|
|
/// Processes all messages from the client pools
|
|
///
|
|
///
|
|
- /// The client pool creates a subscription to the All-Events subscription,
|
|
|
|
- /// this callback checks if there are any listener to this event, otherwise
|
|
|
|
- /// it will not process the event.
|
|
|
|
|
|
+ /// Mainly to schedule subscriptions and pass the message over to the
|
|
|
|
+ /// listeners
|
|
pub async fn process_message(
|
|
pub async fn process_message(
|
|
self: &Arc<Self>,
|
|
self: &Arc<Self>,
|
|
message: Response,
|
|
message: Response,
|
|
@@ -121,53 +139,22 @@ impl Manager {
|
|
match message {
|
|
match message {
|
|
Response::EndOfStoredEvents(subscription_id) => {
|
|
Response::EndOfStoredEvents(subscription_id) => {
|
|
let subscription_id = PoolSubscriptionId((subscription_id.0, None));
|
|
let subscription_id = PoolSubscriptionId((subscription_id.0, None));
|
|
- let mut subscription = self.subscription_manager.subbscriptions_mut().await;
|
|
|
|
|
|
+ let mut subscription = self.subscriptions.write().await;
|
|
if let Some(s) = subscription.get_mut(&subscription_id) {
|
|
if let Some(s) = subscription.get_mut(&subscription_id) {
|
|
- *s.status.lock().await = Status::Stale;
|
|
|
|
- let _ = s.active_subscription.take();
|
|
|
|
|
|
+ let old_status = s.status;
|
|
|
|
+ s.status = Status::Subscribed;
|
|
|
|
|
|
- self.active_subscriptions.fetch_sub(1, Ordering::Relaxed);
|
|
|
|
- self.stale_subscriptions.fetch_add(1, Ordering::Relaxed);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return_to
|
|
|
|
- .try_send((
|
|
|
|
- Response::EndOfStoredEvents(subscription_id.0 .0.into()),
|
|
|
|
- url,
|
|
|
|
- ))
|
|
|
|
- .map_err(|e| Error::InternalChannel(e.to_string()))?;
|
|
|
|
-
|
|
|
|
- Ok(())
|
|
|
|
- }
|
|
|
|
- Response::Event(relayer::Event {
|
|
|
|
- subscription_id,
|
|
|
|
- event,
|
|
|
|
- }) => {
|
|
|
|
- if !is_all_events(&subscription_id) {
|
|
|
|
- // This is not an All-Events subscription, it must be passed on as it is an stored event
|
|
|
|
- return_to
|
|
|
|
- .try_send((
|
|
|
|
- Response::Event(relayer::Event {
|
|
|
|
- subscription_id,
|
|
|
|
- event,
|
|
|
|
- }),
|
|
|
|
- url.clone(),
|
|
|
|
- ))
|
|
|
|
- .map_err(|e| Error::InternalChannel(e.to_string()))?;
|
|
|
|
- return Ok(());
|
|
|
|
|
|
+ if old_status == Status::Fetching {
|
|
|
|
+ return_to
|
|
|
|
+ .try_send((
|
|
|
|
+ Response::EndOfStoredEvents(subscription_id.0 .0.into()),
|
|
|
|
+ url,
|
|
|
|
+ ))
|
|
|
|
+ .map_err(|e| Error::InternalChannel(e.to_string()))?;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- for id in self.subscription_manager.get_subscribers(&event).await {
|
|
|
|
- return_to
|
|
|
|
- .try_send((
|
|
|
|
- Response::Event(relayer::Event {
|
|
|
|
- subscription_id: id.0 .0.clone(),
|
|
|
|
- event: event.clone(),
|
|
|
|
- }),
|
|
|
|
- url.clone(),
|
|
|
|
- ))
|
|
|
|
- .map_err(|e| Error::InternalChannel(e.to_string()))?;
|
|
|
|
- }
|
|
|
|
|
|
+ self.active_subscription_scheduler();
|
|
|
|
|
|
Ok(())
|
|
Ok(())
|
|
}
|
|
}
|
|
@@ -180,43 +167,102 @@ impl Manager {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- async fn update_active_subscriptions(self: &Arc<Self>) {
|
|
|
|
- if self.active_subscriptions.load(Ordering::Relaxed) >= MAX_SUBSCRIPTIONS
|
|
|
|
- || self.queued_subscriptions.load(Ordering::Relaxed) == 0
|
|
|
|
- {
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
|
|
+ fn active_subscription_scheduler(self: &Arc<Self>) {
|
|
|
|
+ let this = self.clone();
|
|
|
|
+ tokio::spawn(async move {
|
|
|
|
+ let clients = this.all_clients.read().await;
|
|
|
|
+ let mut subscriptions = this.subscriptions.write().await;
|
|
|
|
+ let mut subscription_queue = this.subscription_queue.write().await;
|
|
|
|
+ let items = subscription_queue.len();
|
|
|
|
|
|
- let clients = self.all_clients.read().await;
|
|
|
|
- let mut subscriptions = self.subscription_manager.subbscriptions_mut().await;
|
|
|
|
|
|
+ // A subscription must be descheduled as its place is needed.
|
|
|
|
+ let mut deschedule =
|
|
|
|
+ |subscriptions: &mut RwLockWriteGuard<
|
|
|
|
+ '_,
|
|
|
|
+ BTreeMap<PoolSubscriptionId, SubscriptionInner>,
|
|
|
|
+ >,
|
|
|
|
+ subscription_queue: &mut RwLockWriteGuard<'_, VecDeque<PoolSubscriptionId>>|
|
|
|
|
+ -> bool {
|
|
|
|
+ for subscription_id in subscription_queue.iter() {
|
|
|
|
+ let mut subscription =
|
|
|
|
+ if let Some(subscription) = subscriptions.get_mut(subscription_id) {
|
|
|
|
+ subscription
|
|
|
|
+ } else {
|
|
|
|
+ continue;
|
|
|
|
+ };
|
|
|
|
|
|
- for subscription in subscriptions.values_mut() {
|
|
|
|
- if *subscription.status.lock().await == Status::Queued {
|
|
|
|
- let wait_all = clients
|
|
|
|
- .values()
|
|
|
|
- .map(|(_, (_, sender))| {
|
|
|
|
- sender.subscribe(subscription.subscription_request.clone())
|
|
|
|
- })
|
|
|
|
- .collect::<Vec<_>>();
|
|
|
|
-
|
|
|
|
- if let Ok(active_subscriptions) = join_all(wait_all)
|
|
|
|
- .await
|
|
|
|
- .into_iter()
|
|
|
|
- .collect::<Result<Vec<_>, _>>()
|
|
|
|
|
|
+ if matches!(subscription.status, Status::Subscribed) {
|
|
|
|
+ // unsubscribe
|
|
|
|
+ let _ = subscription.active_subscription.take();
|
|
|
|
+ // update counter
|
|
|
|
+ this.active_subscriptions.fetch_sub(1, Ordering::Relaxed);
|
|
|
|
+ // update since for next request
|
|
|
|
+ let now = Utc::now();
|
|
|
|
+ for filter in subscription.subscription_request.filters.iter_mut() {
|
|
|
|
+ filter.since = Some(now);
|
|
|
|
+ }
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ false
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ for _ in (0..items) {
|
|
|
|
+ let subscription_id = if let Some(subscription_id) = subscription_queue.pop_front()
|
|
{
|
|
{
|
|
- subscription.active_subscription = Some(active_subscriptions);
|
|
|
|
- *subscription.status.lock().await = Status::Subscribed;
|
|
|
|
-
|
|
|
|
- let queued_subscribed =
|
|
|
|
- self.queued_subscriptions.fetch_sub(1, Ordering::Relaxed);
|
|
|
|
- let active_subscriptions =
|
|
|
|
- self.active_subscriptions.fetch_add(1, Ordering::Relaxed);
|
|
|
|
- if queued_subscribed == 0 || active_subscriptions >= MAX_SUBSCRIPTIONS {
|
|
|
|
|
|
+ subscription_id
|
|
|
|
+ } else {
|
|
|
|
+ continue;
|
|
|
|
+ };
|
|
|
|
+ let mut subscription =
|
|
|
|
+ if let Some(subscription) = subscriptions.remove(&subscription_id) {
|
|
|
|
+ subscription
|
|
|
|
+ } else {
|
|
|
|
+ continue;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // add subscription id back to the last element, to be visited later
|
|
|
|
+ subscription_queue.push_back(subscription_id.clone());
|
|
|
|
+
|
|
|
|
+ let prev_status = subscription.status;
|
|
|
|
+
|
|
|
|
+ if matches!(prev_status, Status::Queued | Status::Requeued) {
|
|
|
|
+ if this.active_subscriptions.load(Ordering::SeqCst) >= MAX_SUBSCRIPTIONS
|
|
|
|
+ && !deschedule(&mut subscriptions, &mut subscription_queue)
|
|
|
|
+ {
|
|
|
|
+ subscriptions.insert(subscription_id, subscription);
|
|
|
|
+ // There is no more room to promote this subscription to active
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ let wait_all = clients
|
|
|
|
+ .values()
|
|
|
|
+ .map(|(_, sender)| {
|
|
|
|
+ sender.subscribe(subscription.subscription_request.clone())
|
|
|
|
+ })
|
|
|
|
+ .collect::<Vec<_>>();
|
|
|
|
+
|
|
|
|
+ if let Ok(active_subscriptions) = join_all(wait_all)
|
|
|
|
+ .await
|
|
|
|
+ .into_iter()
|
|
|
|
+ .collect::<Result<Vec<_>, _>>()
|
|
|
|
+ {
|
|
|
|
+ subscription.active_subscription = Some(active_subscriptions);
|
|
|
|
+ subscription.status = if prev_status == Status::Queued {
|
|
|
|
+ Status::Fetching
|
|
|
|
+ } else {
|
|
|
|
+ Status::Resubscribed
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ this.active_subscriptions.fetch_add(1, Ordering::Relaxed);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ // add it back
|
|
|
|
+ subscriptions.insert(subscription_id, subscription);
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
+ });
|
|
}
|
|
}
|
|
|
|
|
|
/// Creates a new subscription with a given filters
|
|
/// Creates a new subscription with a given filters
|
|
@@ -225,43 +271,47 @@ impl Manager {
|
|
subscription_request: subscribe::Subscribe,
|
|
subscription_request: subscribe::Subscribe,
|
|
specific_url: Option<Url>,
|
|
specific_url: Option<Url>,
|
|
) -> ActiveSubscription {
|
|
) -> ActiveSubscription {
|
|
- let status = Arc::new(Mutex::new(Status::Queued));
|
|
|
|
- let id = self
|
|
|
|
- .subscription_manager
|
|
|
|
- .subscribe(
|
|
|
|
- PoolSubscriptionId((
|
|
|
|
- subscription_request.subscription_id.clone(),
|
|
|
|
- specific_url.clone(),
|
|
|
|
- )),
|
|
|
|
- subscription_request.filters.clone(),
|
|
|
|
- SubscriptionInner {
|
|
|
|
- status: status.clone(),
|
|
|
|
- active_subscription: None,
|
|
|
|
- subscription_request,
|
|
|
|
- },
|
|
|
|
- )
|
|
|
|
- .await;
|
|
|
|
-
|
|
|
|
- self.queued_subscriptions.fetch_add(1, Ordering::Relaxed);
|
|
|
|
|
|
+ let subscription_id = PoolSubscriptionId((
|
|
|
|
+ subscription_request.subscription_id.clone(),
|
|
|
|
+ specific_url.clone(),
|
|
|
|
+ ));
|
|
|
|
|
|
- let this = self.clone();
|
|
|
|
- tokio::spawn(async move {
|
|
|
|
- this.update_active_subscriptions().await;
|
|
|
|
- });
|
|
|
|
|
|
+ self.subscriptions.write().await.insert(
|
|
|
|
+ subscription_id.clone(),
|
|
|
|
+ SubscriptionInner {
|
|
|
|
+ status: Status::Queued,
|
|
|
|
+ active_subscription: None,
|
|
|
|
+ subscription_request,
|
|
|
|
+ },
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ // add subscription to the queue, to be scheduled eventually
|
|
|
|
+ self.subscription_queue
|
|
|
|
+ .write()
|
|
|
|
+ .await
|
|
|
|
+ .push_back(subscription_id.clone());
|
|
|
|
+
|
|
|
|
+ self.total_subscriptions.fetch_add(1, Ordering::Relaxed);
|
|
|
|
+ self.active_subscription_scheduler();
|
|
|
|
|
|
ActiveSubscription {
|
|
ActiveSubscription {
|
|
- _id: id,
|
|
|
|
- status,
|
|
|
|
- active_subscriptions: self.active_subscriptions.clone(),
|
|
|
|
- queued_subscriptions: self.queued_subscriptions.clone(),
|
|
|
|
- stale_subscriptions: self.stale_subscriptions.clone(),
|
|
|
|
|
|
+ unsubscriber: Some((subscription_id, self.clone())),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ fn remove(self: Arc<Self>, subscription_id: PoolSubscriptionId) {
|
|
|
|
+ let this = self;
|
|
|
|
+ tokio::spawn(async move {
|
|
|
|
+ let mut subscriptions = this.subscriptions.write().await;
|
|
|
|
+ if let Some(id) = subscriptions.remove(&subscription_id) {
|
|
|
|
+ this.active_subscription_scheduler();
|
|
|
|
+ this.total_subscriptions.fetch_sub(1, Ordering::Relaxed);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
/// Total number of subscribers
|
|
/// Total number of subscribers
|
|
pub fn total_subscribers(&self) -> usize {
|
|
pub fn total_subscribers(&self) -> usize {
|
|
- self.active_subscriptions.load(Ordering::Relaxed)
|
|
|
|
- + self.queued_subscriptions.load(Ordering::Relaxed)
|
|
|
|
- + self.stale_subscriptions.load(Ordering::Relaxed)
|
|
|
|
|
|
+ self.total_subscriptions.load(Ordering::Relaxed)
|
|
}
|
|
}
|
|
}
|
|
}
|