1#![allow(dead_code)]
40#![allow(clippy::too_many_arguments)]
41
42use std::future::Future;
43use std::path::Path;
44use std::pin::Pin;
45use std::task::{Context, Poll};
46
47#[cfg(feature = "async")]
48use futures::{Stream, StreamExt};
49#[cfg(feature = "async")]
50use tokio::fs::File;
51#[cfg(feature = "async")]
52use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader};
53
54use crate::error::{IoError, Result};
55
56#[derive(Debug, Clone)]
58pub struct AsyncStreamingConfig {
59 pub chunk_size: usize,
61 pub buffer_size: usize,
63 pub concurrency: usize,
65 pub enable_backpressure: bool,
67 pub operation_timeout_ms: Option<u64>,
69 pub max_chunks: Option<usize>,
71 pub skip_chunks: usize,
73}
74
75impl Default for AsyncStreamingConfig {
76 fn default() -> Self {
77 Self {
78 chunk_size: 64 * 1024, buffer_size: 8 * 1024, concurrency: 4,
81 enable_backpressure: true,
82 operation_timeout_ms: None,
83 max_chunks: None,
84 skip_chunks: 0,
85 }
86 }
87}
88
89impl AsyncStreamingConfig {
90 pub fn new() -> Self {
92 Self::default()
93 }
94
95 pub fn chunk_size(mut self, size: usize) -> Self {
97 self.chunk_size = size;
98 self
99 }
100
101 pub fn buffer_size(mut self, size: usize) -> Self {
103 self.buffer_size = size;
104 self
105 }
106
107 pub fn concurrency(mut self, level: usize) -> Self {
109 self.concurrency = level;
110 self
111 }
112
113 pub fn enable_backpressure(mut self, enable: bool) -> Self {
115 self.enable_backpressure = enable;
116 self
117 }
118
119 pub fn timeout(mut self, timeoutms: u64) -> Self {
121 self.operation_timeout_ms = Some(timeoutms);
122 self
123 }
124
125 pub fn max_chunks(mut self, max: usize) -> Self {
127 self.max_chunks = Some(max);
128 self
129 }
130
131 pub fn skip_chunks(mut self, skip: usize) -> Self {
133 self.skip_chunks = skip;
134 self
135 }
136}
137
138pub struct AsyncChunkedReader {
140 reader: BufReader<File>,
141 config: AsyncStreamingConfig,
142 chunks_read: usize,
143 total_bytes_read: u64,
144 finished: bool,
145}
146
147impl AsyncChunkedReader {
148 pub async fn new<P: AsRef<Path>>(path: P, config: AsyncStreamingConfig) -> Result<Self> {
150 let file = File::open(path.as_ref())
151 .await
152 .map_err(|e| IoError::FileError(format!("Failed to open file: {}", e)))?;
153
154 let reader = BufReader::with_capacity(config.buffer_size, file);
155
156 Ok(Self {
157 reader,
158 config,
159 chunks_read: 0,
160 total_bytes_read: 0,
161 finished: false,
162 })
163 }
164
165 pub fn bytes_read(&self) -> u64 {
167 self.total_bytes_read
168 }
169
170 pub fn chunks_read(&self) -> usize {
172 self.chunks_read
173 }
174
175 pub fn is_finished(&self) -> bool {
177 self.finished
178 }
179
180 pub async fn read_next_chunk(&mut self) -> Result<Option<Vec<u8>>> {
182 if self.finished {
183 return Ok(None);
184 }
185
186 if self.chunks_read < self.config.skip_chunks {
188 let mut buffer = vec![0u8; self.config.chunk_size];
189 match self.reader.read(&mut buffer).await {
190 Ok(0) => {
191 self.finished = true;
192 return Ok(None);
193 }
194 Ok(bytes_read) => {
195 self.total_bytes_read += bytes_read as u64;
196 self.chunks_read += 1;
197 return Box::pin(self.read_next_chunk()).await; }
199 Err(e) => {
200 self.finished = true;
201 return Err(IoError::FileError(format!("Failed to skip chunk: {}", e)));
202 }
203 }
204 }
205
206 if let Some(max) = self.config.max_chunks {
208 if self.chunks_read >= max + self.config.skip_chunks {
209 self.finished = true;
210 return Ok(None);
211 }
212 }
213
214 let mut chunk = vec![0u8; self.config.chunk_size];
215
216 let read_future = self.reader.read(&mut chunk);
218
219 let bytes_read = if let Some(timeout_ms) = self.config.operation_timeout_ms {
220 match tokio::time::timeout(tokio::time::Duration::from_millis(timeout_ms), read_future)
221 .await
222 {
223 Ok(Ok(bytes)) => bytes,
224 Ok(Err(e)) => {
225 self.finished = true;
226 return Err(IoError::FileError(format!("Failed to read chunk: {}", e)));
227 }
228 Err(_) => {
229 self.finished = true;
230 return Err(IoError::FileError("Read operation timed out".to_string()));
231 }
232 }
233 } else {
234 match read_future.await {
235 Ok(bytes) => bytes,
236 Err(e) => {
237 self.finished = true;
238 return Err(IoError::FileError(format!("Failed to read chunk: {}", e)));
239 }
240 }
241 };
242
243 if bytes_read == 0 {
244 self.finished = true;
246 Ok(None)
247 } else {
248 chunk.truncate(bytes_read);
249 self.total_bytes_read += bytes_read as u64;
250 self.chunks_read += 1;
251 Ok(Some(chunk))
252 }
253 }
254}
255
256impl Stream for AsyncChunkedReader {
257 type Item = Result<Vec<u8>>;
258
259 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
260 if self.finished {
261 return Poll::Ready(None);
262 }
263
264 let read_future = self.read_next_chunk();
266 tokio::pin!(read_future);
267
268 match read_future.poll(cx) {
269 Poll::Ready(Ok(Some(chunk))) => Poll::Ready(Some(Ok(chunk))),
270 Poll::Ready(Ok(None)) => Poll::Ready(None),
271 Poll::Ready(Err(e)) => Poll::Ready(Some(Err(e))),
272 Poll::Pending => Poll::Pending,
273 }
274 }
275}
276
277pub struct AsyncLineReader {
279 reader: BufReader<File>,
280 config: AsyncStreamingConfig,
281 lines_read: usize,
282 finished: bool,
283}
284
285impl AsyncLineReader {
286 pub async fn new<P: AsRef<Path>>(path: P, config: AsyncStreamingConfig) -> Result<Self> {
288 let file = File::open(path.as_ref())
289 .await
290 .map_err(|e| IoError::FileError(format!("Failed to open file: {}", e)))?;
291
292 let reader = BufReader::with_capacity(config.buffer_size, file);
293
294 Ok(Self {
295 reader,
296 config,
297 lines_read: 0,
298 finished: false,
299 })
300 }
301
302 pub fn lines_read(&self) -> usize {
304 self.lines_read
305 }
306
307 pub fn is_finished(&self) -> bool {
309 self.finished
310 }
311
312 pub async fn read_next_lines(&mut self) -> Result<Option<Vec<String>>> {
314 if self.finished {
315 return Ok(None);
316 }
317
318 if self.lines_read < self.config.skip_chunks {
320 let mut line = String::new();
321 match self.reader.read_line(&mut line).await {
322 Ok(0) => {
323 self.finished = true;
324 return Ok(None);
325 }
326 Ok(_) => {
327 self.lines_read += 1;
328 return Box::pin(self.read_next_lines()).await; }
330 Err(e) => {
331 self.finished = true;
332 return Err(IoError::FileError(format!("Failed to skip line: {}", e)));
333 }
334 }
335 }
336
337 if let Some(max) = self.config.max_chunks {
339 if self.lines_read >= max + self.config.skip_chunks {
340 self.finished = true;
341 return Ok(None);
342 }
343 }
344
345 let mut lines = Vec::new();
346 let target_lines = self.config.chunk_size; for _ in 0..target_lines {
349 let mut line = String::new();
350
351 let read_result = if let Some(timeout_ms) = self.config.operation_timeout_ms {
352 match tokio::time::timeout(
353 tokio::time::Duration::from_millis(timeout_ms),
354 self.reader.read_line(&mut line),
355 )
356 .await
357 {
358 Ok(result) => result,
359 Err(_) => {
360 self.finished = true;
361 return Err(IoError::FileError(
362 "Read line operation timed out".to_string(),
363 ));
364 }
365 }
366 } else {
367 self.reader.read_line(&mut line).await
368 };
369
370 match read_result {
371 Ok(0) => {
372 self.finished = true;
374 break;
375 }
376 Ok(_) => {
377 if line.ends_with('\n') {
379 line.pop();
380 if line.ends_with('\r') {
381 line.pop();
382 }
383 }
384 lines.push(line);
385 self.lines_read += 1;
386 }
387 Err(e) => {
388 self.finished = true;
389 return Err(IoError::FileError(format!("Failed to read line: {}", e)));
390 }
391 }
392 }
393
394 if lines.is_empty() {
395 Ok(None)
396 } else {
397 Ok(Some(lines))
398 }
399 }
400}
401
402impl Stream for AsyncLineReader {
403 type Item = Result<Vec<String>>;
404
405 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
406 if self.finished {
407 return Poll::Ready(None);
408 }
409
410 let read_future = self.read_next_lines();
411 tokio::pin!(read_future);
412
413 match read_future.poll(cx) {
414 Poll::Ready(Ok(Some(lines))) => Poll::Ready(Some(Ok(lines))),
415 Poll::Ready(Ok(None)) => Poll::Ready(None),
416 Poll::Ready(Err(e)) => Poll::Ready(Some(Err(e))),
417 Poll::Pending => Poll::Pending,
418 }
419 }
420}
421
422#[derive(Debug, Clone, Default)]
424pub struct AsyncStreamingStats {
425 pub bytes_processed: u64,
427 pub chunks_processed: usize,
429 pub lines_processed: usize,
431 pub processing_time_ms: f64,
433 pub concurrent_operations: usize,
435 pub avg_speed_mbps: f64,
437 pub peak_memory_usage: usize,
439}
440
441impl AsyncStreamingStats {
442 pub fn new() -> Self {
444 Self::default()
445 }
446
447 pub fn update_chunk(&mut self, bytes: u64, processing_time_ms: f64) {
449 self.bytes_processed += bytes;
450 self.chunks_processed += 1;
451 self.processing_time_ms += processing_time_ms;
452
453 if self.processing_time_ms > 0.0 {
454 let total_mb = self.bytes_processed as f64 / (1024.0 * 1024.0);
455 let total_seconds = self.processing_time_ms / 1000.0;
456 self.avg_speed_mbps = total_mb / total_seconds;
457 }
458 }
459
460 pub fn update_lines(&mut self, lines: usize) {
462 self.lines_processed += lines;
463 }
464
465 pub fn update_concurrency(&mut self, count: usize) {
467 self.concurrent_operations = count;
468 }
469
470 pub fn update_memory_usage(&mut self, usage: usize) {
472 self.peak_memory_usage = self.peak_memory_usage.max(usage);
473 }
474
475 pub fn summary(&self) -> String {
477 format!(
478 "Async processed {} bytes in {} chunks ({} lines), {:.2} MB/s, {} concurrent ops, peak memory: {} KB",
479 self.bytes_processed,
480 self.chunks_processed,
481 self.lines_processed,
482 self.avg_speed_mbps,
483 self.concurrent_operations,
484 self.peak_memory_usage / 1024
485 )
486 }
487}
488
489pub async fn process_file_async<P, F, Fut, T>(
491 path: P,
492 config: AsyncStreamingConfig,
493 processor: F,
494) -> Result<(Vec<T>, AsyncStreamingStats)>
495where
496 P: AsRef<Path>,
497 F: Fn(Vec<u8>, usize) -> Fut + Send + Sync + 'static,
498 Fut: std::future::Future<Output = Result<T>> + Send,
499 T: Send + 'static,
500{
501 let reader = AsyncChunkedReader::new(path, config.clone()).await?;
502 let mut stats = AsyncStreamingStats::new();
503 let mut results = Vec::new();
504
505 let start_time = std::time::Instant::now();
506
507 let processor = std::sync::Arc::new(processor);
509 let mut chunk_stream = reader
510 .enumerate()
511 .map(|(chunk_id, chunk_result)| {
512 let processor = processor.clone();
513 async move {
514 match chunk_result {
515 Ok(chunk) => {
516 let chunk_start = std::time::Instant::now();
517 let result = processor(chunk.clone(), chunk_id).await?;
518 let processing_time = chunk_start.elapsed().as_secs_f64() * 1000.0;
519 Ok((result, chunk.len(), processing_time))
520 }
521 Err(e) => Err(e),
522 }
523 }
524 })
525 .buffer_unordered(config.concurrency);
526
527 while let Some(result) = chunk_stream.next().await {
529 match result {
530 Ok((processed_result, bytes, processing_time)) => {
531 results.push(processed_result);
532 stats.update_chunk(bytes as u64, processing_time);
533 stats.update_concurrency(config.concurrency);
534 }
535 Err(e) => return Err(e),
536 }
537 }
538
539 stats.processing_time_ms = start_time.elapsed().as_secs_f64() * 1000.0;
540
541 Ok((results, stats))
542}
543
544pub async fn process_csv_async<P, F, Fut, T>(
546 path: P,
547 config: AsyncStreamingConfig,
548 processor: F,
549) -> Result<(Vec<T>, AsyncStreamingStats)>
550where
551 P: AsRef<Path>,
552 F: Fn(Vec<String>, usize) -> Fut + Send + Sync + 'static,
553 Fut: std::future::Future<Output = Result<T>> + Send,
554 T: Send + 'static,
555{
556 let reader = AsyncLineReader::new(path, config.clone()).await?;
557 let mut stats = AsyncStreamingStats::new();
558 let mut results = Vec::new();
559
560 let start_time = std::time::Instant::now();
561
562 let processor = std::sync::Arc::new(processor);
564 let mut line_stream = reader
565 .enumerate()
566 .map(|(chunk_id, lines_result)| {
567 let processor = processor.clone();
568 async move {
569 match lines_result {
570 Ok(lines) => {
571 let chunk_start = std::time::Instant::now();
572 let mut chunk_results = Vec::new();
573
574 for (line_id, line) in lines.into_iter().enumerate() {
575 let result = processor(vec![line], chunk_id * 1000 + line_id).await?;
576 chunk_results.push(result);
577 }
578
579 let processing_time = chunk_start.elapsed().as_secs_f64() * 1000.0;
580 Ok((chunk_results, processing_time))
581 }
582 Err(e) => Err(e),
583 }
584 }
585 })
586 .buffer_unordered(config.concurrency);
587
588 while let Some(result) = line_stream.next().await {
590 match result {
591 Ok((chunk_results, processing_time)) => {
592 let line_count = chunk_results.len();
593 results.extend(chunk_results);
594 stats.update_chunk(0, processing_time); stats.update_lines(line_count);
596 stats.update_concurrency(config.concurrency);
597 }
598 Err(e) => return Err(e),
599 }
600 }
601
602 stats.processing_time_ms = start_time.elapsed().as_secs_f64() * 1000.0;
603
604 Ok((results, stats))
605}
606
607pub struct CancellationToken {
609 cancelled: std::sync::Arc<std::sync::atomic::AtomicBool>,
610}
611
612impl CancellationToken {
613 pub fn new() -> Self {
615 Self {
616 cancelled: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
617 }
618 }
619
620 pub fn cancel(&self) {
622 self.cancelled
623 .store(true, std::sync::atomic::Ordering::SeqCst);
624 }
625
626 pub fn is_cancelled(&self) -> bool {
628 self.cancelled.load(std::sync::atomic::Ordering::SeqCst)
629 }
630
631 pub fn clone(&self) -> Self {
633 Self {
634 cancelled: self.cancelled.clone(),
635 }
636 }
637}
638
639impl Default for CancellationToken {
640 fn default() -> Self {
641 Self::new()
642 }
643}
644
645#[cfg(test)]
646mod tests {
647 use super::*;
648 use tempfile::tempdir;
649
650 #[tokio::test]
651 async fn test_async_chunked_reader() {
652 let temp_dir = tempdir().expect("Operation failed");
653 let file_path = temp_dir.path().join("test_async.txt");
654
655 let test_data = "0123456789".repeat(100); std::fs::write(&file_path, &test_data).expect("Operation failed");
658
659 let config = AsyncStreamingConfig::new().chunk_size(100);
660 let mut reader = AsyncChunkedReader::new(&file_path, config)
661 .await
662 .expect("Operation failed");
663
664 let mut chunks = Vec::new();
665 while let Some(chunk_result) = reader.read_next_chunk().await.expect("Operation failed") {
666 chunks.push(chunk_result);
667 }
668
669 assert_eq!(chunks.len(), 10); for chunk in &chunks {
671 assert_eq!(chunk.len(), 100);
672 }
673 }
674
675 #[tokio::test]
676 async fn test_async_line_reader() {
677 let temp_dir = tempdir().expect("Operation failed");
678 let file_path = temp_dir.path().join("test_async_lines.txt");
679
680 let lines: Vec<String> = (0..50).map(|i| format!("Line {}", i)).collect();
682 std::fs::write(&file_path, lines.join("\n")).expect("Operation failed");
683
684 let config = AsyncStreamingConfig::new().chunk_size(10); let mut reader = AsyncLineReader::new(&file_path, config)
686 .await
687 .expect("Operation failed");
688
689 let mut chunks = Vec::new();
690 while let Some(lines_result) = reader.read_next_lines().await.expect("Operation failed") {
691 chunks.push(lines_result);
692 }
693
694 assert_eq!(chunks.len(), 5); for chunk in &chunks {
696 assert_eq!(chunk.len(), 10);
697 }
698 }
699
700 #[tokio::test]
701 async fn test_async_config() {
702 let config = AsyncStreamingConfig::new()
703 .chunk_size(1024)
704 .buffer_size(4096)
705 .concurrency(8)
706 .timeout(5000);
707
708 assert_eq!(config.chunk_size, 1024);
709 assert_eq!(config.buffer_size, 4096);
710 assert_eq!(config.concurrency, 8);
711 assert_eq!(config.operation_timeout_ms, Some(5000));
712 }
713
714 #[tokio::test]
715 async fn test_cancellation_token() {
716 let token = CancellationToken::new();
717 assert!(!token.is_cancelled());
718
719 token.cancel();
720 assert!(token.is_cancelled());
721
722 let cloned_token = token.clone();
723 assert!(cloned_token.is_cancelled());
724 }
725}