nectar_primitives/file/
splitter_parallel.rs1use std::marker::PhantomData;
4
5use rayon::prelude::*;
6
7use crate::bmt::DEFAULT_BODY_SIZE;
8use crate::chunk::AnyChunk;
9
10use super::constants::{LEVEL_LIMIT, compute_spans_inline};
11use super::error::{FileError, Result};
12use super::mode::{PlainMode, SplitMode};
13use super::read_at::ReadAt;
14use super::tree::TreeParams;
15
16#[cfg(feature = "encryption")]
17use super::mode::EncryptedMode;
18
19pub struct GenericParallelSplitter<M: SplitMode, const BODY_SIZE: usize = DEFAULT_BODY_SIZE> {
24 _mode: PhantomData<M>,
25}
26
27pub type ParallelSplitter<const BODY_SIZE: usize = DEFAULT_BODY_SIZE> =
29 GenericParallelSplitter<PlainMode, BODY_SIZE>;
30
31#[cfg(feature = "encryption")]
33pub type EncryptedParallelSplitter<const BODY_SIZE: usize = DEFAULT_BODY_SIZE> =
34 GenericParallelSplitter<EncryptedMode, BODY_SIZE>;
35
36impl<M, const BODY_SIZE: usize> std::fmt::Debug for GenericParallelSplitter<M, BODY_SIZE>
37where
38 M: SplitMode,
39{
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 f.debug_struct("GenericParallelSplitter")
42 .finish_non_exhaustive()
43 }
44}
45
46impl<M, const BODY_SIZE: usize> GenericParallelSplitter<M, BODY_SIZE>
47where
48 M: SplitMode + Send + Sync,
49{
50 pub fn split_into<R, F>(source: &R, sink: F) -> Result<M::RootRef>
55 where
56 R: ReadAt + Sync,
57 F: Fn(AnyChunk<BODY_SIZE>) + Sync,
58 {
59 const { super::constants::assert_valid_body_size::<BODY_SIZE>() };
60 let size = source.len();
61 let tree = TreeParams::<BODY_SIZE>::new(size);
62
63 if size == 0 {
64 let (chunk, root) = M::empty_chunk::<BODY_SIZE>()?;
65 sink(chunk.into());
66 return Ok(root);
67 }
68
69 let spans = compute_spans_inline(BODY_SIZE / M::REF_SIZE);
70
71 let level0_refs = Self::create_data_chunks(source, &tree, &sink)?;
73
74 Self::build_intermediate_levels(level0_refs, size, &spans, &sink)
76 }
77
78 pub fn split_to_vec<R: ReadAt + Sync>(
83 source: &R,
84 ) -> Result<(M::RootRef, Vec<AnyChunk<BODY_SIZE>>)> {
85 let chunks = std::sync::Mutex::new(Vec::new());
86 let root = Self::split_into(source, |chunk| chunks.lock().unwrap().push(chunk))?;
87 Ok((root, chunks.into_inner().unwrap()))
88 }
89
90 fn create_data_chunks<R, F>(
91 source: &R,
92 tree: &TreeParams<BODY_SIZE>,
93 sink: &F,
94 ) -> Result<Vec<M::RefBytes>>
95 where
96 R: ReadAt + Sync,
97 F: Fn(AnyChunk<BODY_SIZE>) + Sync,
98 {
99 let data_chunks = tree.data_chunks();
100 let size = tree.size();
101
102 let results: Vec<Result<M::RefBytes>> = (0..data_chunks)
103 .into_par_iter()
104 .map(|i| {
105 let offset = i * BODY_SIZE as u64;
106 let chunk_size = ((size - offset) as usize).min(BODY_SIZE);
107
108 let mut buf = vec![0u8; chunk_size];
109 source
110 .read_at(offset, &mut buf)
111 .map_err(|e| FileError::Store(Box::new(e)))?;
112
113 let span = if i + 1 == data_chunks {
114 size - offset
115 } else {
116 BODY_SIZE as u64
117 };
118
119 let chunk_bytes = super::helpers::build_intermediate_payload(span, &buf);
120
121 let (chunk, ref_bytes) = M::prepare_chunk::<BODY_SIZE>(chunk_bytes)?;
122 sink(chunk.into());
123 Ok(ref_bytes)
124 })
125 .collect();
126
127 results.into_iter().collect()
128 }
129
130 fn build_intermediate_levels<F>(
131 mut refs: Vec<M::RefBytes>,
132 total_size: u64,
133 spans: &[u64; LEVEL_LIMIT],
134 sink: &F,
135 ) -> Result<M::RootRef>
136 where
137 F: Fn(AnyChunk<BODY_SIZE>) + Sync,
138 {
139 let mut level = 1;
140
141 while refs.len() > 1 {
142 refs = Self::build_level(&refs, level, total_size, spans, sink)?;
143 level += 1;
144 }
145
146 M::extract_root(refs[0].as_ref())
148 }
149
150 fn build_level<F>(
151 refs: &[M::RefBytes],
152 level: usize,
153 total_size: u64,
154 spans: &[u64; LEVEL_LIMIT],
155 sink: &F,
156 ) -> Result<Vec<M::RefBytes>>
157 where
158 F: Fn(AnyChunk<BODY_SIZE>) + Sync,
159 {
160 let refs_per_chunk = M::refs_per_chunk(BODY_SIZE);
161 let chunks_at_level = refs.len().div_ceil(refs_per_chunk);
162 let max_span = spans[level] * BODY_SIZE as u64;
163
164 let results: Vec<Result<M::RefBytes>> = (0..chunks_at_level)
165 .into_par_iter()
166 .map(|i| {
167 let start = i * refs_per_chunk;
168 let end = (start + refs_per_chunk).min(refs.len());
169 let child_refs = &refs[start..end];
170
171 if child_refs.len() == 1 {
173 return Ok(child_refs[0].clone());
174 }
175
176 let span = if i + 1 == chunks_at_level {
177 total_size.saturating_sub(i as u64 * max_span)
178 } else {
179 max_span
180 };
181
182 let ref_data: Vec<u8> = child_refs
183 .iter()
184 .flat_map(|r| r.as_ref())
185 .copied()
186 .collect();
187 let chunk_bytes = super::helpers::build_intermediate_payload(span, &ref_data);
188
189 let (chunk, ref_bytes) = M::prepare_chunk::<BODY_SIZE>(chunk_bytes)?;
190 sink(chunk.into());
191 Ok(ref_bytes)
192 })
193 .collect();
194
195 results.into_iter().collect()
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use crate::file::join;
203 use crate::store::MemoryStore;
204
205 fn split_and_store(
206 data: &[u8],
207 ) -> (crate::chunk::ChunkAddress, MemoryStore<DEFAULT_BODY_SIZE>) {
208 let (root, chunks) = ParallelSplitter::<DEFAULT_BODY_SIZE>::split_to_vec(&data).unwrap();
209 (root, MemoryStore::from_chunks(chunks))
210 }
211
212 generate_plain_splitter_tests!(split_and_store);
213
214 #[test]
215 fn test_parallel_splitter_varying_data() {
216 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * 5 + 123)
217 .map(|i| (i % 256) as u8)
218 .collect();
219
220 let (root, store) = split_and_store(&data);
221
222 let (seq_root, _) = crate::file::split::<DEFAULT_BODY_SIZE>(&data).unwrap();
223 assert_eq!(root, seq_root);
224
225 let recovered = futures::executor::block_on(join(&store, root)).unwrap();
226 assert_eq!(recovered, data);
227 }
228
229 #[test]
230 fn test_split_into_lock_free_sink() {
231 use std::sync::atomic::{AtomicUsize, Ordering};
232
233 let data = vec![0xAB; DEFAULT_BODY_SIZE + 1];
234 let count = AtomicUsize::new(0);
235 let root = ParallelSplitter::<DEFAULT_BODY_SIZE>::split_into(&data.as_slice(), |_chunk| {
236 count.fetch_add(1, Ordering::Relaxed);
237 })
238 .unwrap();
239 assert!(!root.is_zero());
240 assert_eq!(count.load(Ordering::Relaxed), 3);
241 }
242
243 #[cfg(feature = "encryption")]
244 mod encrypted {
245 use super::*;
246 use crate::file::{EncryptedParallelSplitter, split_encrypted};
247 use crate::store::MemoryStore;
248
249 fn encrypted_split_and_store(
250 data: &[u8],
251 ) -> (
252 crate::chunk::encryption::EncryptedChunkRef,
253 MemoryStore<DEFAULT_BODY_SIZE>,
254 ) {
255 let (root_ref, chunks) =
256 EncryptedParallelSplitter::<DEFAULT_BODY_SIZE>::split_to_vec(&data).unwrap();
257 (root_ref, MemoryStore::from_chunks(chunks))
258 }
259
260 generate_encrypted_splitter_tests!(encrypted_split_and_store);
261
262 #[test]
263 fn test_encrypted_parallel_matches_sequential() {
264 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * 5 + 123)
265 .map(|i| (i % 256) as u8)
266 .collect();
267
268 let (par_ref, par_store) = encrypted_split_and_store(&data);
269 let (seq_ref, seq_store) = split_encrypted::<DEFAULT_BODY_SIZE>(&data).unwrap();
270
271 assert_eq!(par_store.len(), seq_store.len());
272
273 let par_recovered = futures::executor::block_on(join(&par_store, par_ref)).unwrap();
274 assert_eq!(par_recovered, data);
275
276 let seq_recovered = futures::executor::block_on(join(&seq_store, seq_ref)).unwrap();
277 assert_eq!(seq_recovered, data);
278 }
279
280 #[test]
281 fn test_encrypted_parallel_nondeterministic() {
282 let data = b"test determinism";
283 let (ref1, _) = encrypted_split_and_store(data);
284 let (ref2, _) = encrypted_split_and_store(data);
285
286 assert_ne!(ref1.address(), ref2.address());
288 }
289 }
290}