crawlkit_engine/
determinism.rs1use std::collections::hash_map::DefaultHasher;
2use std::hash::{Hash, Hasher};
3use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
4use std::sync::Arc;
5
6pub struct DeterminismController {
23 seed: u64,
25 enforced: Arc<AtomicBool>,
27 counter: AtomicU64,
29}
30
31impl DeterminismController {
32 #[must_use]
34 pub fn new(seed: u64) -> Self {
35 Self {
36 seed,
37 enforced: Arc::new(AtomicBool::new(true)),
38 counter: AtomicU64::new(0),
39 }
40 }
41
42 #[must_use]
44 pub fn with_default_seed() -> Self {
45 Self::new(0)
46 }
47
48 #[must_use]
50 pub fn seed(&self) -> u64 {
51 self.seed
52 }
53
54 #[must_use]
56 pub fn derive_seed(&self, context: &str) -> u64 {
57 let counter = self.counter.fetch_add(1, Ordering::AcqRel);
58 let mut hasher = DefaultHasher::new();
59 self.seed.hash(&mut hasher);
60 context.hash(&mut hasher);
61 counter.hash(&mut hasher);
62 hasher.finish()
63 }
64
65 #[must_use]
67 pub fn is_enforced(&self) -> bool {
68 self.enforced.load(Ordering::Acquire)
69 }
70
71 pub fn set_enforced(&self, enforced: bool) {
73 self.enforced.store(enforced, Ordering::Release);
74 }
75
76 #[must_use]
78 pub fn content_hash(content: &str) -> u64 {
79 let mut hasher = DefaultHasher::new();
80 content.hash(&mut hasher);
81 hasher.finish()
82 }
83
84 #[must_use]
86 pub fn url_hash(url: &str) -> u64 {
87 let mut hasher = DefaultHasher::new();
88 url.hash(&mut hasher);
89 hasher.finish()
90 }
91}
92
93impl Default for DeterminismController {
94 fn default() -> Self {
95 Self::with_default_seed()
96 }
97}
98
99pub fn deterministic_sort<T>(items: &mut [T], key_fn: impl Fn(&T) -> u64) {
101 items.sort_by_key(|a| key_fn(a));
102}
103
104#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn test_determinism_controller_seed() {
114 let ctrl = DeterminismController::new(42);
115 assert_eq!(ctrl.seed(), 42);
116 assert!(ctrl.is_enforced());
117 }
118
119 #[test]
120 fn test_determinism_controller_derive_seed() {
121 let ctrl = DeterminismController::new(42);
122 let seed1 = ctrl.derive_seed("context1");
123 let seed2 = ctrl.derive_seed("context2");
124 assert_ne!(seed1, seed2);
125 }
126
127 #[test]
128 fn test_determinism_controller_same_seed() {
129 let ctrl1 = DeterminismController::new(42);
130 let ctrl2 = DeterminismController::new(42);
131 assert_eq!(ctrl1.seed(), ctrl2.seed());
132 }
133
134 #[test]
135 fn test_content_hash_deterministic() {
136 let hash1 = DeterminismController::content_hash("hello");
137 let hash2 = DeterminismController::content_hash("hello");
138 assert_eq!(hash1, hash2);
139 }
140
141 #[test]
142 fn test_content_hash_different() {
143 let hash1 = DeterminismController::content_hash("hello");
144 let hash2 = DeterminismController::content_hash("world");
145 assert_ne!(hash1, hash2);
146 }
147}