1use liminal::channel::{PublishRecord, Recipient};
4use std::collections::HashMap;
5use std::num::NonZeroU32;
6use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
7use std::sync::{Arc, Mutex, OnceLock};
8use std::time::Instant;
9
10use super::error::{TapError, lock};
11use super::message::{
12 Hop, PayloadGrant, PublishFact, Reached, TapBatch, TapFilter, TapIdent, TapMessage, TapSource,
13};
14use super::ring::BoundedRing;
15mod call;
16mod publish;
17mod reader;
18
19pub const DEFAULT_TAP_CAPACITY: usize = 4096;
21
22pub const MAX_SAMPLE_ONE_IN: NonZeroU32 = match NonZeroU32::new(1 << 16) {
24 Some(most) => most,
25 None => NonZeroU32::MIN,
26};
27
28const TWO: NonZeroU32 = NonZeroU32::MIN.saturating_add(1);
29
30pub type TapWaker = Arc<dyn Fn() + Send + Sync>;
33
34#[derive(Debug)]
37struct Admitted {
38 one_in: NonZeroU32,
39 message: TapMessage,
40}
41
42#[derive(Debug)]
43struct SubscriberState {
44 ring: BoundedRing<Admitted>,
45 one_in: NonZeroU32,
46 seen: u64,
47 next_seq: u64,
48}
49
50impl SubscriberState {
51 fn admits(&mut self) -> bool {
54 let take = self.seen % u64::from(self.one_in.get()) == 0;
55 self.seen = self.seen.wrapping_add(1);
56 take
57 }
58
59 fn after_push(&mut self) {
61 if self.ring.len().saturating_mul(2) > self.ring.capacity() {
62 self.one_in = self.one_in.saturating_mul(TWO).min(MAX_SAMPLE_ONE_IN);
63 }
64 }
65}
66
67struct Subscriber {
68 id: u64,
69 filter: TapFilter,
70 grant: Option<PayloadGrant>,
71 state: Mutex<SubscriberState>,
72}
73
74impl std::fmt::Debug for Subscriber {
75 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 formatter
77 .debug_struct("Subscriber")
78 .field("id", &self.id)
79 .field("filter", &self.filter)
80 .finish_non_exhaustive()
81 }
82}
83
84#[derive(Default)]
85struct TapShared {
86 active: AtomicUsize,
87 observed: AtomicU64,
88 #[cfg(test)]
91 observed_server: AtomicU64,
92 dropped_total: AtomicU64,
93 unsubscribe_failures: AtomicU64,
94 next_id: AtomicU64,
95 subscribers: Mutex<Vec<Arc<Subscriber>>>,
96 stations: Mutex<HashMap<u64, Arc<str>>>,
99 receivers: Mutex<HashMap<u64, u64>>,
102 buffers: Mutex<Vec<PublishRecord>>,
105 lent: AtomicU64,
107 no_stations: Arc<[Reached]>,
111 waker: OnceLock<TapWaker>,
112}
113
114impl std::fmt::Debug for TapShared {
115 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 formatter
117 .debug_struct("TapShared")
118 .field("active", &self.active)
119 .field("observed", &self.observed)
120 .field("dropped_total", &self.dropped_total)
121 .finish_non_exhaustive()
122 }
123}
124
125#[derive(Clone, Debug)]
127pub struct Tap {
128 shared: Arc<TapShared>,
129 node: Arc<str>,
130 depot: Option<Arc<str>>,
133 capacity: usize,
134}
135
136impl Tap {
137 #[must_use]
141 pub fn new(node: &str, capacity: usize) -> Self {
142 Self::build(node, capacity, OnceLock::new())
143 }
144
145 #[must_use]
149 pub fn with_waker(node: &str, capacity: usize, waker: TapWaker) -> Self {
150 Self::build(node, capacity, OnceLock::from(waker))
151 }
152
153 fn build(node: &str, capacity: usize, waker: OnceLock<TapWaker>) -> Self {
154 Self {
155 shared: Arc::new(TapShared {
156 waker,
157 ..TapShared::default()
158 }),
159 node: Arc::from(node),
160 depot: None,
161 capacity: capacity.max(1),
162 }
163 }
164
165 #[must_use]
169 pub fn with_depot(mut self, depot: bool) -> Self {
170 self.depot = depot.then(|| Arc::from(format!("{}.depot", self.node)));
171 self
172 }
173
174 #[must_use]
177 pub fn reserved(&self, id: &str) -> Option<&'static str> {
178 if id == &*self.node {
179 Some("exchange")
180 } else if self.depot.as_deref() == Some(id) {
181 Some("depot")
182 } else {
183 None
184 }
185 }
186
187 pub fn subscribe(&self, filter: TapFilter) -> Result<TapSubscription, TapError> {
192 self.subscribe_with(filter, None)
193 }
194
195 pub fn subscribe_with(
200 &self,
201 filter: TapFilter,
202 grant: Option<PayloadGrant>,
203 ) -> Result<TapSubscription, TapError> {
204 let id = self.shared.next_id.fetch_add(1, Ordering::Relaxed);
205 let subscriber = Arc::new(Subscriber {
206 id,
207 filter,
208 grant,
209 state: Mutex::new(SubscriberState {
210 ring: BoundedRing::new(self.capacity),
211 one_in: NonZeroU32::MIN,
212 seen: 0,
213 next_seq: 1,
214 }),
215 });
216 let mut subscribers = lock(&self.shared.subscribers, "tap subscribers")?;
217 subscribers.push(Arc::clone(&subscriber));
218 self.shared
219 .active
220 .store(subscribers.len(), Ordering::Release);
221 drop(subscribers);
222 Ok(TapSubscription {
223 shared: Arc::clone(&self.shared),
224 subscriber,
225 })
226 }
227
228 #[must_use]
232 pub fn observations(&self) -> u64 {
233 self.shared.observed.load(Ordering::Relaxed)
234 }
235
236 #[cfg(test)]
240 #[must_use]
241 pub(crate) fn server_observations(&self) -> u64 {
242 self.shared.observed_server.load(Ordering::Relaxed)
243 }
244
245 #[must_use]
247 pub fn subscriber_count(&self) -> usize {
248 self.shared.active.load(Ordering::Acquire)
249 }
250
251 #[must_use]
253 pub fn dropped_total(&self) -> u64 {
254 self.shared.dropped_total.load(Ordering::Relaxed)
255 }
256
257 #[cfg(test)]
260 pub(crate) fn poison_subscribers(&self) -> bool {
261 std::thread::scope(|scope| {
262 scope
263 .spawn(|| {
264 let _held = self.shared.subscribers.lock();
265 std::panic::resume_unwind(Box::new("poisoned for a test"));
266 })
267 .join()
268 .is_err()
269 }) && self.shared.subscribers.is_poisoned()
270 }
271
272 pub fn name_station(&self, connection: u64, station: &str) -> Result<(), TapError> {
278 lock(&self.shared.stations, "tap stations")?.insert(connection, Arc::from(station));
279 Ok(())
280 }
281
282 pub fn forget_station(&self, connection: u64) -> Result<(), TapError> {
287 lock(&self.shared.stations, "tap stations")?.remove(&connection);
288 Ok(())
289 }
290
291 pub fn name_receiver(&self, subscriber: u64, connection: u64) -> Result<(), TapError> {
297 lock(&self.shared.receivers, "tap receivers")?.insert(subscriber, connection);
298 Ok(())
299 }
300
301 pub fn forget_receiver(&self, subscriber: u64) -> Result<(), TapError> {
307 lock(&self.shared.receivers, "tap receivers")?.remove(&subscriber);
308 Ok(())
309 }
310
311 pub fn forget_receivers_of(&self, connection: u64) -> Result<(), TapError> {
316 lock(&self.shared.receivers, "tap receivers")?.retain(|_, owner| *owner != connection);
317 Ok(())
318 }
319
320 pub fn lend_recipients(&self) -> Result<Option<PublishRecord>, TapError> {
329 if self.shared.active.load(Ordering::Acquire) == 0 {
330 return Ok(None);
331 }
332 self.shared.lent.fetch_add(1, Ordering::Relaxed);
333 Ok(Some(
334 lock(&self.shared.buffers, "tap buffers")?
335 .pop()
336 .unwrap_or_default(),
337 ))
338 }
339
340 pub fn give_back(&self, mut buffer: PublishRecord) -> Result<(), TapError> {
346 buffer.clear();
347 lock(&self.shared.buffers, "tap buffers")?.push(buffer);
348 Ok(())
349 }
350
351 #[must_use]
354 pub fn recipients_lent(&self) -> u64 {
355 self.shared.lent.load(Ordering::Relaxed)
356 }
357
358 #[must_use]
361 pub fn unsubscribe_failures(&self) -> u64 {
362 self.shared.unsubscribe_failures.load(Ordering::Relaxed)
363 }
364}
365
366#[derive(Debug)]
368pub struct TapSubscription {
369 shared: Arc<TapShared>,
370 subscriber: Arc<Subscriber>,
371}
372
373fn elapsed_ms(elapsed: std::time::Duration) -> Option<u64> {
376 u64::try_from(elapsed.as_millis()).ok()
377}
378
379fn gather<'station>(
387 recipients: usize,
388 named: usize,
389 first_at: impl Fn(usize) -> Option<(&'station Arc<str>, Instant)>,
390) -> Result<Option<Arc<[Reached]>>, TapError> {
391 let Some(first) = (0..recipients).find_map(&first_at) else {
392 return if named == 0 {
393 Ok(None)
394 } else {
395 Err(TapError::StationsDisagree { named, found: 0 })
396 };
397 };
398 let mut next = 0;
403 let mut found = 0_usize;
404 let reached = |(station, queued_at): (&Arc<str>, Instant)| Reached {
405 station: Arc::clone(station),
406 queued_at,
407 };
408 let list: Arc<[Reached]> = (0..named)
409 .map(|_| {
410 match (next..recipients)
411 .find_map(|index| first_at(index).map(|station| (index, station)))
412 {
413 Some((index, station)) => {
414 next = index + 1;
415 found += 1;
416 reached(station)
417 }
418 None => reached(first),
419 }
420 })
421 .collect();
422 let beyond = (next..recipients)
423 .filter(|index| first_at(*index).is_some())
424 .count();
425 if found != named || beyond != 0 {
426 return Err(TapError::StationsDisagree {
427 named,
428 found: found + beyond,
429 });
430 }
431 Ok(Some(list))
432}
433
434#[cfg(test)]
435mod gather_tests {
436 use std::sync::Arc;
437 use std::time::{Duration, Instant};
438
439 use super::{Reached, TapError, elapsed_ms, gather};
440
441 #[test]
444 fn an_elapsed_time_beyond_u64_milliseconds_has_no_reading() {
445 assert_eq!(elapsed_ms(Duration::from_millis(7)), Some(7));
446 assert_eq!(elapsed_ms(Duration::MAX), None);
447 }
448
449 #[test]
455 fn a_fill_that_disagrees_with_the_count_is_refused() {
456 let ledger: Arc<str> = Arc::from("orders.ledger");
457 let queued = Instant::now();
458 let gathered = gather(2, 2, |index| (index == 0).then_some((&ledger, queued)));
459 assert!(
460 matches!(
461 gathered,
462 Err(TapError::StationsDisagree { named: 2, found: 1 })
463 ),
464 "{gathered:?}"
465 );
466 }
467
468 #[test]
471 fn a_fill_that_agrees_builds_the_list() -> Result<(), TapError> {
472 let ledger: Arc<str> = Arc::from("orders.ledger");
473 let audit: Arc<str> = Arc::from("orders.audit");
474 let queued = Instant::now();
475 let first_at = |index: usize| match index {
476 0 => Some((&ledger, queued)),
477 2 => Some((&audit, queued)),
478 _ => None,
479 };
480 let reached = |station: &Arc<str>| Reached {
481 station: Arc::clone(station),
482 queued_at: queued,
483 };
484 assert_eq!(
485 gather(3, 2, first_at)?,
486 Some(Arc::from(vec![reached(&ledger), reached(&audit)]))
487 );
488 assert_eq!(gather(3, 0, |_| None)?, None);
489 Ok(())
490 }
491}