1mod websocket;
3
4use std::collections::{HashMap, HashSet};
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::Arc;
7
8use tokio::runtime::Handle;
9use tokio::task::AbortHandle;
10use tokio::time::{interval, MissedTickBehavior};
11
12use crate::bus::BusSender;
13use crate::store::StoreWork;
14use crate::sub::Sub;
15
16pub(crate) struct SubscriptionHandles {
17 pub handles: parking_lot::Mutex<HashMap<u64, AbortHandle>>,
18}
19
20impl SubscriptionHandles {
21 pub fn new() -> Self {
22 Self {
23 handles: parking_lot::Mutex::new(HashMap::new()),
24 }
25 }
26
27 pub fn abort_all(&self) {
28 self.handles
29 .lock()
30 .drain()
31 .for_each(|(_, handle)| handle.abort());
32 }
33}
34
35pub(crate) fn sync_subscriptions<S, M, B>(
37 sub: &Sub<M>,
38 registry: &SubscriptionHandles,
39 handle: Handle,
40 tx: BusSender<M>,
41 backend: B,
42 shutdown: Arc<AtomicBool>,
43) where
44 S: Send + 'static,
45 M: Send + 'static,
46 B: StoreWork<S, M> + Clone + Send + Sync + 'static,
47{
48 let mut desired = HashSet::new();
49 collect_sub_ids(sub, &mut desired);
50
51 let mut active = registry.handles.lock();
52 active.retain(|id, abort| {
53 if desired.contains(id) {
54 true
55 } else {
56 abort.abort();
57 false
58 }
59 });
60
61 spawn_subscriptions(
62 sub,
63 &mut active,
64 handle,
65 tx,
66 backend,
67 shutdown,
68 );
69}
70
71fn spawn_subscriptions<S, M, B>(
72 sub: &Sub<M>,
73 active: &mut HashMap<u64, AbortHandle>,
74 handle: Handle,
75 tx: BusSender<M>,
76 backend: B,
77 shutdown: Arc<AtomicBool>,
78) where
79 S: Send + 'static,
80 M: Send + 'static,
81 B: StoreWork<S, M> + Clone + Send + Sync + 'static,
82{
83 match sub {
84 Sub::None => {}
85 Sub::Tick { id, every, produce } => {
86 spawn_if_new(*id, active, || {
87 spawn_tick(*every, *produce, handle, tx, backend, shutdown)
88 });
89 }
90 Sub::Stream { id, name, every, produce } => {
91 spawn_if_new(*id, active, || {
92 spawn_stream(name, *every, *produce, handle, tx, backend, shutdown)
93 });
94 }
95 Sub::WebSocket { id, url, every, produce } => {
96 spawn_if_new(*id, active, || {
97 websocket::spawn_websocket(url, *every, *produce, handle, tx, backend, shutdown)
98 });
99 }
100 Sub::MapMsg { inner, map } => spawn_unit_subscriptions(
101 inner.as_ref(),
102 *map,
103 active,
104 handle,
105 tx,
106 backend,
107 shutdown,
108 ),
109 Sub::Batch(items) => {
110 for item in items {
111 spawn_subscriptions(
112 item,
113 active,
114 handle.clone(),
115 tx.clone(),
116 backend.clone(),
117 shutdown.clone(),
118 );
119 }
120 }
121 }
122}
123
124fn spawn_unit_subscriptions<S, M, B>(
125 sub: &Sub<()>,
126 map: fn(()) -> M,
127 active: &mut HashMap<u64, AbortHandle>,
128 handle: Handle,
129 tx: BusSender<M>,
130 backend: B,
131 shutdown: Arc<AtomicBool>,
132) where
133 S: Send + 'static,
134 M: Send + 'static,
135 B: StoreWork<S, M> + Clone + Send + Sync + 'static,
136{
137 match sub {
138 Sub::None => {}
139 Sub::Tick { id, every, .. } => {
140 spawn_if_new(*id, active, || {
141 spawn_tick_mapped(*every, map, handle, tx, backend, shutdown)
142 });
143 }
144 Sub::Stream { id, name, every, .. } => {
145 spawn_if_new(*id, active, || {
146 spawn_stream_mapped(name, *every, map, handle, tx, backend, shutdown)
147 });
148 }
149 Sub::WebSocket { id, url, every, .. } => {
150 spawn_if_new(*id, active, || {
151 websocket::spawn_websocket_mapped(url, *every, map, handle, tx, backend, shutdown)
152 });
153 }
154 Sub::MapMsg { .. } => {}
155 Sub::Batch(items) => {
156 for item in items {
157 spawn_unit_subscriptions(
158 item,
159 map,
160 active,
161 handle.clone(),
162 tx.clone(),
163 backend.clone(),
164 shutdown.clone(),
165 );
166 }
167 }
168 }
169}
170
171fn spawn_if_new(id: u64, active: &mut HashMap<u64, AbortHandle>, spawn: impl FnOnce() -> AbortHandle) {
172 if active.contains_key(&id) {
173 return;
174 }
175 active.insert(id, spawn());
176}
177
178fn spawn_tick<S, M, B>(
179 every: std::time::Duration,
180 produce: fn() -> M,
181 handle: Handle,
182 tx: BusSender<M>,
183 backend: B,
184 shutdown: Arc<AtomicBool>,
185) -> AbortHandle
186where
187 S: Send + 'static,
188 M: Send + 'static,
189 B: StoreWork<S, M> + Clone + Send + Sync + 'static,
190{
191 handle
192 .spawn(async move {
193 let mut ticker = interval(every);
194 ticker.set_missed_tick_behavior(MissedTickBehavior::Skip);
195 ticker.tick().await;
196 loop {
197 if shutdown.load(Ordering::Relaxed) {
198 break;
199 }
200 ticker.tick().await;
201 if shutdown.load(Ordering::Relaxed) {
202 break;
203 }
204 crate::runtime::dispatch::dispatch_from_subscription(&backend, &tx, produce());
205 }
206 })
207 .abort_handle()
208}
209
210fn spawn_tick_mapped<S, M, B>(
211 every: std::time::Duration,
212 map: fn(()) -> M,
213 handle: Handle,
214 tx: BusSender<M>,
215 backend: B,
216 shutdown: Arc<AtomicBool>,
217) -> AbortHandle
218where
219 S: Send + 'static,
220 M: Send + 'static,
221 B: StoreWork<S, M> + Clone + Send + Sync + 'static,
222{
223 handle
224 .spawn(async move {
225 let mut ticker = interval(every);
226 ticker.set_missed_tick_behavior(MissedTickBehavior::Skip);
227 ticker.tick().await;
228 loop {
229 if shutdown.load(Ordering::Relaxed) {
230 break;
231 }
232 ticker.tick().await;
233 if shutdown.load(Ordering::Relaxed) {
234 break;
235 }
236 crate::runtime::dispatch::dispatch_from_subscription(&backend, &tx, map(()));
237 }
238 })
239 .abort_handle()
240}
241
242fn spawn_stream<S, M, B>(
243 _name: &'static str,
244 every: std::time::Duration,
245 produce: fn() -> M,
246 handle: Handle,
247 tx: BusSender<M>,
248 backend: B,
249 shutdown: Arc<AtomicBool>,
250) -> AbortHandle
251where
252 S: Send + 'static,
253 M: Send + 'static,
254 B: StoreWork<S, M> + Clone + Send + Sync + 'static,
255{
256 handle
257 .spawn(async move {
258 let mut ticker = interval(every);
259 ticker.set_missed_tick_behavior(MissedTickBehavior::Skip);
260 loop {
261 if shutdown.load(Ordering::Relaxed) {
262 break;
263 }
264 ticker.tick().await;
265 if shutdown.load(Ordering::Relaxed) {
266 break;
267 }
268 crate::runtime::dispatch::dispatch_from_subscription(&backend, &tx, produce());
269 }
270 })
271 .abort_handle()
272}
273
274fn spawn_stream_mapped<S, M, B>(
275 _name: &'static str,
276 every: std::time::Duration,
277 map: fn(()) -> M,
278 handle: Handle,
279 tx: BusSender<M>,
280 backend: B,
281 shutdown: Arc<AtomicBool>,
282) -> AbortHandle
283where
284 S: Send + 'static,
285 M: Send + 'static,
286 B: StoreWork<S, M> + Clone + Send + Sync + 'static,
287{
288 handle
289 .spawn(async move {
290 let mut ticker = interval(every);
291 ticker.set_missed_tick_behavior(MissedTickBehavior::Skip);
292 loop {
293 if shutdown.load(Ordering::Relaxed) {
294 break;
295 }
296 ticker.tick().await;
297 if shutdown.load(Ordering::Relaxed) {
298 break;
299 }
300 crate::runtime::dispatch::dispatch_from_subscription(&backend, &tx, map(()));
301 }
302 })
303 .abort_handle()
304}
305
306pub(crate) fn collect_sub_ids<M>(sub: &Sub<M>, out: &mut HashSet<u64>) {
307 match sub {
308 Sub::None => {}
309 Sub::Tick { id, .. }
310 | Sub::Stream { id, .. }
311 | Sub::WebSocket { id, .. } => {
312 out.insert(*id);
313 }
314 Sub::MapMsg { inner, .. } => collect_sub_ids(inner, out),
315 Sub::Batch(items) => {
316 for item in items {
317 collect_sub_ids(item, out);
318 }
319 }
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326 use std::time::Duration;
327
328 fn unit() {}
329
330 fn mapped(_: ()) -> i32 {
331 7
332 }
333
334 #[test]
335 fn collect_ids_from_batch() {
336 fn zero() -> i32 {
337 0
338 }
339 let sub = Sub::batch([
340 Sub::tick(1, Duration::from_secs(1), zero),
341 Sub::stream(2, "events", Duration::from_secs(1), zero),
342 ]);
343 let mut ids = HashSet::new();
344 collect_sub_ids(&sub, &mut ids);
345 assert_eq!(ids, HashSet::from([1, 2]));
346 }
347
348 #[test]
349 fn collect_ids_from_map_msg() {
350 let sub = Sub::MapMsg {
351 inner: Box::new(Sub::tick(9, Duration::from_millis(1), unit)),
352 map: mapped,
353 };
354 let mut ids = HashSet::new();
355 collect_sub_ids(&sub, &mut ids);
356 assert_eq!(ids, HashSet::from([9]));
357 }
358}