1use crate::progress::TableProgress;
7use futures::StreamExt;
8use log::debug;
9use std::collections::VecDeque;
10use std::io;
11use std::sync::{Arc, Mutex};
12use tokio::task::JoinSet;
13
14pub trait Source: Send {
19 fn create(self, buffer: Vec<u8>) -> Vec<u8>;
21
22 fn header(&self, buffer: Vec<u8>) -> Vec<u8>;
27}
28
29pub trait Sink: Send {
33 fn sink(&mut self, buffer: &[u8]) -> Result<(), io::Error>;
35
36 fn flush(self) -> Result<(), io::Error>;
38}
39
40pub async fn generate_in_chunks<G, I, S>(
53 sink: S,
54 sources: I,
55 num_threads: usize,
56) -> Result<(), io::Error>
57where
58 G: Source + 'static,
59 I: Iterator<Item = G>,
60 S: Sink + 'static,
61{
62 let progress = Default::default();
63 generate_in_chunks_impl(sink, sources, num_threads, progress).await
64}
65
66pub(crate) async fn generate_in_chunks_with_progress<G, I, S>(
67 sink: S,
68 sources: I,
69 num_threads: usize,
70 progress: TableProgress,
71) -> Result<(), io::Error>
72where
73 G: Source + 'static,
74 I: Iterator<Item = G>,
75 S: Sink + 'static,
76{
77 generate_in_chunks_impl(sink, sources, num_threads, progress).await
78}
79
80async fn generate_in_chunks_impl<G, I, S>(
81 mut sink: S,
82 sources: I,
83 num_threads: usize,
84 progress: TableProgress,
85) -> Result<(), io::Error>
86where
87 G: Source + 'static,
88 I: Iterator<Item = G>,
89 S: Sink + 'static,
90{
91 let recycler = BufferRecycler::new();
92 let mut sources = sources.peekable();
93
94 debug!("Using {num_threads} threads");
96
97 let Some(first) = sources.peek() else {
98 return Ok(()); };
100 let header = first.header(Vec::new());
101
102 let sources_and_recyclers = sources.map(|generator| (generator, recycler.clone()));
103
104 let (tx, mut rx) = tokio::sync::mpsc::channel(num_threads);
106
107 let mut stream = futures::stream::iter(sources_and_recyclers)
109 .map(async |(source, recycler)| {
111 let buffer = recycler.new_buffer(1024 * 1024 * 8);
112 let mut join_set = JoinSet::new();
114 join_set.spawn(async move { source.create(buffer) });
115 join_set
117 .join_next()
118 .await
119 .expect("had one item")
120 .expect("join_next join is infallible unless task panics")
121 })
122 .buffered(num_threads)
124 .map(async |buffer| {
125 if let Err(e) = tx.send(buffer).await {
131 debug!("Error sending buffer to writer: {e}");
132 }
133 });
134
135 let captured_recycler = recycler.clone();
138 let captured_progress = progress.clone();
139 let writer_task = tokio::task::spawn_blocking(move || {
140 sink.sink(&header)?;
142 while let Some(buffer) = rx.blocking_recv() {
143 sink.sink(&buffer)?;
144 captured_recycler.return_buffer(buffer);
145 captured_progress.increment_output_unit();
146 }
147 sink.flush()
149 });
150
151 while let Some(write_task) = stream.next().await {
153 if writer_task.is_finished() {
155 debug!("writer task is done early, stopping writer");
156 break;
157 }
158 write_task.await; }
160 drop(stream); drop(tx); debug!("waiting for writer task to complete");
165 writer_task.await.expect("writer task panicked")
166}
167
168#[derive(Debug, Clone)]
172struct BufferRecycler {
173 buffers: Arc<Mutex<VecDeque<Vec<u8>>>>,
174}
175
176impl BufferRecycler {
177 fn new() -> Self {
178 Self {
179 buffers: Arc::new(Mutex::new(VecDeque::new())),
180 }
181 }
182 fn new_buffer(&self, size: usize) -> Vec<u8> {
184 let mut buffers = self.buffers.lock().unwrap();
185 if let Some(mut buffer) = buffers.pop_front() {
186 buffer.clear();
187 if size > buffer.capacity() {
188 buffer.reserve(size - buffer.capacity());
189 }
190 buffer
191 } else {
192 Vec::with_capacity(size)
193 }
194 }
195
196 fn return_buffer(&self, buffer: Vec<u8>) {
197 let mut buffers = self.buffers.lock().unwrap();
198 buffers.push_back(buffer);
199 }
200}
201
202#[cfg(all(test, feature = "progress"))]
203mod tests {
204 use super::*;
205 use crate::progress::{ProgressTracker, RunProgress};
206 use crate::Table;
207 use std::sync::atomic::{AtomicU64, Ordering};
208
209 #[derive(Debug)]
210 struct CountingProgress {
211 increments: AtomicU64,
212 }
213
214 impl ProgressTracker for CountingProgress {
215 fn increment(&self, _table: Table, units: u64) {
216 self.increments.fetch_add(units, Ordering::Relaxed);
217 }
218 }
219
220 struct TestSource {
221 header: &'static [u8],
222 data: &'static [u8],
223 }
224
225 impl Source for TestSource {
226 fn header(&self, mut buffer: Vec<u8>) -> Vec<u8> {
227 buffer.extend_from_slice(self.header);
228 buffer
229 }
230
231 fn create(self, mut buffer: Vec<u8>) -> Vec<u8> {
232 buffer.extend_from_slice(self.data);
233 buffer
234 }
235 }
236
237 struct CapturingSink {
238 writes: Arc<Mutex<Vec<Vec<u8>>>>,
239 }
240
241 impl Sink for CapturingSink {
242 fn sink(&mut self, buffer: &[u8]) -> Result<(), io::Error> {
243 self.writes.lock().unwrap().push(buffer.to_vec());
244 Ok(())
245 }
246
247 fn flush(self) -> Result<(), io::Error> {
248 Ok(())
249 }
250 }
251
252 #[tokio::test]
253 async fn progress_counts_generated_chunks_not_header() {
254 let writes = Arc::new(Mutex::new(Vec::new()));
255 let tracker = Arc::new(CountingProgress {
256 increments: AtomicU64::new(0),
257 });
258 let progress: Arc<dyn ProgressTracker> = tracker.clone();
259 let progress = RunProgress::with_tracker(progress).for_table(Table::Region);
260
261 let sources = vec![
262 TestSource {
263 header: b"header\n",
264 data: b"row-1\n",
265 },
266 TestSource {
267 header: b"header\n",
268 data: b"row-2\n",
269 },
270 ];
271
272 generate_in_chunks_with_progress(
273 CapturingSink {
274 writes: Arc::clone(&writes),
275 },
276 sources.into_iter(),
277 1,
278 progress,
279 )
280 .await
281 .unwrap();
282
283 assert_eq!(tracker.increments.load(Ordering::Relaxed), 2);
284 assert_eq!(
285 *writes.lock().unwrap(),
286 vec![
287 b"header\n".to_vec(),
288 b"row-1\n".to_vec(),
289 b"row-2\n".to_vec()
290 ]
291 );
292 }
293}