futu_mcp/state/push_subscribers/
modern_delivery.rs1use 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 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}