1use std::{
2 error::Error,
3 sync::{atomic::AtomicBool, Arc},
4};
5
6use bytemuck::Pod;
7use crossbeam_channel::{bounded, unbounded, Receiver, Select, Sender};
8use log::debug;
9
10use crate::{
11 chunk::{FileChunk, FileChunkDir, MemChunk},
12 merge::merge_chunks_with_binary_heap,
13 Result,
14};
15
16pub struct SortConfig {
17 pub(crate) max_chunk_bytes: usize,
18 pub(crate) concurrency: usize,
19 pub(crate) merge_k: usize,
20 pub(crate) canceled: Arc<AtomicBool>,
21}
22
23impl Default for SortConfig {
24 fn default() -> Self {
25 Self {
26 max_chunk_bytes: 512 * 1024 * 1024,
27 concurrency: 8,
28 merge_k: 16,
29 canceled: Arc::new(AtomicBool::new(false)),
30 }
31 }
32}
33
34impl SortConfig {
35 pub fn new() -> Self {
36 Default::default()
37 }
38
39 pub fn set_cancel_flag(self, canceled: Arc<AtomicBool>) -> Self {
40 Self { canceled, ..self }
41 }
42
43 pub fn get_cancel_flag(&self) -> Arc<AtomicBool> {
44 self.canceled.clone()
45 }
46
47 pub(crate) fn ensure_not_canceled<E>(&self) -> Result<(), E> {
48 if self.canceled.load(std::sync::atomic::Ordering::Relaxed) {
49 Err(crate::Error::Canceled)
50 } else {
51 Ok(())
52 }
53 }
54
55 pub fn max_chunk_bytes(self, max_chunk_bytes: usize) -> Self {
56 assert!(
57 max_chunk_bytes > 0,
58 "max_chunk_bytes must be greater than 0"
59 );
60 Self {
61 max_chunk_bytes,
62 ..self
63 }
64 }
65
66 pub fn concurrency(self, concurrency: usize) -> Self {
67 assert!(concurrency > 0, "concurrency must be greater than 0");
68 Self {
69 concurrency,
70 ..self
71 }
72 }
73
74 pub fn merge_k(self, merge_k: usize) -> Self {
75 assert!(merge_k >= 2, "merge_k must not be less than 2");
76 Self { merge_k, ..self }
77 }
78}
79
80pub fn sort<K, E>(
81 source: impl Iterator<Item = std::result::Result<(K, Vec<u8>), E>> + Send,
82 config: SortConfig,
83) -> SortedIter<K, E>
84where
85 K: Ord + Pod + Copy + Send + Sync,
86 E: Error + Send + 'static,
87{
88 let (output_tx, output_rx) = bounded(config.concurrency * 16);
89 let chunk_dir = match FileChunkDir::<K>::new() {
90 Ok(chunk_dir) => Arc::new(chunk_dir),
91 Err(e) => {
92 let _ = output_tx.send(Err(e));
93 return SortedIter::new(output_rx, None);
94 }
95 };
96 let (file_chunk_tx, file_chunk_rx) = unbounded();
97 let chunk_dir = chunk_dir.clone();
98
99 {
100 let chunk_dir = chunk_dir.clone();
101 rayon::ThreadPoolBuilder::new()
102 .num_threads(config.concurrency + 1)
103 .build()
104 .unwrap()
105 .install(|| {
106 start_sorting_stage(&config, source, chunk_dir.clone(), file_chunk_tx);
107 start_merging_stage(&config, file_chunk_rx, chunk_dir.clone(), output_tx);
108 });
109 }
110
111 SortedIter::new(output_rx, Some(chunk_dir))
112}
113
114pub struct SortedIter<K: Pod, E> {
115 output_rx: Receiver<Result<(K, Vec<u8>), E>>,
116 done: bool,
117 #[allow(dead_code)]
118 chunk_dir: Option<Arc<FileChunkDir<K>>>,
119}
120
121impl<K: Pod, E> SortedIter<K, E> {
122 fn new(
123 output_rx: Receiver<Result<(K, Vec<u8>), E>>,
124 chunk_dir: Option<Arc<FileChunkDir<K>>>,
125 ) -> Self {
126 SortedIter {
127 output_rx,
128 chunk_dir,
129 done: false,
130 }
131 }
132}
133
134impl<K: Pod, E> Iterator for SortedIter<K, E> {
135 type Item = Result<(K, Vec<u8>), E>;
136
137 fn next(&mut self) -> Option<Self::Item> {
138 if self.done {
139 return None;
140 }
141 match self.output_rx.recv() {
142 Ok(Ok(v)) => Some(Ok(v)),
143 Ok(Err(e)) => {
144 self.done = true;
145 Some(Err(e))
146 }
147 Err(_) => {
148 self.done = true;
149 None
150 }
151 }
152 }
153}
154
155fn mem_to_file_chunk<K: Pod + Ord, E>(
156 buffer: Vec<(K, Vec<u8>)>,
157 chunk_dir: Arc<FileChunkDir<K>>,
158) -> Result<FileChunk<K>, E> {
159 let mem_chunk = MemChunk::from_unsorted(buffer);
160 let mut file_chunk = chunk_dir.add_chunk()?;
161 mem_chunk.write_to_file(&mut file_chunk)?;
162 Ok(file_chunk.finalize())
163}
164
165fn start_sorting_stage<K, E>(
166 config: &SortConfig,
167 source: impl Iterator<Item = std::result::Result<(K, Vec<u8>), E>> + Send,
168 chunk_dir: Arc<FileChunkDir<K>>,
169 chunk_tx: Sender<Result<FileChunk<K>, E>>,
170) where
171 K: Ord + Pod + Copy + Send + Sync,
172 E: Send + 'static,
173{
174 debug!("Sorting stage started.");
175
176 let item_header_size = std::mem::size_of::<Vec<u8>>();
177 let mut chunk_size = 0;
178
179 let mut buffer = Vec::new();
180
181 for res in source {
182 match res {
183 Ok((key, value)) => {
184 let item_size = item_header_size + value.len();
185 if chunk_size + item_size >= config.max_chunk_bytes {
186 let buffer = std::mem::take(&mut buffer);
187 let chunk_dir = chunk_dir.clone();
188 let chunk_tx = chunk_tx.clone();
189 if let Err(e) = config.ensure_not_canceled() {
190 let _ = chunk_tx.send(Err(e));
191 return;
192 }
193 rayon::spawn(move || {
194 let _ = chunk_tx.send(mem_to_file_chunk(buffer, chunk_dir));
195 });
196 chunk_size = 0;
197 }
198 chunk_size += item_size;
199 buffer.push((key, value));
200 }
201 Err(e) => {
202 let _ = chunk_tx.send(Err(crate::Error::Source(e)));
203 }
204 }
205 }
206
207 if !buffer.is_empty() {
209 if let Err(e) = config.ensure_not_canceled() {
210 let _ = chunk_tx.send(Err(e));
211 return;
212 }
213 rayon::spawn(move || {
214 let _ = chunk_tx.send(mem_to_file_chunk(buffer, chunk_dir));
215 });
216 }
217}
218
219fn start_merging_stage<K, E>(
220 config: &SortConfig,
221 chunk_rx: Receiver<Result<FileChunk<K>, E>>,
222 chunk_dir: Arc<FileChunkDir<K>>,
223 output_tx: Sender<Result<(K, Vec<u8>), E>>,
224) where
225 K: Ord + Pod + Copy + Send + Sync,
226 E: Send + 'static,
227{
228 debug!("Merging stage started.");
229
230 let (merged_tx, merged_rx) = unbounded::<Result<FileChunk<K>, E>>();
231 let mut pending = Vec::new();
232 let mut source_finished = false;
233 let mut num_running_merges = 0;
234
235 let mut recv_select = Select::new();
236 recv_select.recv(&chunk_rx); recv_select.recv(&merged_rx); loop {
240 let idx = recv_select.ready();
241 match idx {
242 0 => match chunk_rx.try_recv() {
244 Ok(Ok(chunk)) => {
245 debug!("Received chunk: items={}", chunk.len());
246 pending.push(chunk)
247 }
248 Ok(Err(e)) => {
249 let _ = output_tx.send(Err(e));
250 break;
251 }
252 Err(_) => {
253 debug!("All chunks received from the sorting stage");
254 source_finished = true;
255 recv_select.remove(0);
256 }
257 },
258 1 => match merged_rx.try_recv() {
260 Ok(Ok(chunk)) => {
261 debug!("Received merged chunk: items={}", chunk.len());
262 num_running_merges -= 1;
263 pending.push(chunk)
264 }
265 Ok(Err(e)) => {
266 let _ = output_tx.send(Err(e));
267 break;
268 }
269 Err(_) => {
270 panic!("merged_rx should not be closed at this point")
271 }
272 },
273 _ => unreachable!(),
274 }
275
276 if let Err(e) = config.ensure_not_canceled() {
277 let _ = output_tx.send(Err(e));
278 }
279
280 let total_chunks = pending.len() + num_running_merges;
282 let num_merge = if source_finished {
283 if pending.len() > config.merge_k {
284 (total_chunks - config.merge_k + 1).min(config.merge_k)
285 } else if num_running_merges == 0 {
286 break;
287 } else {
288 continue;
289 }
290 } else if total_chunks >= config.merge_k * 2 - 1 {
291 if pending.len() >= config.merge_k {
292 config.merge_k
293 } else {
294 continue;
295 }
296 } else {
297 continue;
298 };
299
300 pending.sort_by_key(|chunk| chunk.len());
301 let remaining = pending.split_off(num_merge.min(pending.len()));
302 let merging = std::mem::replace(&mut pending, remaining);
303
304 let merged_tx = merged_tx.clone();
305 let mut chunk_writer = match chunk_dir.add_chunk() {
306 Ok(chunk_writer) => chunk_writer,
307 Err(e) => {
308 let _ = output_tx.send(Err(e));
309 break;
310 }
311 };
312
313 debug!("Start merging {} chunks", merging.len());
315 num_running_merges += 1;
316 let canceled = config.canceled.clone();
317 rayon::spawn(move || {
318 match merge_chunks_with_binary_heap(canceled, merging, |(key, value)| {
319 chunk_writer.push(&key, &value)
320 }) {
321 Ok(()) => {
322 let _ = merged_tx.send(Ok(chunk_writer.finalize()));
323 }
324 Err(e) => {
325 let _ = merged_tx.send(Err(e));
326 }
327 }
328 });
329 }
330
331 let canceled = config.canceled.clone();
332 if canceled.load(std::sync::atomic::Ordering::Relaxed) {
333 return;
334 }
335
336 debug!("Start iteration by merging {} chunks", pending.len());
337 rayon::spawn(move || {
338 if let Err(e) = merge_chunks_with_binary_heap(canceled, pending, |(key, value)| {
339 let _ = output_tx.send(Ok((key, value)));
340 Ok(())
341 }) {
342 let _ = merged_tx.send(Err(e));
343 }
344 drop(chunk_dir);
345 });
346}