1use crate::trace::{Span, Tag};
18use crate::{hashtable::ConcurrentHashTable, key::CacheHashKey, CacheKey, NoCacheReason};
19
20use http::Extensions;
21use pingora_timeout::timeout;
22use std::sync::Arc;
23use std::time::Duration;
24
25pub type CacheKeyLockImpl = dyn CacheKeyLock + Send + Sync;
26
27pub trait CacheKeyLock {
28 fn lock(&self, key: &CacheKey, stale_writer: bool) -> Locked;
36
37 fn release(&self, key: &CacheKey, permit: WritePermit, reason: LockStatus);
42
43 fn trace_lock_wait(&self, span: &mut Span, _read_lock: &ReadLock, lock_status: LockStatus) {
45 let tag_value: &'static str = lock_status.into();
46 span.set_tag(|| Tag::new("status", tag_value));
47 }
48
49 fn custom_lock_status(&self, _custom_no_cache: &'static str) -> LockStatus {
51 LockStatus::GiveUp
54 }
55}
56
57const N_SHARDS: usize = 16;
58
59#[derive(Debug)]
61pub struct CacheLock {
62 lock_table: ConcurrentHashTable<LockStub, N_SHARDS>,
63 age_timeout_default: Duration,
65}
66
67#[derive(Debug)]
69pub enum Locked {
70 Write(WritePermit),
72 Read(ReadLock),
74}
75
76impl Locked {
77 pub fn is_write(&self) -> bool {
79 matches!(self, Self::Write(_))
80 }
81}
82
83impl CacheLock {
84 pub fn new_boxed(age_timeout: Duration) -> Box<Self> {
90 Box::new(CacheLock {
91 lock_table: ConcurrentHashTable::new(),
92 age_timeout_default: age_timeout,
93 })
94 }
95
96 pub fn new(age_timeout_default: Duration) -> Self {
102 CacheLock {
103 lock_table: ConcurrentHashTable::new(),
104 age_timeout_default,
105 }
106 }
107}
108
109impl CacheKeyLock for CacheLock {
110 fn lock(&self, key: &CacheKey, stale_writer: bool) -> Locked {
111 let hash = key.combined_bin();
112 let key = u128::from_be_bytes(hash); let table = self.lock_table.get(key);
114 if let Some(lock) = table.read().get(&key) {
115 if !matches!(
123 lock.0.lock_status(),
124 LockStatus::Dangling | LockStatus::AgeTimeout
125 ) {
126 return Locked::Read(lock.read_lock());
127 }
128 }
131
132 let mut table = table.write();
133 if let Some(lock) = table.get(&key) {
135 if !matches!(
136 lock.0.lock_status(),
137 LockStatus::Dangling | LockStatus::AgeTimeout
138 ) {
139 return Locked::Read(lock.read_lock());
140 }
141 }
142 let (permit, stub) =
143 WritePermit::new(self.age_timeout_default, stale_writer, Extensions::new());
144 table.insert(key, stub);
145 Locked::Write(permit)
146 }
147
148 fn release(&self, key: &CacheKey, mut permit: WritePermit, reason: LockStatus) {
149 let hash = key.combined_bin();
150 let key = u128::from_be_bytes(hash); if permit.lock.lock_status() == LockStatus::AgeTimeout {
152 permit.unlock(LockStatus::AgeTimeout);
158 } else if let Some(_lock) = self.lock_table.write(key).remove(&key) {
159 permit.unlock(reason);
160 }
161 }
164}
165
166use log::warn;
167use parking_lot::Mutex;
168use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
169use std::time::Instant;
170use strum::{FromRepr, IntoStaticStr};
171use tokio::sync::{oneshot, Semaphore};
172
173#[derive(Debug, Copy, Clone, PartialEq, Eq, IntoStaticStr, FromRepr)]
175#[repr(u8)]
176pub enum LockStatus {
177 Waiting = 0,
179 Done = 1,
181 TransientError = 2,
183 GiveUp = 3,
186 Dangling = 4,
188 WaitTimeout = 5,
190 AgeTimeout = 6,
192}
193
194impl From<LockStatus> for u8 {
195 fn from(l: LockStatus) -> u8 {
196 match l {
197 LockStatus::Waiting => 0,
198 LockStatus::Done => 1,
199 LockStatus::TransientError => 2,
200 LockStatus::GiveUp => 3,
201 LockStatus::Dangling => 4,
202 LockStatus::WaitTimeout => 5,
203 LockStatus::AgeTimeout => 6,
204 }
205 }
206}
207
208impl From<u8> for LockStatus {
209 fn from(v: u8) -> Self {
210 Self::from_repr(v).unwrap_or(Self::GiveUp)
211 }
212}
213
214#[derive(Debug)]
215pub struct LockCore {
216 pub lock_start: Instant,
217 pub age_timeout: Duration,
218 pub(super) lock: Semaphore,
219 lock_status: AtomicU8,
221 stale_writer: bool,
222 extensions: Extensions,
223 fill: Mutex<FillState>,
227 published_any: AtomicBool,
231}
232
233#[derive(Debug, Default)]
234struct FillState {
235 published: Vec<u64>,
237 waiters: Vec<TokenWaiter>,
241 sweep_cursor: usize,
244}
245
246const WAITER_SWEEP_BUDGET: usize = 8;
249
250#[derive(Debug)]
251struct TokenWaiter {
252 unusable: Arc<[UnusableFill]>,
254 tell: oneshot::Sender<UnusableFill>,
256}
257
258impl LockCore {
259 pub fn new_arc(timeout: Duration, stale_writer: bool, extensions: Extensions) -> Arc<Self> {
260 Arc::new(LockCore {
261 lock: Semaphore::new(0),
262 age_timeout: timeout,
263 lock_start: Instant::now(),
264 lock_status: AtomicU8::new(LockStatus::Waiting.into()),
265 stale_writer,
266 extensions,
267 fill: Mutex::new(FillState::default()),
268 published_any: AtomicBool::new(false),
269 })
270 }
271
272 pub fn locked(&self) -> bool {
273 self.lock.available_permits() == 0
274 }
275
276 fn watch_fill(&self, unusable: &Arc<[UnusableFill]>) -> Watching {
279 let mut fill = self.fill.lock();
280 if let Some(matched) = first_match(&fill.published, unusable) {
281 return Watching::AlreadyPublished(matched);
282 }
283
284 let mut budget = WAITER_SWEEP_BUDGET;
287 while budget > 0 && !fill.waiters.is_empty() {
288 if fill.sweep_cursor >= fill.waiters.len() {
289 fill.sweep_cursor = 0;
290 }
291 let at = fill.sweep_cursor;
292 if fill.waiters[at].tell.is_closed() {
293 fill.waiters.swap_remove(at);
295 } else {
296 fill.sweep_cursor += 1;
297 }
298 budget -= 1;
299 }
300
301 let (tell, told) = oneshot::channel();
302 fill.waiters.push(TokenWaiter {
303 unusable: unusable.clone(),
304 tell,
305 });
306 Watching::Registered(told)
307 }
308
309 fn publish(&self, tokens: &[u64]) {
311 let woken = {
312 let mut fill = self.fill.lock();
313 fill.published.clear();
314 fill.published.extend_from_slice(tokens);
315 self.published_any
317 .store(!tokens.is_empty(), Ordering::Relaxed);
318
319 let mut woken = Vec::new();
322 let mut i = 0;
323 while i < fill.waiters.len() {
324 if fill.waiters[i].tell.is_closed() {
325 fill.waiters.swap_remove(i);
326 continue;
327 }
328 match first_match(tokens, &fill.waiters[i].unusable) {
329 Some(matched) => woken.push((fill.waiters.swap_remove(i).tell, matched)),
330 None => i += 1,
331 }
332 }
333 woken
334 };
335
336 for (tell, matched) in woken {
338 let _ = tell.send(matched);
339 }
340 }
341
342 #[cfg(test)]
344 fn registered_waiters(&self) -> usize {
345 self.fill
346 .lock()
347 .waiters
348 .iter()
349 .filter(|waiter| !waiter.tell.is_closed())
350 .count()
351 }
352
353 #[cfg(test)]
355 fn retained_waiters(&self) -> usize {
356 self.fill.lock().waiters.len()
357 }
358
359 pub fn unlock(&self, reason: LockStatus) {
360 assert!(
361 reason != LockStatus::WaitTimeout,
362 "WaitTimeout is not stored in LockCore"
363 );
364 self.lock_status.store(reason.into(), Ordering::SeqCst);
365 self.lock.add_permits(10);
368 }
369
370 pub fn lock_status(&self) -> LockStatus {
371 self.lock_status.load(Ordering::SeqCst).into()
372 }
373
374 pub fn stale_writer(&self) -> bool {
376 self.stale_writer
377 }
378
379 pub fn extensions(&self) -> &Extensions {
380 &self.extensions
381 }
382}
383
384#[derive(Debug)]
388pub struct ReadLock(Arc<LockCore>);
389
390#[derive(Debug, Clone, Copy, PartialEq, Eq)]
394pub struct UnusableFill {
395 pub token: u64,
398 pub reason: &'static str,
403}
404
405fn first_match(published: &[u64], unusable: &[UnusableFill]) -> Option<UnusableFill> {
408 unusable
409 .iter()
410 .find(|candidate| published.contains(&candidate.token))
411 .copied()
412}
413
414#[derive(Debug, Clone, Copy, PartialEq, Eq)]
420pub enum LockWaitOutcome {
421 Done,
423 TransientError,
426 Dangling,
429 WaitTimeout,
431 AgeTimeout,
434 GiveUp,
437 Abandoned {
441 reason: NoCacheReason,
443 token: u64,
445 },
446}
447
448impl LockWaitOutcome {
449 pub fn lock_status(&self) -> LockStatus {
453 match self {
454 LockWaitOutcome::Done => LockStatus::Done,
455 LockWaitOutcome::TransientError => LockStatus::TransientError,
456 LockWaitOutcome::Dangling => LockStatus::Dangling,
457 LockWaitOutcome::WaitTimeout => LockStatus::WaitTimeout,
458 LockWaitOutcome::AgeTimeout => LockStatus::AgeTimeout,
459 LockWaitOutcome::GiveUp | LockWaitOutcome::Abandoned { .. } => LockStatus::GiveUp,
460 }
461 }
462}
463
464#[derive(Debug, Clone)]
476pub struct UnusableFills {
477 pub(crate) fills: Arc<[UnusableFill]>,
478}
479
480impl UnusableFills {
481 pub fn new(fills: impl Into<Arc<[UnusableFill]>>) -> Self {
484 UnusableFills {
485 fills: fills.into(),
486 }
487 }
488
489 pub fn fills(&self) -> &[UnusableFill] {
491 &self.fills
492 }
493
494 pub fn first_match(&self, published: &[u64]) -> Option<UnusableFill> {
498 first_match(published, &self.fills)
499 }
500}
501
502#[derive(Debug, Clone, Copy, PartialEq, Eq)]
504pub enum WaitOutcome {
505 Released,
508 AgeTimeout,
510 Abandoned(UnusableFill),
512}
513
514enum Watching {
515 Registered(oneshot::Receiver<UnusableFill>),
516 AlreadyPublished(UnusableFill),
517}
518
519impl ReadLock {
520 pub async fn wait(&self) {
522 self.wait_inner(None).await;
523 }
524
525 pub async fn wait_unless_published(&self, unusable: &UnusableFills) -> WaitOutcome {
532 self.wait_inner(Some(&unusable.fills)).await
533 }
534
535 async fn wait_inner(&self, unusable: Option<&Arc<[UnusableFill]>>) -> WaitOutcome {
536 if !self.locked() {
537 return WaitOutcome::Released;
538 }
539
540 let Some(duration) = self.0.age_timeout.checked_sub(self.0.lock_start.elapsed()) else {
550 self.0
552 .lock_status
553 .store(LockStatus::AgeTimeout.into(), Ordering::SeqCst);
554 return WaitOutcome::AgeTimeout;
555 };
556
557 let unusable = unusable.filter(|unusable| !unusable.is_empty());
560
561 let told = match unusable {
562 None => {
564 return Self::writer_done(&self.0, duration).await;
565 }
566 Some(unusable) => match self.0.watch_fill(unusable) {
567 Watching::AlreadyPublished(matched) => {
569 if !self.locked() {
574 return WaitOutcome::Released;
575 }
576 return WaitOutcome::Abandoned(matched);
577 }
578 Watching::Registered(told) => told,
579 },
580 };
581 let told_to_stop = async {
582 match told.await {
583 Ok(matched) => WaitOutcome::Abandoned(matched),
584 Err(_) => {
592 debug_assert!(false, "fill waiter sender dropped while registered");
593 WaitOutcome::Released
594 }
595 }
596 };
597
598 tokio::select! {
601 biased;
602 outcome = Self::writer_done(&self.0, duration) => outcome,
603 outcome = told_to_stop => outcome,
604 }
605 }
606
607 async fn writer_done(core: &LockCore, duration: Duration) -> WaitOutcome {
609 match timeout(duration, core.lock.acquire()).await {
610 Ok(Ok(_)) => {
611 WaitOutcome::Released
613 }
614 Ok(Err(e)) => {
615 warn!("error acquiring semaphore {e:?}");
616 WaitOutcome::Released
617 }
618 Err(_) => {
619 core.lock_status
620 .store(LockStatus::AgeTimeout.into(), Ordering::SeqCst);
621 WaitOutcome::AgeTimeout
622 }
623 }
624 }
625
626 pub fn locked(&self) -> bool {
628 self.0.locked()
629 }
630
631 pub fn expired(&self) -> bool {
633 self.0.lock_start.elapsed() >= self.0.age_timeout
636 }
637
638 pub fn lock_status(&self) -> LockStatus {
640 let status = self.0.lock_status();
641 if matches!(status, LockStatus::Waiting) && self.expired() {
642 LockStatus::AgeTimeout
643 } else {
644 status
645 }
646 }
647
648 pub fn extensions(&self) -> &Extensions {
649 self.0.extensions()
650 }
651}
652
653#[derive(Debug)]
655pub struct WritePermit {
656 lock: Arc<LockCore>,
657 finished: bool,
658}
659
660impl WritePermit {
661 pub fn new(
663 timeout: Duration,
664 stale_writer: bool,
665 extensions: Extensions,
666 ) -> (WritePermit, LockStub) {
667 let lock = LockCore::new_arc(timeout, stale_writer, extensions);
668 let stub = LockStub(lock.clone());
669 (
670 WritePermit {
671 lock,
672 finished: false,
673 },
674 stub,
675 )
676 }
677
678 pub fn stale_writer(&self) -> bool {
680 self.lock.stale_writer()
681 }
682
683 pub fn unlock(&mut self, reason: LockStatus) {
684 self.finished = true;
685 self.lock.unlock(reason);
686 }
687
688 pub fn lock_status(&self) -> LockStatus {
689 self.lock.lock_status()
690 }
691
692 pub fn extensions(&self) -> &Extensions {
693 self.lock.extensions()
694 }
695
696 pub fn publish(&self, tokens: &[u64]) {
704 if tokens.is_empty() && !self.lock.published_any.load(Ordering::Relaxed) {
710 return;
711 }
712 self.lock.publish(tokens);
713 }
714}
715
716impl Drop for WritePermit {
717 fn drop(&mut self) {
718 if !self.finished {
720 debug_assert!(false, "Dangling cache lock started!");
721 self.unlock(LockStatus::Dangling);
722 }
723 }
724}
725
726#[derive(Debug)]
727pub struct LockStub(pub Arc<LockCore>);
728impl LockStub {
729 pub fn read_lock(&self) -> ReadLock {
730 ReadLock(self.0.clone())
731 }
732
733 pub fn extensions(&self) -> &Extensions {
734 &self.0.extensions
735 }
736}
737
738#[cfg(test)]
739mod test {
740 use super::*;
741 use crate::CacheKey;
742
743 const WRONG_PLACE: u64 = 7;
744 const SOMEWHERE_ELSE: u64 = 9;
745
746 const NO_GOOD: &str = "NoGoodToThisReader";
748
749 fn cannot_use(token: u64) -> [UnusableFill; 1] {
751 [UnusableFill {
752 token,
753 reason: NO_GOOD,
754 }]
755 }
756
757 fn new_lock(age: Duration) -> (WritePermit, LockStub) {
758 WritePermit::new(age, false, Extensions::new())
759 }
760
761 fn reader(stub: &LockStub) -> ReadLock {
762 ReadLock(stub.0.clone())
763 }
764
765 async fn registered(stub: &LockStub, want: usize) {
769 for _ in 0..1_000 {
770 if stub.0.registered_waiters() >= want {
771 return;
772 }
773 tokio::task::yield_now().await;
774 }
775 panic!(
776 "expected {want} registered waiter(s), found {}",
777 stub.0.registered_waiters()
778 );
779 }
780
781 struct CountPolls {
784 inner: std::pin::Pin<Box<dyn std::future::Future<Output = WaitOutcome> + Send>>,
785 polls: Arc<std::sync::atomic::AtomicUsize>,
786 }
787
788 impl std::future::Future for CountPolls {
789 type Output = WaitOutcome;
790
791 fn poll(
792 self: std::pin::Pin<&mut Self>,
793 cx: &mut std::task::Context<'_>,
794 ) -> std::task::Poll<WaitOutcome> {
795 let this = self.get_mut();
796 this.polls.fetch_add(1, Ordering::Relaxed);
797 this.inner.as_mut().poll(cx)
798 }
799 }
800
801 #[tokio::test]
804 async fn only_the_reader_whose_tokens_match_is_woken() {
805 let (permit, stub) = new_lock(Duration::from_secs(30));
806 let polls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
807
808 let leaves = reader(&stub);
809 let left = tokio::spawn(async move {
810 leaves
811 .wait_unless_published(&UnusableFills::new(cannot_use(WRONG_PLACE)))
812 .await
813 });
814 let stays = reader(&stub);
815 let stayed = tokio::spawn(CountPolls {
816 inner: Box::pin(async move {
817 stays
818 .wait_unless_published(&UnusableFills::new(cannot_use(SOMEWHERE_ELSE)))
819 .await
820 }),
821 polls: polls.clone(),
822 });
823 registered(&stub, 2).await;
824 let polled_before = polls.load(Ordering::Relaxed);
825
826 permit.publish(&[WRONG_PLACE]);
827
828 assert_eq!(
829 left.await.unwrap(),
830 WaitOutcome::Abandoned(UnusableFill {
831 token: WRONG_PLACE,
832 reason: NO_GOOD,
833 })
834 );
835 assert!(!stayed.is_finished(), "the other reader still coalesces");
836 assert_eq!(
837 polls.load(Ordering::Relaxed),
838 polled_before,
839 "and was never woken by a publication that does not concern it"
840 );
841 assert_eq!(
842 stub.0.registered_waiters(),
843 1,
844 "the reader that left is no longer registered"
845 );
846 assert_eq!(
847 stub.0.lock_status(),
848 LockStatus::Waiting,
849 "giving up does not touch the shared status"
850 );
851
852 let mut permit = permit;
853 permit.unlock(LockStatus::Done);
854 assert_eq!(stayed.await.unwrap(), WaitOutcome::Released);
855 assert!(
856 polls.load(Ordering::Relaxed) > polled_before,
857 "it is woken when the writer actually releases"
858 );
859 }
860
861 #[tokio::test]
864 async fn a_reader_that_names_no_tokens_is_never_registered() {
865 let (permit, stub) = new_lock(Duration::from_secs(30));
866 let core = reader(&stub);
867 let waiting = tokio::spawn(async move { core.wait().await });
868 tokio::task::yield_now().await;
869
870 assert_eq!(stub.0.registered_waiters(), 0);
871 permit.publish(&[WRONG_PLACE, SOMEWHERE_ELSE]);
872 tokio::task::yield_now().await;
873 assert!(!waiting.is_finished());
874 assert_eq!(stub.0.registered_waiters(), 0);
875
876 let mut permit = permit;
877 permit.unlock(LockStatus::Done);
878 waiting.await.unwrap();
879 }
880
881 #[tokio::test]
884 async fn tokens_published_before_the_reader_arrives_are_not_missed() {
885 let (permit, stub) = new_lock(Duration::from_secs(30));
886 permit.publish(&[WRONG_PLACE]); let outcome = reader(&stub)
889 .wait_unless_published(&UnusableFills::new(cannot_use(WRONG_PLACE)))
890 .await;
891
892 assert_eq!(
893 outcome,
894 WaitOutcome::Abandoned(UnusableFill {
895 token: WRONG_PLACE,
896 reason: NO_GOOD,
897 })
898 );
899 assert_eq!(stub.0.registered_waiters(), 0, "and it did not register");
900
901 let mut permit = permit;
902 permit.unlock(LockStatus::Done);
903 }
904
905 #[tokio::test]
908 async fn a_reader_is_not_stopped_by_a_superseded_publication() {
909 let (permit, stub) = new_lock(Duration::from_secs(30));
910 permit.publish(&[WRONG_PLACE]);
911 permit.publish(&[SOMEWHERE_ELSE]); let stays = reader(&stub);
914 let stayed = tokio::spawn(async move {
915 stays
916 .wait_unless_published(&UnusableFills::new(cannot_use(WRONG_PLACE)))
917 .await
918 });
919 registered(&stub, 1).await;
920 assert!(!stayed.is_finished(), "the old attempt no longer matters");
921
922 permit.publish(&[WRONG_PLACE]);
924 assert_eq!(
925 stayed.await.unwrap(),
926 WaitOutcome::Abandoned(UnusableFill {
927 token: WRONG_PLACE,
928 reason: NO_GOOD,
929 })
930 );
931
932 let mut permit = permit;
933 permit.unlock(LockStatus::Done);
934 }
935
936 #[tokio::test]
945 async fn cancelling_the_wait_leaves_nothing_to_match() {
946 let (mut permit, stub) = new_lock(Duration::from_secs(30));
947
948 let waits = reader(&stub);
949 let interest = UnusableFills::new(cannot_use(WRONG_PLACE));
950 let mut wait = Box::pin(waits.wait_unless_published(&interest));
954
955 assert!(
956 futures::poll!(wait.as_mut()).is_pending(),
957 "the writer still holds the lock"
958 );
959 assert_eq!(stub.0.registered_waiters(), 1);
960 assert_eq!(stub.0.retained_waiters(), 1);
961
962 drop(wait);
963 assert_eq!(
964 stub.0.registered_waiters(),
965 0,
966 "a cancelled reader must not be left for the writer to test"
967 );
968 assert_eq!(
969 stub.0.retained_waiters(),
970 1,
971 "though the entry is still there until something sweeps it"
972 );
973
974 permit.publish(&[SOMEWHERE_ELSE]);
975 assert_eq!(
976 stub.0.retained_waiters(),
977 0,
978 "and a publication sweeps it even without matching it"
979 );
980
981 permit.unlock(LockStatus::Done);
982 }
983
984 #[tokio::test]
987 async fn departed_readers_are_swept_by_later_registrations() {
988 let (mut permit, stub) = new_lock(Duration::from_secs(30));
989 let interest = UnusableFills::new(cannot_use(WRONG_PLACE));
990
991 for _ in 0..200 {
992 let waits = reader(&stub);
993 let mut wait = Box::pin(waits.wait_unless_published(&interest));
994 assert!(
995 futures::poll!(wait.as_mut()).is_pending(),
996 "the writer still holds the lock"
997 );
998 drop(wait);
999 }
1000
1001 assert_eq!(
1002 stub.0.registered_waiters(),
1003 0,
1004 "every one of those readers has gone"
1005 );
1006 assert!(
1007 stub.0.retained_waiters() <= WAITER_SWEEP_BUDGET,
1008 "swept back down, found {}",
1009 stub.0.retained_waiters()
1010 );
1011
1012 permit.unlock(LockStatus::Done);
1013 }
1014
1015 #[tokio::test]
1019 async fn departed_readers_behind_live_ones_are_still_reclaimed() {
1020 let (mut permit, stub) = new_lock(Duration::from_secs(30));
1021 let interest = UnusableFills::new(cannot_use(WRONG_PLACE));
1022
1023 let waits = reader(&stub);
1027
1028 let mut live = Vec::new();
1030 for _ in 0..(WAITER_SWEEP_BUDGET * 2) {
1031 let mut wait = Box::pin(waits.wait_unless_published(&interest));
1032 assert!(futures::poll!(wait.as_mut()).is_pending());
1033 live.push(wait);
1034 }
1035 assert_eq!(stub.0.registered_waiters(), live.len());
1036
1037 let departed = WAITER_SWEEP_BUDGET * 4;
1039 for _ in 0..departed {
1040 let mut wait = Box::pin(waits.wait_unless_published(&interest));
1041 assert!(futures::poll!(wait.as_mut()).is_pending());
1042 drop(wait);
1043 }
1044
1045 assert_eq!(
1046 stub.0.registered_waiters(),
1047 live.len(),
1048 "the readers that stayed are all still registered"
1049 );
1050 assert!(
1051 stub.0.retained_waiters() <= live.len() + WAITER_SWEEP_BUDGET,
1052 "departed readers must be reclaimed despite never being at the front, \
1053 leaving an overhead set by the sweep budget rather than by how many \
1054 readers have come and gone: found {} entries for {} live readers",
1055 stub.0.retained_waiters(),
1056 live.len()
1057 );
1058
1059 drop(live);
1060 permit.unlock(LockStatus::Done);
1061 }
1062
1063 #[tokio::test]
1067 async fn a_wait_cut_short_by_an_outer_timeout_leaves_nothing_to_match() {
1068 let (mut permit, stub) = new_lock(Duration::from_secs(30));
1069
1070 let waits = reader(&stub);
1071 let cut_short = tokio::time::timeout(
1072 Duration::from_millis(10),
1073 waits.wait_unless_published(&UnusableFills::new(cannot_use(WRONG_PLACE))),
1074 )
1075 .await;
1076
1077 assert!(cut_short.is_err(), "the writer never released");
1078 assert_eq!(
1079 stub.0.registered_waiters(),
1080 0,
1081 "nothing is left for a publication to match"
1082 );
1083 assert_eq!(
1084 stub.0.retained_waiters(),
1085 1,
1086 "the entry outlives the wait, to be swept later"
1087 );
1088
1089 permit.unlock(LockStatus::Done);
1090 }
1091
1092 #[tokio::test]
1096 async fn an_empty_interest_registers_no_waiter() {
1097 let (mut permit, stub) = new_lock(Duration::from_secs(30));
1098
1099 let waits = reader(&stub);
1100 let waited =
1101 tokio::spawn(async move { waits.wait_unless_published(&UnusableFills::new([])).await });
1102
1103 let names_something = reader(&stub);
1108 let named = tokio::spawn(async move {
1109 names_something
1110 .wait_unless_published(&UnusableFills::new(cannot_use(WRONG_PLACE)))
1111 .await
1112 });
1113 registered(&stub, 1).await;
1114
1115 assert_eq!(
1116 stub.0.registered_waiters(),
1117 1,
1118 "an interest that names nothing has nothing to be told about"
1119 );
1120 drop(named);
1121
1122 permit.unlock(LockStatus::Done);
1125 assert_eq!(waited.await.unwrap(), WaitOutcome::Released);
1126 }
1127
1128 #[tokio::test]
1132 async fn publishing_nothing_clears_a_previous_publication() {
1133 let (permit, stub) = new_lock(Duration::from_secs(30));
1134 permit.publish(&[WRONG_PLACE]);
1135 permit.publish(&[]); assert_eq!(
1140 first_match(&stub.0.fill.lock().published, &cannot_use(WRONG_PLACE)),
1141 None,
1142 "the abandoned attempt no longer matters"
1143 );
1144
1145 let stays = reader(&stub);
1146 let stayed = tokio::spawn(async move {
1147 stays
1148 .wait_unless_published(&UnusableFills::new(cannot_use(WRONG_PLACE)))
1149 .await
1150 });
1151
1152 let mut permit = permit;
1153 permit.unlock(LockStatus::Done);
1154 assert_eq!(stayed.await.unwrap(), WaitOutcome::Released);
1155 }
1156
1157 #[tokio::test]
1160 async fn a_reader_that_times_out_reports_the_age_timeout() {
1161 let (permit, stub) = new_lock(Duration::from_millis(50));
1162
1163 let outcome = reader(&stub)
1164 .wait_unless_published(&UnusableFills::new(cannot_use(WRONG_PLACE)))
1165 .await;
1166
1167 assert_eq!(outcome, WaitOutcome::AgeTimeout);
1168
1169 let mut permit = permit;
1170 permit.unlock(LockStatus::Done);
1171 }
1172
1173 #[test]
1174 fn test_get_release() {
1175 let cache_lock = CacheLock::new_boxed(Duration::from_secs(1000));
1176 let key1 = CacheKey::new("a", "1");
1177 let locked1 = cache_lock.lock(&key1, false);
1178 assert!(locked1.is_write()); let locked2 = cache_lock.lock(&key1, false);
1180 assert!(!locked2.is_write()); if let Locked::Write(permit) = locked1 {
1182 cache_lock.release(&key1, permit, LockStatus::Done);
1183 }
1184 let locked3 = cache_lock.lock(&key1, false);
1185 assert!(locked3.is_write()); if let Locked::Write(permit) = locked3 {
1187 cache_lock.release(&key1, permit, LockStatus::Done);
1188 }
1189 }
1190
1191 #[tokio::test]
1192 async fn test_lock() {
1193 let cache_lock = CacheLock::new_boxed(Duration::from_secs(1000));
1194 let key1 = CacheKey::new("a", "1");
1195 let mut permit = match cache_lock.lock(&key1, false) {
1196 Locked::Write(w) => w,
1197 _ => panic!(),
1198 };
1199 let lock = match cache_lock.lock(&key1, false) {
1200 Locked::Read(r) => r,
1201 _ => panic!(),
1202 };
1203 assert!(lock.locked());
1204 let handle = tokio::spawn(async move {
1205 lock.wait().await;
1206 assert_eq!(lock.lock_status(), LockStatus::Done);
1207 });
1208 permit.unlock(LockStatus::Done);
1209 handle.await.unwrap(); }
1211
1212 #[tokio::test]
1213 async fn test_lock_timeout() {
1214 let cache_lock = CacheLock::new_boxed(Duration::from_secs(1));
1215 let key1 = CacheKey::new("a", "1");
1216 let mut permit = match cache_lock.lock(&key1, false) {
1217 Locked::Write(w) => w,
1218 _ => panic!(),
1219 };
1220 let lock = match cache_lock.lock(&key1, false) {
1221 Locked::Read(r) => r,
1222 _ => panic!(),
1223 };
1224 assert!(lock.locked());
1225
1226 let handle = tokio::spawn(async move {
1227 lock.wait().await;
1229 assert_eq!(lock.lock_status(), LockStatus::AgeTimeout);
1230 });
1231
1232 tokio::time::sleep(Duration::from_millis(2100)).await;
1233
1234 handle.await.unwrap(); let mut permit2 = match cache_lock.lock(&key1, false) {
1238 Locked::Write(w) => w,
1239 _ => panic!(),
1240 };
1241 let lock2 = match cache_lock.lock(&key1, false) {
1242 Locked::Read(r) => r,
1243 _ => panic!(),
1244 };
1245 assert!(lock2.locked());
1246 let handle = tokio::spawn(async move {
1247 lock2.wait().await;
1249 assert_eq!(lock2.lock_status(), LockStatus::Done);
1250 });
1251
1252 permit.unlock(LockStatus::Done);
1253 permit2.unlock(LockStatus::Done);
1254 handle.await.unwrap();
1255 }
1256
1257 #[tokio::test]
1258 async fn test_lock_expired_release() {
1259 let cache_lock = CacheLock::new_boxed(Duration::from_secs(1));
1260 let key1 = CacheKey::new("a", "1");
1261 let permit = match cache_lock.lock(&key1, false) {
1262 Locked::Write(w) => w,
1263 _ => panic!(),
1264 };
1265
1266 let lock = match cache_lock.lock(&key1, false) {
1267 Locked::Read(r) => r,
1268 _ => panic!(),
1269 };
1270 assert!(lock.locked());
1271 let handle = tokio::spawn(async move {
1272 lock.wait().await;
1274 assert_eq!(lock.lock_status(), LockStatus::AgeTimeout);
1275 });
1276
1277 tokio::time::sleep(Duration::from_millis(1100)).await; handle.await.unwrap(); cache_lock.release(&key1, permit, LockStatus::Done);
1282
1283 let mut permit = match cache_lock.lock(&key1, false) {
1285 Locked::Write(w) => w,
1286 _ => panic!(),
1287 };
1288 assert_eq!(permit.lock.lock_status(), LockStatus::Waiting);
1289
1290 let lock2 = match cache_lock.lock(&key1, false) {
1291 Locked::Read(r) => r,
1292 _ => panic!(),
1293 };
1294 assert!(lock2.locked());
1295 let handle = tokio::spawn(async move {
1296 lock2.wait().await;
1298 assert_eq!(lock2.lock_status(), LockStatus::Done);
1299 });
1300
1301 permit.unlock(LockStatus::Done);
1302 handle.await.unwrap();
1303 }
1304
1305 #[tokio::test]
1306 async fn test_lock_expired_no_reader() {
1307 let cache_lock = CacheLock::new_boxed(Duration::from_secs(1));
1308 let key1 = CacheKey::new("a", "1");
1309 let mut permit = match cache_lock.lock(&key1, false) {
1310 Locked::Write(w) => w,
1311 _ => panic!(),
1312 };
1313 tokio::time::sleep(Duration::from_millis(1100)).await; assert_eq!(permit.lock.lock_status(), LockStatus::Waiting);
1317
1318 let lock = match cache_lock.lock(&key1, false) {
1319 Locked::Read(r) => r,
1320 _ => panic!(),
1321 };
1322 lock.wait().await;
1324 assert_eq!(lock.lock_status(), LockStatus::AgeTimeout);
1325 assert_eq!(permit.lock.lock_status(), LockStatus::AgeTimeout);
1326 permit.unlock(LockStatus::AgeTimeout);
1327 }
1328
1329 #[tokio::test]
1330 async fn test_lock_concurrent() {
1331 let _ = env_logger::builder().is_test(true).try_init();
1332 let cache_lock = Arc::new(CacheLock::new_boxed(Duration::from_secs(1)));
1334 let key1 = CacheKey::new("a", "1");
1335
1336 let mut handles = vec![];
1337
1338 const READERS: usize = 30;
1339 for _ in 0..READERS {
1340 let key1 = key1.clone();
1341 let cache_lock = cache_lock.clone();
1342 handles.push(tokio::spawn(async move {
1344 loop {
1346 match cache_lock.lock(&key1, false) {
1347 Locked::Write(permit) => {
1348 let _ = tokio::time::sleep(Duration::from_millis(5)).await;
1349 cache_lock.release(&key1, permit, LockStatus::Done);
1350 break;
1351 }
1352 Locked::Read(r) => {
1353 r.wait().await;
1354 }
1355 }
1356 }
1357 }));
1358 }
1359
1360 for handle in handles {
1361 handle.await.unwrap();
1362 }
1363 }
1364
1365 #[tokio::test]
1370 async fn an_expired_lock_times_out_rather_than_abandoning() {
1371 let (mut permit, stub) = new_lock(Duration::from_millis(10));
1372 stub.0.publish(&[WRONG_PLACE]);
1373 tokio::time::sleep(Duration::from_millis(30)).await;
1374
1375 let outcome = reader(&stub)
1376 .wait_unless_published(&UnusableFills::new(cannot_use(WRONG_PLACE)))
1377 .await;
1378
1379 assert_eq!(
1380 outcome,
1381 WaitOutcome::AgeTimeout,
1382 "expiry is decided before any published token is consulted"
1383 );
1384 assert_eq!(
1385 stub.0.lock_status(),
1386 LockStatus::AgeTimeout,
1387 "the dead lock must not be left reading Waiting"
1388 );
1389
1390 permit.unlock(LockStatus::Done);
1391 }
1392}