1use std::time::Duration;
12
13use crate::time::{Timeout, Timestamp, duration_to_ticks};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
22pub enum Priority {
23 Low,
25 #[default]
27 Normal,
28 High,
30 NeverRemove,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
37pub enum RemoveByTagBehavior {
38 #[default]
41 Expire,
42 Remove,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
49pub enum KeyModifierMode {
50 #[default]
53 Prefix,
54 Suffix,
56 None,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq)]
67pub struct EagerThreshold(f32);
68
69impl EagerThreshold {
70 #[must_use]
72 pub fn new(fraction: f32) -> Option<Self> {
73 if fraction > 0.0 && fraction < 1.0 {
74 Some(Self(fraction))
75 } else {
76 None
77 }
78 }
79
80 #[must_use]
82 pub fn fraction(self) -> f32 {
83 self.0
84 }
85}
86
87#[derive(Debug, Clone)]
93pub struct EntryOptions {
94 duration: Duration,
96 memory_duration: Option<Duration>,
97 distributed_duration: Option<Duration>,
98 eager_refresh_threshold: Option<EagerThreshold>,
99 jitter_max: Duration,
100
101 lock_timeout: Timeout,
103 memory_lock_timeout: Option<Timeout>,
106 distributed_lock_timeout: Option<Timeout>,
107
108 is_fail_safe_enabled: bool,
110 fail_safe_max_duration: Duration,
111 fail_safe_throttle_duration: Duration,
112 distributed_fail_safe_max_duration: Option<Duration>,
113
114 factory_soft_timeout: Timeout,
116 factory_hard_timeout: Timeout,
117 allow_timed_out_factory_background_completion: bool,
118
119 skip_memory_read: bool,
121 skip_memory_write: bool,
122 priority: Priority,
123 size: Option<i64>,
124 allow_stale_on_read_only: bool,
125
126 distributed_soft_timeout: Timeout,
128 distributed_hard_timeout: Timeout,
129 allow_background_distributed_operations: bool,
130 rethrow_distributed_exceptions: bool,
131 rethrow_serialization_exceptions: bool,
132 skip_distributed_read: bool,
133 skip_distributed_write: bool,
134 skip_distributed_read_when_stale: bool,
135 skip_distributed_locker: bool,
136 enable_auto_clone: bool,
137
138 skip_backplane_notifications: bool,
140 allow_background_backplane_operations: bool,
141 rethrow_backplane_exceptions: bool,
142}
143
144impl Default for EntryOptions {
145 fn default() -> Self {
146 Self {
147 duration: Duration::from_secs(30),
148 memory_duration: None,
149 distributed_duration: None,
150 eager_refresh_threshold: None,
151 jitter_max: Duration::ZERO,
152
153 lock_timeout: Timeout::Infinite,
154 memory_lock_timeout: None,
155 distributed_lock_timeout: None,
156
157 is_fail_safe_enabled: false,
158 fail_safe_max_duration: Duration::from_secs(60 * 60 * 24), fail_safe_throttle_duration: Duration::from_secs(30),
160 distributed_fail_safe_max_duration: None,
161
162 factory_soft_timeout: Timeout::Infinite,
163 factory_hard_timeout: Timeout::Infinite,
164 allow_timed_out_factory_background_completion: true,
165
166 skip_memory_read: false,
167 skip_memory_write: false,
168 priority: Priority::Normal,
169 size: None,
170 allow_stale_on_read_only: false,
171
172 distributed_soft_timeout: Timeout::Infinite,
173 distributed_hard_timeout: Timeout::Infinite,
174 allow_background_distributed_operations: false,
175 rethrow_distributed_exceptions: false,
176 rethrow_serialization_exceptions: true,
177 skip_distributed_read: false,
178 skip_distributed_write: false,
179 skip_distributed_read_when_stale: false,
180 skip_distributed_locker: false,
181 enable_auto_clone: false,
182
183 skip_backplane_notifications: false,
184 allow_background_backplane_operations: true,
185 rethrow_backplane_exceptions: false,
186 }
187 }
188}
189
190impl EntryOptions {
191 #[must_use]
194 pub fn new(duration: Duration) -> Self {
195 Self {
196 duration,
197 ..Self::default()
198 }
199 }
200
201 #[must_use]
205 pub fn with_duration(mut self, duration: Duration) -> Self {
206 self.duration = duration;
207 self
208 }
209
210 #[must_use]
212 pub fn with_memory_duration(mut self, duration: Duration) -> Self {
213 self.memory_duration = Some(duration);
214 self
215 }
216
217 #[must_use]
219 pub fn with_distributed_duration(mut self, duration: Duration) -> Self {
220 self.distributed_duration = Some(duration);
221 self
222 }
223
224 #[must_use]
227 pub fn with_eager_refresh(mut self, threshold: Option<EagerThreshold>) -> Self {
228 self.eager_refresh_threshold = threshold;
229 self
230 }
231
232 #[must_use]
235 pub fn with_jitter_max(mut self, jitter_max: Duration) -> Self {
236 self.jitter_max = jitter_max;
237 self
238 }
239
240 #[must_use]
243 pub fn with_lock_timeout(mut self, timeout: Timeout) -> Self {
244 self.lock_timeout = timeout;
245 self
246 }
247
248 #[must_use]
251 pub fn with_memory_lock_timeout(mut self, timeout: Timeout) -> Self {
252 self.memory_lock_timeout = Some(timeout);
253 self
254 }
255
256 #[must_use]
259 pub fn with_distributed_lock_timeout(mut self, timeout: Timeout) -> Self {
260 self.distributed_lock_timeout = Some(timeout);
261 self
262 }
263
264 #[must_use]
268 pub fn with_fail_safe(
269 mut self,
270 enabled: bool,
271 max_duration: Option<Duration>,
272 throttle_duration: Option<Duration>,
273 ) -> Self {
274 self.is_fail_safe_enabled = enabled;
275 if let Some(max) = max_duration {
276 self.fail_safe_max_duration = max;
277 }
278 if let Some(throttle) = throttle_duration {
279 self.fail_safe_throttle_duration = throttle;
280 }
281 self
282 }
283
284 #[must_use]
287 pub fn with_factory_timeouts(
288 mut self,
289 soft: Timeout,
290 hard: Timeout,
291 allow_background_completion: bool,
292 ) -> Self {
293 self.factory_soft_timeout = soft;
294 self.factory_hard_timeout = hard;
295 self.allow_timed_out_factory_background_completion = allow_background_completion;
296 self
297 }
298
299 #[must_use]
301 pub fn with_priority(mut self, priority: Priority) -> Self {
302 self.priority = priority;
303 self
304 }
305
306 #[must_use]
308 pub fn with_size(mut self, size: i64) -> Self {
309 self.size = Some(size);
310 self
311 }
312
313 #[must_use]
316 pub fn with_allow_stale_on_read_only(mut self, allow: bool) -> Self {
317 self.allow_stale_on_read_only = allow;
318 self
319 }
320
321 #[must_use]
323 pub fn with_skip_memory(mut self, skip_read: bool, skip_write: bool) -> Self {
324 self.skip_memory_read = skip_read;
325 self.skip_memory_write = skip_write;
326 self
327 }
328
329 #[must_use]
331 pub fn with_skip_distributed(mut self, skip_read: bool, skip_write: bool) -> Self {
332 self.skip_distributed_read = skip_read;
333 self.skip_distributed_write = skip_write;
334 self
335 }
336
337 #[must_use]
340 pub fn with_skip_distributed_read_when_stale(mut self, skip: bool) -> Self {
341 self.skip_distributed_read_when_stale = skip;
342 self
343 }
344
345 #[must_use]
348 pub fn with_distributed_fail_safe_max_duration(mut self, max: Duration) -> Self {
349 self.distributed_fail_safe_max_duration = Some(max);
350 self
351 }
352
353 #[must_use]
356 pub fn with_rethrow_distributed_exceptions(mut self, rethrow: bool) -> Self {
357 self.rethrow_distributed_exceptions = rethrow;
358 self
359 }
360
361 #[must_use]
364 pub fn with_rethrow_serialization_exceptions(mut self, rethrow: bool) -> Self {
365 self.rethrow_serialization_exceptions = rethrow;
366 self
367 }
368
369 #[must_use]
373 pub fn with_allow_background_backplane_operations(mut self, allow: bool) -> Self {
374 self.allow_background_backplane_operations = allow;
375 self
376 }
377
378 #[must_use]
380 pub fn with_distributed_timeouts(mut self, soft: Timeout, hard: Timeout) -> Self {
381 self.distributed_soft_timeout = soft;
382 self.distributed_hard_timeout = hard;
383 self
384 }
385
386 #[must_use]
388 pub fn with_allow_background_distributed_operations(mut self, allow: bool) -> Self {
389 self.allow_background_distributed_operations = allow;
390 self
391 }
392
393 #[must_use]
395 pub fn with_skip_backplane_notifications(mut self, skip: bool) -> Self {
396 self.skip_backplane_notifications = skip;
397 self
398 }
399
400 #[must_use]
404 pub fn duration(&self) -> Duration {
405 self.duration
406 }
407
408 #[must_use]
410 pub fn is_fail_safe_enabled(&self) -> bool {
411 self.is_fail_safe_enabled
412 }
413
414 #[must_use]
417 pub fn fail_safe_throttle_duration(&self) -> Duration {
418 self.fail_safe_throttle_duration
419 }
420
421 #[must_use]
423 pub fn eager_refresh_threshold(&self) -> Option<EagerThreshold> {
424 self.eager_refresh_threshold
425 }
426
427 #[must_use]
430 pub fn lock_timeout(&self) -> Timeout {
431 self.lock_timeout
432 }
433
434 #[must_use]
437 pub fn memory_lock_timeout(&self) -> Timeout {
438 self.memory_lock_timeout.unwrap_or(self.lock_timeout)
439 }
440
441 #[must_use]
444 pub fn distributed_lock_timeout(&self) -> Timeout {
445 self.distributed_lock_timeout.unwrap_or(self.lock_timeout)
446 }
447
448 #[must_use]
450 pub fn allow_timed_out_factory_background_completion(&self) -> bool {
451 self.allow_timed_out_factory_background_completion
452 }
453
454 #[must_use]
456 pub fn skip_memory_read(&self) -> bool {
457 self.skip_memory_read
458 }
459
460 #[must_use]
462 pub fn skip_memory_write(&self) -> bool {
463 self.skip_memory_write
464 }
465
466 #[must_use]
468 pub fn allow_stale_on_read_only(&self) -> bool {
469 self.allow_stale_on_read_only
470 }
471
472 #[must_use]
474 pub fn priority(&self) -> Priority {
475 self.priority
476 }
477
478 #[must_use]
480 pub fn skip_backplane_notifications(&self) -> bool {
481 self.skip_backplane_notifications
482 }
483
484 #[must_use]
486 pub fn allow_background_backplane_operations(&self) -> bool {
487 self.allow_background_backplane_operations
488 }
489
490 #[must_use]
492 pub fn skip_distributed_read(&self) -> bool {
493 self.skip_distributed_read
494 }
495
496 #[must_use]
498 pub fn skip_distributed_read_when_stale(&self) -> bool {
499 self.skip_distributed_read_when_stale
500 }
501
502 #[must_use]
504 pub fn skip_distributed_locker(&self) -> bool {
505 self.skip_distributed_locker
506 }
507
508 #[must_use]
510 pub fn with_skip_distributed_locker(mut self, skip: bool) -> Self {
511 self.skip_distributed_locker = skip;
512 self
513 }
514
515 #[must_use]
522 pub fn enable_auto_clone(&self) -> bool {
523 self.enable_auto_clone
524 }
525
526 #[must_use]
528 pub fn with_enable_auto_clone(mut self, enable: bool) -> Self {
529 self.enable_auto_clone = enable;
530 self
531 }
532
533 #[must_use]
535 pub fn distributed_soft_timeout(&self) -> Timeout {
536 self.distributed_soft_timeout
537 }
538
539 #[must_use]
541 pub fn distributed_hard_timeout(&self) -> Timeout {
542 self.distributed_hard_timeout
543 }
544
545 #[must_use]
547 pub fn skip_distributed_write(&self) -> bool {
548 self.skip_distributed_write
549 }
550
551 #[must_use]
553 pub fn allow_background_distributed_operations(&self) -> bool {
554 self.allow_background_distributed_operations
555 }
556
557 #[must_use]
559 pub fn rethrow_distributed_exceptions(&self) -> bool {
560 self.rethrow_distributed_exceptions
561 }
562
563 #[must_use]
566 pub fn rethrow_serialization_exceptions(&self) -> bool {
567 self.rethrow_serialization_exceptions
568 }
569
570 #[must_use]
572 pub fn rethrow_backplane_exceptions(&self) -> bool {
573 self.rethrow_backplane_exceptions
574 }
575
576 #[must_use]
580 pub fn resolved_memory_duration(&self) -> Duration {
581 self.memory_duration.unwrap_or(self.duration)
582 }
583
584 #[must_use]
586 pub fn resolved_distributed_duration(&self) -> Duration {
587 self.distributed_duration.unwrap_or(self.duration)
588 }
589
590 #[must_use]
592 pub fn resolved_distributed_fail_safe_max_duration(&self) -> Duration {
593 self.distributed_fail_safe_max_duration
594 .unwrap_or(self.fail_safe_max_duration)
595 }
596
597 #[must_use]
604 pub fn physical_ttl(&self) -> Duration {
605 let logical = self.resolved_memory_duration();
606 if self.is_fail_safe_enabled {
607 logical.max(self.fail_safe_max_duration)
608 } else {
609 logical
610 }
611 }
612
613 #[must_use]
617 pub fn distributed_physical_ttl(&self) -> Duration {
618 let logical = self.resolved_distributed_duration();
619 if self.is_fail_safe_enabled {
620 logical.max(self.resolved_distributed_fail_safe_max_duration())
621 } else {
622 logical
623 }
624 }
625
626 #[must_use]
631 pub fn appropriate_factory_timeout(&self, has_fallback: bool) -> Timeout {
632 let mut selected = Timeout::Infinite;
633 if self.is_fail_safe_enabled && has_fallback && !self.factory_soft_timeout.is_infinite() {
634 selected = self.factory_soft_timeout;
635 }
636 selected.min(self.factory_hard_timeout)
637 }
638
639 #[must_use]
646 pub fn appropriate_distributed_timeout(&self, has_fallback: bool) -> Timeout {
647 let mut selected = Timeout::Infinite;
648 if self.is_fail_safe_enabled && has_fallback && !self.distributed_soft_timeout.is_infinite()
649 {
650 selected = self.distributed_soft_timeout;
651 }
652 selected.min(self.distributed_hard_timeout)
653 }
654
655 #[must_use]
658 pub fn logical_expiration(&self, created: Timestamp) -> Timestamp {
659 let base = created.saturating_add(self.resolved_memory_duration());
660 if self.jitter_max.is_zero() {
661 base
662 } else {
663 let max_ticks = duration_to_ticks(self.jitter_max);
664 let extra = if max_ticks > 0 {
665 fastrand::i64(0..=max_ticks)
666 } else {
667 0
668 };
669 base.saturating_add(crate::time::ticks_to_duration(extra))
670 }
671 }
672
673 #[must_use]
676 pub fn eager_refresh_at(&self, created: Timestamp) -> Option<Timestamp> {
677 let threshold = self.eager_refresh_threshold?;
678 let window = self.resolved_memory_duration();
679 let elapsed = window.mul_f32(threshold.fraction());
680 Some(created.saturating_add(elapsed))
681 }
682}
683
684#[cfg(test)]
685mod tests {
686 use super::*;
687
688 #[test]
689 fn eager_threshold_rejects_out_of_range() {
690 assert!(EagerThreshold::new(0.5).is_some());
691 assert!(EagerThreshold::new(0.0).is_none());
692 assert!(EagerThreshold::new(1.0).is_none());
693 assert!(EagerThreshold::new(-0.1).is_none());
694 assert!(EagerThreshold::new(1.5).is_none());
695 }
696
697 #[test]
698 fn physical_ttl_uses_max_not_sum() {
699 let opts = EntryOptions::new(Duration::from_secs(10)).with_fail_safe(
700 true,
701 Some(Duration::from_secs(100)),
702 None,
703 );
704 assert_eq!(opts.physical_ttl(), Duration::from_secs(100));
705
706 let opts2 = EntryOptions::new(Duration::from_secs(200)).with_fail_safe(
707 true,
708 Some(Duration::from_secs(100)),
709 None,
710 );
711 assert_eq!(opts2.physical_ttl(), Duration::from_secs(200));
713 }
714
715 #[test]
716 fn physical_ttl_without_fail_safe_is_logical_duration() {
717 let opts = EntryOptions::new(Duration::from_secs(10));
718 assert_eq!(opts.physical_ttl(), Duration::from_secs(10));
719 }
720
721 #[test]
722 fn soft_timeout_ignored_without_fallback() {
723 let opts = EntryOptions::new(Duration::from_secs(10)).with_factory_timeouts(
724 Timeout::After(Duration::from_millis(50)),
725 Timeout::Infinite,
726 true,
727 );
728 assert_eq!(opts.appropriate_factory_timeout(true), Timeout::Infinite);
730 }
731
732 #[test]
733 fn soft_timeout_applies_with_fail_safe_and_fallback() {
734 let opts = EntryOptions::new(Duration::from_secs(10))
735 .with_fail_safe(true, None, None)
736 .with_factory_timeouts(
737 Timeout::After(Duration::from_millis(50)),
738 Timeout::Infinite,
739 true,
740 );
741 assert_eq!(
742 opts.appropriate_factory_timeout(true),
743 Timeout::After(Duration::from_millis(50))
744 );
745 assert_eq!(opts.appropriate_factory_timeout(false), Timeout::Infinite);
747 }
748
749 #[test]
750 fn hard_timeout_wins_when_shorter() {
751 let opts = EntryOptions::new(Duration::from_secs(10))
752 .with_fail_safe(true, None, None)
753 .with_factory_timeouts(
754 Timeout::After(Duration::from_millis(50)),
755 Timeout::After(Duration::from_millis(20)),
756 true,
757 );
758 assert_eq!(
759 opts.appropriate_factory_timeout(true),
760 Timeout::After(Duration::from_millis(20))
761 );
762 }
763
764 #[test]
765 fn distributed_soft_timeout_applies_only_with_fail_safe_and_fallback() {
766 let opts = EntryOptions::new(Duration::from_secs(10))
767 .with_fail_safe(true, None, None)
768 .with_distributed_timeouts(
769 Timeout::After(Duration::from_millis(50)),
770 Timeout::Infinite,
771 );
772 assert_eq!(
773 opts.appropriate_distributed_timeout(true),
774 Timeout::After(Duration::from_millis(50))
775 );
776 assert_eq!(
778 opts.appropriate_distributed_timeout(false),
779 Timeout::Infinite
780 );
781
782 let no_fs = EntryOptions::new(Duration::from_secs(10)).with_distributed_timeouts(
784 Timeout::After(Duration::from_millis(50)),
785 Timeout::Infinite,
786 );
787 assert_eq!(
788 no_fs.appropriate_distributed_timeout(true),
789 Timeout::Infinite
790 );
791 }
792
793 #[test]
794 fn distributed_hard_timeout_wins_when_shorter() {
795 let opts = EntryOptions::new(Duration::from_secs(10))
796 .with_fail_safe(true, None, None)
797 .with_distributed_timeouts(
798 Timeout::After(Duration::from_millis(50)),
799 Timeout::After(Duration::from_millis(20)),
800 );
801 assert_eq!(
802 opts.appropriate_distributed_timeout(true),
803 Timeout::After(Duration::from_millis(20))
804 );
805 }
806
807 #[test]
808 fn lock_timeouts_inherit_general_by_default() {
809 let opts = EntryOptions::new(Duration::from_secs(10))
810 .with_lock_timeout(Timeout::After(Duration::from_millis(100)));
811 assert_eq!(
812 opts.memory_lock_timeout(),
813 Timeout::After(Duration::from_millis(100))
814 );
815 assert_eq!(
816 opts.distributed_lock_timeout(),
817 Timeout::After(Duration::from_millis(100))
818 );
819 }
820
821 #[test]
822 fn specific_lock_timeouts_override_general() {
823 let opts = EntryOptions::new(Duration::from_secs(10))
824 .with_lock_timeout(Timeout::After(Duration::from_millis(100)))
825 .with_memory_lock_timeout(Timeout::After(Duration::from_millis(20)))
826 .with_distributed_lock_timeout(Timeout::Infinite);
827 assert_eq!(
828 opts.memory_lock_timeout(),
829 Timeout::After(Duration::from_millis(20))
830 );
831 assert_eq!(opts.distributed_lock_timeout(), Timeout::Infinite);
832 assert_eq!(
834 opts.lock_timeout(),
835 Timeout::After(Duration::from_millis(100))
836 );
837 }
838
839 #[test]
840 fn jitter_widens_logical_expiration_within_bound() {
841 let base_dur = Duration::from_secs(100);
842 let jitter = Duration::from_secs(10);
843 let created = Timestamp::from_ticks(0);
844
845 let plain = EntryOptions::new(base_dur);
847 let base_exp = plain.logical_expiration(created);
848 assert_eq!(plain.logical_expiration(created).ticks(), base_exp.ticks());
849
850 let jittered = EntryOptions::new(base_dur).with_jitter_max(jitter);
852 let upper = base_exp.saturating_add(jitter).ticks();
853 for _ in 0..200 {
854 let exp = jittered.logical_expiration(created).ticks();
855 assert!(exp >= base_exp.ticks(), "jitter never shortens expiration");
856 assert!(exp <= upper, "jitter never exceeds jitter_max");
857 }
858 }
859
860 #[test]
861 fn rethrow_and_background_setters_flip_the_flags() {
862 let o = EntryOptions::new(Duration::from_secs(1))
863 .with_rethrow_distributed_exceptions(true)
864 .with_rethrow_serialization_exceptions(false)
865 .with_allow_background_backplane_operations(false)
866 .with_distributed_fail_safe_max_duration(Duration::from_secs(7));
867 assert!(o.rethrow_distributed_exceptions());
868 assert!(!o.rethrow_serialization_exceptions());
869 assert!(!o.allow_background_backplane_operations());
870 assert_eq!(
871 o.resolved_distributed_fail_safe_max_duration(),
872 Duration::from_secs(7)
873 );
874 }
875}