Skip to main content

futu_mcp/state/push_subscribers/
modern_delivery.rs

1use super::*;
2
3impl ModernResourceDelivery {
4    pub(super) fn new(handle: String, permit: Arc<OwnedSemaphorePermit>) -> Self {
5        Self {
6            handle,
7            inner: StdMutex::new(ModernResourceInner {
8                queue: VecDeque::with_capacity(MODERN_PUSH_QUEUE_CAPACITY),
9                dropped_events: 0,
10                enqueued_generation: 0,
11                next_listener_token: 0,
12                active_listener: None,
13                pending_notification: None,
14                #[cfg(test)]
15                notification_send_pause: None,
16                #[cfg(test)]
17                notification_send_attempts: 0,
18                #[cfg(test)]
19                notification_send_failures: 0,
20                closed: false,
21            }),
22            _permit: permit,
23        }
24    }
25
26    /// Queue one already-authorized event. At most one notification future may
27    /// be pending per handle; additional pushes remain visible through read and
28    /// are coalesced behind that notification.
29    pub fn enqueue(&self, event: serde_json::Value) -> Option<PendingModernNotification> {
30        let mut inner = self.lock_inner();
31        if inner.closed {
32            return None;
33        }
34        if inner.queue.len() == MODERN_PUSH_QUEUE_CAPACITY {
35            inner.queue.pop_front();
36            inner.dropped_events = inner.dropped_events.saturating_add(1);
37        }
38        inner.queue.push_back(event);
39        inner.enqueued_generation = inner.enqueued_generation.wrapping_add(1);
40        if inner.pending_notification.is_some() {
41            return None;
42        }
43        let listener = inner.active_listener.as_ref()?;
44        let work = PendingModernNotification {
45            listener_token: listener.token,
46            sink: listener.sink.clone(),
47            cancel: listener.cancel.clone(),
48            uri: push_resource_uri(&self.handle),
49            generation: inner.enqueued_generation,
50        };
51        inner.pending_notification = Some(listener.token);
52        Some(work)
53    }
54
55    pub async fn send_pending_notification(&self, mut work: PendingModernNotification) {
56        loop {
57            #[cfg(test)]
58            {
59                let mut inner = self.lock_inner();
60                inner.notification_send_attempts =
61                    inner.notification_send_attempts.saturating_add(1);
62            }
63            let result = tokio::select! {
64                biased;
65                () = work.cancel.cancelled() => Ok(()),
66                result = work.sink.notify_resource_updated(work.uri.clone()) => result,
67            };
68            #[cfg(test)]
69            if result.is_err() {
70                let mut inner = self.lock_inner();
71                inner.notification_send_failures =
72                    inner.notification_send_failures.saturating_add(1);
73            }
74            #[cfg(test)]
75            let notification_send_pause = self.lock_inner().notification_send_pause.take();
76            #[cfg(test)]
77            if let Some((sent, resume)) = notification_send_pause {
78                sent.wait().await;
79                resume.wait().await;
80            }
81            let (listener_to_cancel, next_work) = {
82                let mut inner = self.lock_inner();
83                if inner.pending_notification != Some(work.listener_token) {
84                    return;
85                }
86                inner.pending_notification = None;
87                let active_listener_matches = inner
88                    .active_listener
89                    .as_ref()
90                    .is_some_and(|listener| listener.token == work.listener_token);
91                if result.is_err() && active_listener_matches {
92                    (inner.active_listener.take(), None)
93                } else if result.is_ok()
94                    && active_listener_matches
95                    && !inner.queue.is_empty()
96                    && inner.enqueued_generation != work.generation
97                {
98                    let Some(listener) = inner.active_listener.as_ref() else {
99                        return;
100                    };
101                    let next = PendingModernNotification {
102                        listener_token: listener.token,
103                        sink: listener.sink.clone(),
104                        cancel: listener.cancel.clone(),
105                        uri: push_resource_uri(&self.handle),
106                        generation: inner.enqueued_generation,
107                    };
108                    inner.pending_notification = Some(listener.token);
109                    (None, Some(next))
110                } else {
111                    (None, None)
112                }
113            };
114            if let Some(listener) = listener_to_cancel {
115                listener.cancel.cancel();
116            }
117            let Some(next_work) = next_work else {
118                return;
119            };
120            work = next_work;
121        }
122    }
123
124    pub(super) fn attach_listener(
125        &self,
126        sink: SubscriptionSink,
127    ) -> Result<
128        (
129            u64,
130            tokio_util::sync::CancellationToken,
131            Option<PendingModernNotification>,
132        ),
133        String,
134    > {
135        let cancel = tokio_util::sync::CancellationToken::new();
136        let listener_sink = sink.clone();
137        let (listener_token, previous, pending) = {
138            let mut inner = self.lock_inner();
139            if inner.closed {
140                return Err("push resource not found for current caller".to_string());
141            }
142            inner.next_listener_token = inner.next_listener_token.wrapping_add(1);
143            let listener_token = inner.next_listener_token;
144            inner.pending_notification = None;
145            let previous = inner
146                .active_listener
147                .replace(ActiveModernListener {
148                    token: listener_token,
149                    sink,
150                    cancel: cancel.clone(),
151                })
152                .map(|listener| listener.cancel);
153            let pending = if inner.queue.is_empty() {
154                None
155            } else {
156                inner.pending_notification = Some(listener_token);
157                Some(PendingModernNotification {
158                    listener_token,
159                    sink: listener_sink,
160                    cancel: cancel.clone(),
161                    uri: push_resource_uri(&self.handle),
162                    generation: inner.enqueued_generation,
163                })
164            };
165            (listener_token, previous, pending)
166        };
167        if let Some(previous) = previous {
168            previous.cancel();
169        }
170        Ok((listener_token, cancel, pending))
171    }
172
173    pub(super) fn detach_listener_if(&self, listener_token: u64) {
174        let removed = {
175            let mut inner = self.lock_inner();
176            if inner
177                .active_listener
178                .as_ref()
179                .is_some_and(|listener| listener.token == listener_token)
180            {
181                inner.pending_notification = None;
182                inner.active_listener.take()
183            } else {
184                None
185            }
186        };
187        if let Some(listener) = removed {
188            listener.cancel.cancel();
189        }
190    }
191
192    pub(super) fn close(&self) {
193        let listener = {
194            let mut inner = self.lock_inner();
195            inner.closed = true;
196            inner.queue.clear();
197            inner.dropped_events = 0;
198            inner.pending_notification = None;
199            inner.active_listener.take()
200        };
201        if let Some(listener) = listener {
202            listener.cancel.cancel();
203        }
204    }
205
206    pub(super) fn drain(&self) -> Result<(Vec<serde_json::Value>, u64), String> {
207        let mut inner = self.lock_inner();
208        if inner.closed {
209            return Err("push resource not found for current caller".to_string());
210        }
211        let events = inner.queue.drain(..).collect();
212        let dropped = std::mem::take(&mut inner.dropped_events);
213        Ok((events, dropped))
214    }
215
216    pub(super) fn lock_inner(&self) -> std::sync::MutexGuard<'_, ModernResourceInner> {
217        match self.inner.lock() {
218            Ok(inner) => inner,
219            Err(poisoned) => {
220                tracing::error!(
221                    handle = %self.handle,
222                    "recovering poisoned modern push queue lock"
223                );
224                self.inner.clear_poison();
225                poisoned.into_inner()
226            }
227        }
228    }
229}