1use std::{
2 future::Future,
3 pin::Pin,
4 sync::Arc,
5 task::{Context, Poll, Waker},
6};
7
8use futures::{FutureExt, SinkExt, StreamExt, TryStreamExt};
9use hopr_api::types::internal::routing::DestinationRouting;
10use hopr_protocol_app::prelude::{ApplicationData, ApplicationDataOut};
11use hopr_protocol_start::{KeepAliveFlag, KeepAliveMessage};
12#[cfg(feature = "runtime-tokio")]
18pub use hopr_utils::network_types::utils::{transfer_session, transfer_session_datagram};
19use hopr_utils::runtime::AbortHandle;
20use tracing::{Instrument, debug, error, instrument};
21
22use crate::{
23 AtomicSurbFlowEstimator, SessionId,
24 balancer::{BalancerStateValues, RateController, RateLimitStreamExt},
25 errors::TransportSessionError,
26 types::HoprStartProtocol,
27};
28
29#[derive(Clone)]
50pub struct SlotNotify {
51 inner: Arc<parking_lot::Mutex<SlotNotifyInner>>,
52}
53
54struct SlotNotifyInner {
55 wakers: Vec<(u64, Waker)>,
56 next_id: u64,
57 generation: u64,
58}
59
60impl SlotNotify {
61 pub fn new() -> Self {
62 Self {
63 inner: Arc::new(parking_lot::Mutex::new(SlotNotifyInner {
64 wakers: Vec::new(),
65 next_id: 0,
66 generation: 0,
67 })),
68 }
69 }
70
71 pub fn notify_waiters(&self) {
84 let woken = {
85 let mut inner = self.inner.lock();
86 inner.generation += 1;
87 inner.wakers.drain(..).collect::<Vec<_>>()
90 };
91 for (_, waker) in woken {
92 waker.wake();
93 }
94 }
95
96 pub fn notified(&self) -> SlotNotifyFuture {
103 let generation = self.inner.lock().generation;
104 SlotNotifyFuture {
105 inner: self.inner.clone(),
106 waker_id: 0,
107 registered: false,
108 gen_at_creation: generation,
109 }
110 }
111}
112
113pub struct SlotNotifyFuture {
119 inner: Arc<parking_lot::Mutex<SlotNotifyInner>>,
120 waker_id: u64,
121 registered: bool,
122 gen_at_creation: u64,
123}
124
125impl Drop for SlotNotifyFuture {
126 fn drop(&mut self) {
127 if self.registered {
128 self.inner.lock().wakers.retain(|(id, _)| *id != self.waker_id);
129 }
130 }
131}
132
133impl Future for SlotNotifyFuture {
134 type Output = ();
135
136 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
137 let this = self.get_mut();
138 let mut inner = this.inner.lock();
139
140 if inner.generation != this.gen_at_creation {
143 return Poll::Ready(());
144 }
145
146 if this.registered {
147 if let Some((_, w)) = inner.wakers.iter_mut().find(|(id, _)| *id == this.waker_id) {
151 if !w.will_wake(cx.waker()) {
158 *w = cx.waker().clone();
159 }
160 }
161 return Poll::Pending;
162 }
163
164 this.waker_id = inner.next_id;
166 inner.next_id += 1;
167 inner.wakers.push((this.waker_id, cx.waker().clone()));
168 this.registered = true;
169 Poll::Pending
170 }
171}
172
173pub(crate) fn insert_into_next_slot<F, K, U, V>(
179 cache: &moka::sync::Cache<K, V>,
180 mut generator: F,
181 value_fn: U,
182 max_capacity: Option<u64>,
183) -> Option<(K, V)>
184where
185 F: FnMut(Option<K>) -> K,
186 K: Copy + std::hash::Hash + Eq + Send + Sync + 'static,
187 U: FnOnce(K) -> V,
188 V: Clone + Send + Sync + 'static,
189{
190 cache.run_pending_tasks();
191
192 if let Some(max) = max_capacity
195 && cache.entry_count() >= max
196 {
197 return None;
198 }
199
200 let value_fn = std::sync::Arc::new(parking_lot::Mutex::new(Some(value_fn)));
203
204 let initial = generator(None);
205 let mut next = initial;
206 loop {
207 let value_fn = value_fn.clone();
208 let insertion_result = cache.entry(next).and_compute_with(move |e| {
209 if e.is_none() {
210 let f = value_fn
211 .lock()
212 .take()
213 .expect("impossible: value_fn was already consumed");
214
215 moka::ops::compute::Op::Put(f(next))
216 } else {
217 moka::ops::compute::Op::Nop
218 }
219 });
220
221 if let moka::ops::compute::CompResult::Inserted(val) = insertion_result {
223 return Some((next, val.into_value()));
224 }
225
226 next = generator(Some(next));
228
229 if next == initial {
231 return None;
232 }
233 }
234}
235
236#[derive(Debug, Clone)]
239pub(crate) enum SurbNotificationMode {
240 DoNotNotify,
242 Target,
244 Level(AtomicSurbFlowEstimator),
246}
247
248#[instrument(level = "debug", skip(sender, routing, notification_mode, cfg, controller, admit))]
262pub(crate) fn spawn_keep_alive_stream<S>(
263 session_id: SessionId,
264 sender: S,
265 routing: DestinationRouting,
266 notification_mode: SurbNotificationMode,
267 cfg: std::sync::Arc<BalancerStateValues>,
268 controller: &RateController,
269 admit: impl Fn() -> bool + Send + 'static,
270) -> AbortHandle
271where
272 S: futures::Sink<(DestinationRouting, ApplicationDataOut)> + Clone + Send + Sync + Unpin + 'static,
273 S::Error: std::error::Error + Send + Sync + 'static,
274{
275 let (ka_stream, abort_handle) = futures::stream::abortable(
277 futures::stream::repeat_with(move || match ¬ification_mode {
278 SurbNotificationMode::Target => HoprStartProtocol::KeepAlive(KeepAliveMessage {
279 session_id,
280 flags: KeepAliveFlag::BalancerTarget.into(),
281 additional_data: cfg.target_surb_buffer_size.load(std::sync::atomic::Ordering::Relaxed),
282 }),
283 SurbNotificationMode::Level(_estimator) => HoprStartProtocol::KeepAlive(KeepAliveMessage {
290 session_id,
291 flags: KeepAliveFlag::BalancerState.into(),
292 additional_data: cfg.buffer_level(),
293 }),
294 SurbNotificationMode::DoNotNotify => HoprStartProtocol::KeepAlive(KeepAliveMessage {
295 session_id,
296 flags: None.into(),
297 additional_data: 0,
298 }),
299 })
300 .rate_limit_with_controller(controller)
301 .filter(move |_| futures::future::ready(admit())),
302 );
303
304 let sender_clone = sender.clone();
305 let fwd_routing_clone = routing.clone();
306
307 debug!(%session_id, "spawning keep-alive stream");
309 let keep_alive_diag = hopr_utils::runtime::diagnostics::ConcurrentDiagnostics::new(
310 "session_keep_alive_try_for_each_concurrent",
311 module_path!(),
312 file!(),
313 line!(),
314 );
315 hopr_utils::runtime::prelude::spawn(hopr_utils::runtime::diagnostics::instrument(
316 ka_stream
317 .map(move |msg| {
318 ApplicationData::try_from(msg)
319 .map(|data| (fwd_routing_clone.clone(), ApplicationDataOut::with_no_packet_info(data)))
320 })
321 .map_err(TransportSessionError::from)
322 .try_for_each_concurrent(None, move |msg| {
323 let mut sender_clone = sender_clone.clone();
324 let keep_alive_diag = keep_alive_diag.clone();
325 keep_alive_diag.wrap(|| async move {
326 sender_clone
327 .send(msg)
328 .await
329 .map_err(TransportSessionError::packet_sending)
330 })
331 })
332 .then(move |res| {
333 match res {
334 Ok(_) => tracing::debug!(
335 component = "session",
336 %session_id,
337 task = "session keepalive",
338 "background task finished"
339 ),
340 Err(error) => error!(%session_id, %error, "keep-alive stream failed"),
341 }
342 futures::future::ready(())
343 })
344 .in_current_span(),
345 "session_keep_alive",
346 module_path!(),
347 file!(),
348 line!(),
349 ));
350
351 abort_handle
352}
353
354#[cfg(test)]
355mod tests {
356 use std::time::Duration;
357
358 use anyhow::anyhow;
359 use futures::FutureExt;
360
361 use super::*;
362
363 fn cycling_generator(prev: Option<u8>) -> u8 {
365 prev.map(|v| (v + 1) % 5).unwrap_or(0)
366 }
367
368 #[tokio::test]
370 async fn test_insert_into_next_slot_sequential() -> anyhow::Result<()> {
371 let cache = moka::sync::Cache::new(10);
372
373 for i in 0..5 {
374 let (k, v) = insert_into_next_slot(&cache, cycling_generator, |k| format!("foo_{k}"), Some(10u64))
375 .ok_or(anyhow!("should insert into slot {i}"))?;
376 assert_eq!(k, i);
377 assert_eq!(format!("foo_{i}"), v);
378 assert_eq!(Some(v), cache.get(&i));
379 }
380
381 Ok(())
382 }
383
384 #[tokio::test]
386 async fn test_insert_into_next_slot_returns_none_when_full() -> anyhow::Result<()> {
387 let cache = moka::sync::Cache::new(10);
388
389 for _ in 0..5 {
390 insert_into_next_slot(&cache, cycling_generator, |k| format!("foo_{k}"), Some(10u64))
391 .ok_or(anyhow!("precondition: should insert"))?;
392 }
393
394 assert!(
395 insert_into_next_slot(&cache, cycling_generator, |_| "foo".to_string(), Some(10u64)).is_none(),
396 "must not find slot when full"
397 );
398
399 Ok(())
400 }
401
402 #[tokio::test]
404 async fn test_insert_into_next_slot_capacity_one_rejects_second_key() -> anyhow::Result<()> {
405 let unit_cache = moka::sync::Cache::new(1);
406
407 let (k0, _v0) = insert_into_next_slot(&unit_cache, |prev| prev.map(|v| v + 1).unwrap_or(0), |k| k, Some(1u64))
408 .ok_or(anyhow!("first insertion must succeed"))?;
409 assert_eq!(k0, 0);
410
411 assert!(
412 insert_into_next_slot(&unit_cache, |prev| prev.map(|v| v + 1).unwrap_or(0), |k| k, Some(1u64)).is_none(),
413 "second distinct key must be rejected when cache capacity is 1"
414 );
415
416 Ok(())
417 }
418
419 #[tokio::test]
421 async fn test_insert_into_next_slot_rejected_insertion_does_not_evict() -> anyhow::Result<()> {
422 let unit_cache = moka::sync::Cache::new(1);
423
424 let (k0, v0) = insert_into_next_slot(&unit_cache, |prev| prev.map(|v| v + 1).unwrap_or(0), |k| k, Some(1u64))
425 .ok_or(anyhow!("first insertion must succeed"))?;
426
427 insert_into_next_slot(&unit_cache, |prev| prev.map(|v| v + 1).unwrap_or(0), |k| k, Some(1u64));
428
429 assert_eq!(
430 Some(v0),
431 unit_cache.get(&k0),
432 "first entry must still be present after rejection"
433 );
434
435 Ok(())
436 }
437
438 fn noop_waker() -> std::task::Waker {
439 futures::task::noop_waker()
440 }
441
442 #[test]
447 fn notification_after_first_poll_completes_future() {
448 let n = Arc::new(SlotNotify::new());
449 let mut fut = n.notified();
450
451 let waker = noop_waker();
452 let mut cx = std::task::Context::from_waker(&waker);
453
454 assert_eq!(fut.poll_unpin(&mut cx), Poll::Pending);
456
457 n.notify_waiters();
458
459 assert_eq!(fut.poll_unpin(&mut cx), Poll::Ready(()));
461 }
462
463 #[test]
464 fn notify_after_creation_before_first_poll_completes() {
465 let n = Arc::new(SlotNotify::new());
466 let mut fut = n.notified();
467
468 n.notify_waiters();
470
471 let waker = noop_waker();
472 let mut cx = std::task::Context::from_waker(&waker);
473
474 assert_eq!(fut.poll_unpin(&mut cx), Poll::Ready(()));
476 }
477
478 #[test]
479 fn spurious_repoll_stays_pending_without_notification() {
480 let n = Arc::new(SlotNotify::new());
481 let mut fut = n.notified();
482
483 let waker = noop_waker();
484 let mut cx = std::task::Context::from_waker(&waker);
485
486 assert_eq!(fut.poll_unpin(&mut cx), Poll::Pending);
488
489 assert_eq!(fut.poll_unpin(&mut cx), Poll::Pending);
491 }
492
493 #[test]
494 fn multiple_notifications_bump_generation() {
495 let n = Arc::new(SlotNotify::new());
496 let mut fut = n.notified();
497
498 let waker = noop_waker();
499 let mut cx = std::task::Context::from_waker(&waker);
500
501 assert_eq!(fut.poll_unpin(&mut cx), Poll::Pending);
502
503 n.notify_waiters();
504 assert_eq!(fut.poll_unpin(&mut cx), Poll::Ready(()));
505
506 let mut fut2 = n.notified();
508 assert_eq!(fut2.poll_unpin(&mut cx), Poll::Pending);
509
510 n.notify_waiters();
511 assert_eq!(fut2.poll_unpin(&mut cx), Poll::Ready(()));
512 }
513
514 #[test]
515 fn drop_uncompleted_future_removes_waker() {
516 let n = Arc::new(SlotNotify::new());
517 {
518 let mut fut = n.notified();
519
520 let waker = noop_waker();
521 let mut cx = std::task::Context::from_waker(&waker);
522
523 assert_eq!(fut.poll_unpin(&mut cx), Poll::Pending);
524 }
526 assert!(n.inner.lock().wakers.is_empty());
528 }
529
530 #[test]
542 fn notify_waiters_releases_the_lock_before_waking() {
543 struct ReentrantWaker(SlotNotify);
544
545 impl std::task::Wake for ReentrantWaker {
546 fn wake(self: Arc<Self>) {
547 drop(self.0.notified());
550 }
551 }
552
553 let n = SlotNotify::new();
554 let mut fut = n.notified();
555 let waker = std::task::Waker::from(Arc::new(ReentrantWaker(n.clone())));
556 let mut cx = std::task::Context::from_waker(&waker);
557
558 assert_eq!(
559 fut.poll_unpin(&mut cx),
560 Poll::Pending,
561 "the first poll registers the waker"
562 );
563
564 n.notify_waiters();
565
566 assert_eq!(
567 fut.poll_unpin(&mut cx),
568 Poll::Ready(()),
569 "the generation advanced, so the waiter is done"
570 );
571 }
572
573 #[test]
585 fn one_notification_wakes_every_parked_waiter() {
586 struct CountingWaker(std::sync::atomic::AtomicUsize);
587
588 impl std::task::Wake for CountingWaker {
589 fn wake(self: Arc<Self>) {
590 self.wake_by_ref();
591 }
592
593 fn wake_by_ref(self: &Arc<Self>) {
594 self.0.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
595 }
596 }
597
598 const WAITERS: usize = 4;
599
600 let n = SlotNotify::new();
601 let counters: Vec<Arc<CountingWaker>> = (0..WAITERS)
602 .map(|_| Arc::new(CountingWaker(std::sync::atomic::AtomicUsize::new(0))))
603 .collect();
604 let mut futures: Vec<_> = (0..WAITERS).map(|_| n.notified()).collect();
605
606 for (i, (fut, counter)) in futures.iter_mut().zip(&counters).enumerate() {
607 let waker = std::task::Waker::from(counter.clone());
608 let mut cx = std::task::Context::from_waker(&waker);
609 assert_eq!(fut.poll_unpin(&mut cx), Poll::Pending, "waiter {i} must park");
610 }
611 assert_eq!(
612 WAITERS,
613 n.inner.lock().wakers.len(),
614 "every waiter must have registered"
615 );
616
617 n.notify_waiters();
618
619 for (i, counter) in counters.iter().enumerate() {
620 assert_eq!(
621 1,
622 counter.0.load(std::sync::atomic::Ordering::Relaxed),
623 "waiter {i} must be woken exactly once by the single notification"
624 );
625 }
626 }
627
628 #[test]
636 fn repolling_with_a_new_waker_replaces_the_stored_one() {
637 struct CountingWaker(std::sync::atomic::AtomicUsize);
638
639 impl std::task::Wake for CountingWaker {
640 fn wake(self: Arc<Self>) {
641 self.wake_by_ref();
642 }
643
644 fn wake_by_ref(self: &Arc<Self>) {
645 self.0.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
646 }
647 }
648
649 let n = SlotNotify::new();
650 let mut fut = n.notified();
651
652 let first = Arc::new(CountingWaker(std::sync::atomic::AtomicUsize::new(0)));
653 let second = Arc::new(CountingWaker(std::sync::atomic::AtomicUsize::new(0)));
654
655 let w1 = std::task::Waker::from(first.clone());
656 assert_eq!(
657 fut.poll_unpin(&mut std::task::Context::from_waker(&w1)),
658 Poll::Pending,
659 "the first poll registers"
660 );
661
662 let w2 = std::task::Waker::from(second.clone());
664 assert_eq!(
665 fut.poll_unpin(&mut std::task::Context::from_waker(&w2)),
666 Poll::Pending,
667 "no notification has happened, so the future stays parked"
668 );
669
670 n.notify_waiters();
671
672 assert_eq!(
673 0,
674 first.0.load(std::sync::atomic::Ordering::Relaxed),
675 "the superseded waker must not be the one woken"
676 );
677 assert_eq!(
678 1,
679 second.0.load(std::sync::atomic::Ordering::Relaxed),
680 "the waker from the most recent poll must be woken exactly once"
681 );
682 }
683
684 #[tokio::test]
689 async fn notified_awaited_completes_on_notify() {
690 let n = Arc::new(SlotNotify::new());
691 let n2 = n.clone();
692
693 let handle = tokio::spawn(async move {
694 n2.notified().await;
695 42u32
696 });
697
698 tokio::time::sleep(Duration::from_millis(10)).await;
699 n.notify_waiters();
700
701 let result = tokio::time::timeout(Duration::from_secs(1), handle)
702 .await
703 .expect("timeout")
704 .expect("task panicked");
705 assert_eq!(result, 42);
706 }
707}