rushdb_storage/
parallel.rs1use std::sync::atomic::{AtomicUsize, Ordering};
13
14static FREE: AtomicUsize = AtomicUsize::new(0);
16static BUDGET: AtomicUsize = AtomicUsize::new(0);
18
19pub fn set_threads(threads: usize) {
23 let helpers = threads.saturating_sub(1);
24 let previous = BUDGET.swap(helpers, Ordering::SeqCst);
25 if helpers >= previous {
26 FREE.fetch_add(helpers - previous, Ordering::SeqCst);
27 } else {
28 let _ = FREE.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |free| {
29 Some(free.saturating_sub(previous - helpers))
30 });
31 }
32}
33
34#[must_use]
36pub fn budget() -> usize {
37 BUDGET.load(Ordering::SeqCst)
38}
39
40struct Lease(usize);
42
43impl Lease {
44 fn take(wanted: usize) -> Self {
45 let mut taken = 0;
46 let _ = FREE.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |free| {
47 taken = free.min(wanted);
48 Some(free - taken)
49 });
50 Self(taken)
51 }
52}
53
54impl Drop for Lease {
55 fn drop(&mut self) {
56 FREE.fetch_add(self.0, Ordering::SeqCst);
57 }
58}
59
60pub fn run_parts<T, E, F>(parts: usize, part: F) -> Result<Vec<T>, E>
72where
73 T: Send,
74 E: Send,
75 F: Fn(usize) -> Result<T, E> + Sync,
76{
77 if parts <= 1 {
78 return (0..parts).map(&part).collect();
79 }
80 let lease = Lease::take(parts - 1);
81 if lease.0 == 0 {
82 return (0..parts).map(&part).collect();
83 }
84 let next = AtomicUsize::new(0);
85 let workers = lease.0 + 1;
86 let mut slots: Vec<Option<Result<T, E>>> = (0..parts).map(|_| None).collect();
87 let results = std::sync::Mutex::new(&mut slots);
88 let work = || loop {
89 let index = next.fetch_add(1, Ordering::Relaxed);
90 if index >= parts {
91 break;
92 }
93 let outcome = part(index);
94 let mut guard = results
95 .lock()
96 .unwrap_or_else(std::sync::PoisonError::into_inner);
97 guard[index] = Some(outcome);
98 };
99 let helper_reads = std::thread::scope(|scope| {
103 let mut helpers = Vec::with_capacity(workers - 1);
105 for _ in 1..workers {
106 helpers.push(scope.spawn(|| {
107 work();
108 crate::read_accounting::take_local()
109 }));
110 }
111 work();
112 helpers
113 .into_iter()
114 .map(|helper| {
115 helper
116 .join()
117 .unwrap_or_else(|panic| std::panic::resume_unwind(panic))
118 })
119 .collect::<Vec<_>>()
120 });
121 for reads in helper_reads {
122 crate::read_accounting::add_local(reads);
123 }
124 drop(lease);
125 slots
126 .into_iter()
127 .map(|slot| slot.expect("every part ran"))
128 .collect()
129}
130
131#[must_use]
134pub fn split(len: u64, parts: usize) -> Vec<std::ops::Range<u64>> {
135 let parts = u64::try_from(parts.max(1)).unwrap_or(1).min(len.max(1));
136 let size = len.div_ceil(parts);
137 (0..parts)
138 .map(|i| (i * size)..((i + 1) * size).min(len))
139 .filter(|range| !range.is_empty())
140 .collect()
141}
142
143#[must_use]
146pub fn parts_for(units: u64, min_units: u64) -> usize {
147 let by_size = usize::try_from(units / min_units.max(1)).unwrap_or(usize::MAX);
148 by_size.clamp(1, budget() + 1)
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 static BUDGET_TESTS: std::sync::Mutex<()> = std::sync::Mutex::new(());
158
159 fn exclusive() -> std::sync::MutexGuard<'static, ()> {
160 BUDGET_TESTS
161 .lock()
162 .unwrap_or_else(std::sync::PoisonError::into_inner)
163 }
164
165 #[test]
166 fn parts_return_in_order_and_errors_by_lowest_part() {
167 let _serial = exclusive();
168 set_threads(4);
169 let out: Result<Vec<usize>, ()> = run_parts(16, |i| Ok(i * 2));
170 assert_eq!(out.unwrap(), (0..16).map(|i| i * 2).collect::<Vec<_>>());
171 let failed: Result<Vec<usize>, usize> =
172 run_parts(16, |i| if i % 5 == 3 { Err(i) } else { Ok(i) });
173 assert_eq!(failed, Err(3));
174 assert_eq!(FREE.load(Ordering::SeqCst), budget());
176 }
177
178 #[test]
179 fn reads_on_helper_threads_count_for_the_caller() {
180 let _serial = exclusive();
181 set_threads(4);
182 crate::read_accounting::enable(true);
183 let _ = crate::read_accounting::take_local();
184 let parts: Result<Vec<()>, ()> = run_parts(8, |part| {
185 crate::read_accounting::record("get", &[0x01, 0, 0, 0, 1, u8::try_from(part).unwrap()]);
186 Ok(())
187 });
188 parts.unwrap();
189 let counted: u64 = crate::read_accounting::take_local().values().sum();
190 assert_eq!(counted, 8, "every part's read is the caller's");
191 }
192
193 #[test]
194 fn split_covers_the_range_without_gaps() {
195 let ranges = split(10, 3);
196 assert_eq!(ranges, vec![0..4, 4..8, 8..10]);
197 assert_eq!(split(2, 8), vec![0..1, 1..2]);
198 assert!(split(0, 4).is_empty());
199 }
200}