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