1use crate::stream::{BoxStream, Flow, NotUsed, Sink, Source, StreamCompletion};
2use crate::{StreamError, StreamResult};
3use futures::{FutureExt, channel::oneshot};
4use std::future::Future;
5use std::net::SocketAddr;
6use std::panic::AssertUnwindSafe;
7use std::path::PathBuf;
8use std::sync::{
9 Arc, Mutex,
10 atomic::{AtomicBool, Ordering},
11 mpsc as std_mpsc,
12};
13use std::thread::{self, Thread};
14use std::time::Duration;
15use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
16use tokio::net::{TcpListener, TcpStream, ToSocketAddrs};
17use tokio::sync::{mpsc, watch};
18
19const DEFAULT_CHUNK_SIZE: usize = 8192;
20const FILE_READ_AHEAD_CHUNKS: usize = 8;
21const FILE_INTERNAL_READ_SIZE: usize = 256 * 1024;
22const TCP_READ_AHEAD_CHUNKS: usize = 1;
23const PARK_INTERVAL: Duration = Duration::from_millis(1);
24const READ_READY_SPINS: usize = 256;
25const BACKPRESSURE_READY_SPINS: usize = 64;
26const BACKPRESSURE_PARK: Duration = Duration::from_micros(10);
27
28#[derive(Clone)]
36struct ConsumerWaker {
37 thread: Arc<Mutex<Option<Thread>>>,
38}
39
40impl ConsumerWaker {
41 fn new() -> Self {
42 Self {
43 thread: Arc::new(Mutex::new(None)),
44 }
45 }
46
47 fn capture_current(&self) {
48 let mut slot = self.thread.lock().expect("consumer waker poisoned");
49 if slot.is_none() {
50 *slot = Some(thread::current());
51 }
52 }
53
54 fn unpark(&self) {
55 let slot = self.thread.lock().expect("consumer waker poisoned");
56 if let Some(t) = slot.as_ref() {
57 t.unpark();
58 }
59 }
60}
61
62fn io_error(error: std::io::Error) -> StreamError {
63 StreamError::Failed(error.to_string())
64}
65
66fn write_zero_error() -> StreamError {
67 StreamError::Failed("async writer returned zero bytes".to_owned())
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
77pub struct IoResult {
78 pub bytes: u64,
79 pub status: StreamResult<()>,
80}
81
82impl IoResult {
83 #[must_use]
85 pub fn succeeded(bytes: u64) -> Self {
86 Self {
87 bytes,
88 status: Ok(()),
89 }
90 }
91
92 #[must_use]
94 pub fn failed(bytes: u64, error: StreamError) -> Self {
95 Self {
96 bytes,
97 status: Err(error),
98 }
99 }
100
101 #[must_use]
103 pub fn bytes(&self) -> u64 {
104 self.bytes
105 }
106
107 #[must_use = "the terminal status should be inspected"]
109 pub fn status(&self) -> StreamResult<()> {
110 self.status.clone()
111 }
112
113 #[must_use]
115 pub fn is_success(&self) -> bool {
116 self.status.is_ok()
117 }
118}
119
120pub type TokioByteSource = Source<Vec<u8>, StreamCompletion<IoResult>>;
122pub type TokioByteSink = Sink<Vec<u8>, StreamCompletion<IoResult>>;
124
125#[derive(Clone)]
126enum DemandTerminal {
127 Complete,
128 Error(StreamError),
129}
130
131enum DemandResponse<T> {
132 Item(T),
133 Complete,
134 Error(StreamError),
135}
136
137struct DemandSourceStream<T> {
138 demands: mpsc::Sender<std_mpsc::Sender<DemandResponse<T>>>,
139 cancel: watch::Sender<bool>,
140 terminal: Arc<Mutex<Option<DemandTerminal>>>,
141 done: bool,
142}
143
144impl<T> DemandSourceStream<T> {
145 fn terminal_response(&self) -> Option<Option<StreamResult<T>>> {
146 self.terminal
147 .lock()
148 .expect("tokio source terminal poisoned")
149 .clone()
150 .map(|terminal| match terminal {
151 DemandTerminal::Complete => None,
152 DemandTerminal::Error(error) => Some(Err(error)),
153 })
154 }
155
156 fn mark_done(&mut self) {
157 self.done = true;
158 let _ = self.cancel.send(true);
159 }
160}
161
162impl<T: Send + 'static> Iterator for DemandSourceStream<T> {
163 type Item = StreamResult<T>;
164
165 fn next(&mut self) -> Option<Self::Item> {
166 if self.done {
167 return None;
168 }
169
170 let stream_cancelled = crate::stream::current_stream_cancelled();
171 let (reply_sender, reply_receiver) = std_mpsc::channel();
172 if !send_bounded_demand(&self.demands, reply_sender, &stream_cancelled) {
173 self.mark_done();
174 return self
175 .terminal_response()
176 .unwrap_or(Some(Err(StreamError::AbruptTermination)));
177 }
178
179 loop {
180 if stream_cancelled
181 .as_ref()
182 .is_some_and(|cancelled| cancelled.load(Ordering::SeqCst))
183 {
184 self.mark_done();
185 return Some(Err(StreamError::Cancelled));
186 }
187
188 match reply_receiver.recv_timeout(PARK_INTERVAL) {
189 Ok(DemandResponse::Item(item)) => return Some(Ok(item)),
190 Ok(DemandResponse::Complete) => {
191 self.mark_done();
192 return None;
193 }
194 Ok(DemandResponse::Error(error)) => {
195 self.mark_done();
196 return Some(Err(error));
197 }
198 Err(std_mpsc::RecvTimeoutError::Timeout) => {}
199 Err(std_mpsc::RecvTimeoutError::Disconnected) => {
200 self.mark_done();
201 return self
202 .terminal_response()
203 .unwrap_or(Some(Err(StreamError::AbruptTermination)));
204 }
205 }
206 }
207 }
208}
209
210impl<T> Drop for DemandSourceStream<T> {
211 fn drop(&mut self) {
212 let _ = self.cancel.send(true);
213 }
214}
215
216struct BoundedByteSourceStream {
217 receiver: mpsc::Receiver<DemandResponse<Vec<u8>>>,
218 cancel: watch::Sender<bool>,
219 terminal: Arc<Mutex<Option<DemandTerminal>>>,
220 done: bool,
221 waker: ConsumerWaker,
222}
223
224impl BoundedByteSourceStream {
225 fn terminal_response(&self) -> Option<Option<StreamResult<Vec<u8>>>> {
226 self.terminal
227 .lock()
228 .expect("tokio source terminal poisoned")
229 .clone()
230 .map(|terminal| match terminal {
231 DemandTerminal::Complete => None,
232 DemandTerminal::Error(error) => Some(Err(error)),
233 })
234 }
235
236 fn mark_done(&mut self) {
237 self.done = true;
238 let _ = self.cancel.send(true);
239 }
240}
241
242impl Iterator for BoundedByteSourceStream {
243 type Item = StreamResult<Vec<u8>>;
244
245 fn next(&mut self) -> Option<Self::Item> {
246 if self.done {
247 return None;
248 }
249
250 self.waker.capture_current();
254
255 let stream_cancelled = crate::stream::current_stream_cancelled();
256 let mut spins = 0usize;
257 loop {
258 if stream_cancelled
259 .as_ref()
260 .is_some_and(|cancelled| cancelled.load(Ordering::SeqCst))
261 {
262 self.mark_done();
263 return Some(Err(StreamError::Cancelled));
264 }
265
266 match self.receiver.try_recv() {
267 Ok(DemandResponse::Item(item)) => return Some(Ok(item)),
268 Ok(DemandResponse::Complete) => {
269 self.mark_done();
270 return None;
271 }
272 Ok(DemandResponse::Error(error)) => {
273 self.mark_done();
274 return Some(Err(error));
275 }
276 Err(mpsc::error::TryRecvError::Empty) => read_wait(&mut spins),
277 Err(mpsc::error::TryRecvError::Disconnected) => {
278 self.mark_done();
279 return self
280 .terminal_response()
281 .unwrap_or(Some(Err(StreamError::AbruptTermination)));
282 }
283 }
284 }
285 }
286}
287
288impl Drop for BoundedByteSourceStream {
289 fn drop(&mut self) {
290 let _ = self.cancel.send(true);
291 }
292}
293
294fn send_bounded_demand<T>(
295 sender: &mpsc::Sender<T>,
296 mut message: T,
297 stream_cancelled: &Option<Arc<AtomicBool>>,
298) -> bool {
299 let mut spins = 0usize;
300 loop {
301 if stream_cancelled
302 .as_ref()
303 .is_some_and(|cancelled| cancelled.load(Ordering::SeqCst))
304 {
305 return false;
306 }
307
308 match sender.try_send(message) {
309 Ok(()) => return true,
310 Err(mpsc::error::TrySendError::Full(returned)) => {
311 message = returned;
312 backpressure_wait(&mut spins);
313 }
314 Err(mpsc::error::TrySendError::Closed(_)) => return false,
315 }
316 }
317}
318
319fn finish_terminal(terminal: &Arc<Mutex<Option<DemandTerminal>>>, value: DemandTerminal) {
320 let mut slot = terminal.lock().expect("tokio source terminal poisoned");
321 if slot.is_none() {
322 *slot = Some(value);
323 }
324}
325
326async fn next_demand<T>(
327 demands: &mut mpsc::Receiver<std_mpsc::Sender<DemandResponse<T>>>,
328 cancel: &mut watch::Receiver<bool>,
329) -> Option<std_mpsc::Sender<DemandResponse<T>>> {
330 if *cancel.borrow() {
331 return None;
332 }
333
334 tokio::select! {
335 demand = demands.recv() => demand,
336 changed = cancel.changed() => {
337 let _ = changed;
338 None
339 }
340 }
341}
342
343fn async_read_source<R, Fut>(
344 open: impl FnOnce() -> Fut + Send + 'static,
345 chunk_size: usize,
346 internal_read_size: usize,
347 read_ahead_chunks: usize,
348) -> (BoxStream<Vec<u8>>, StreamCompletion<IoResult>)
349where
350 R: AsyncRead + Unpin + Send + 'static,
351 Fut: Future<Output = std::io::Result<R>> + Send + 'static,
352{
353 assert!(chunk_size > 0, "chunk size must be greater than zero");
354 assert!(
355 read_ahead_chunks > 0,
356 "read-ahead bound must be greater than zero"
357 );
358 let internal_read_size = internal_read_size.max(chunk_size);
359 let (item_sender, item_receiver) = mpsc::channel(read_ahead_chunks);
360 let (cancel_sender, cancel_receiver) = watch::channel(false);
361 let (mat_sender, mat_receiver) = oneshot::channel();
362 let terminal = Arc::new(Mutex::new(None));
363 let terminal_for_task = Arc::clone(&terminal);
364 let waker = ConsumerWaker::new();
365 let producer_waker = waker.clone();
366
367 crate::stream::stream_tokio_runtime().spawn(async move {
368 let result = AssertUnwindSafe(run_async_read_task(
369 open(),
370 chunk_size,
371 internal_read_size,
372 item_sender,
373 cancel_receiver,
374 Arc::clone(&terminal_for_task),
375 producer_waker,
376 ))
377 .catch_unwind()
378 .await
379 .unwrap_or_else(|_| {
380 finish_terminal(
381 &terminal_for_task,
382 DemandTerminal::Error(StreamError::AbruptTermination),
383 );
384 Err(StreamError::AbruptTermination)
385 });
386 let _ = mat_sender.send(result);
387 });
388
389 (
390 Box::new(BoundedByteSourceStream {
391 receiver: item_receiver,
392 cancel: cancel_sender,
393 terminal,
394 done: false,
395 waker,
396 }) as BoxStream<Vec<u8>>,
397 StreamCompletion::from_receiver(mat_receiver, None),
398 )
399}
400
401async fn run_async_read_task<R, Fut>(
402 open: Fut,
403 chunk_size: usize,
404 internal_read_size: usize,
405 items: mpsc::Sender<DemandResponse<Vec<u8>>>,
406 mut cancel: watch::Receiver<bool>,
407 terminal: Arc<Mutex<Option<DemandTerminal>>>,
408 waker: ConsumerWaker,
409) -> StreamResult<IoResult>
410where
411 R: AsyncRead + Unpin + Send + 'static,
412 Fut: Future<Output = std::io::Result<R>> + Send + 'static,
413{
414 let mut bytes = 0_u64;
415 let mut reader = tokio::select! {
416 reader = open => match reader {
417 Ok(reader) => reader,
418 Err(error) => {
419 let error = io_error(error);
420 finish_terminal(&terminal, DemandTerminal::Error(error.clone()));
421 let _ = send_read_item(&items, DemandResponse::Error(error.clone()), &mut cancel, &waker).await;
422 return Ok(IoResult::failed(bytes, error));
423 }
424 },
425 changed = cancel.changed() => {
426 let _ = changed;
427 finish_terminal(&terminal, DemandTerminal::Error(StreamError::Cancelled));
428 return Ok(IoResult::failed(bytes, StreamError::Cancelled));
429 }
430 };
431
432 let mut buffer = vec![0_u8; internal_read_size];
433 let mut pending_tail = Vec::with_capacity(chunk_size);
434 loop {
435 let read = tokio::select! {
436 read = reader.read(&mut buffer) => read,
437 changed = cancel.changed() => {
438 let _ = changed;
439 finish_terminal(&terminal, DemandTerminal::Error(StreamError::Cancelled));
440 return Ok(IoResult::failed(bytes, StreamError::Cancelled));
441 }
442 };
443
444 match read {
445 Ok(0) => {
446 if !pending_tail.is_empty()
447 && !send_read_item(
448 &items,
449 DemandResponse::Item(std::mem::take(&mut pending_tail)),
450 &mut cancel,
451 &waker,
452 )
453 .await
454 {
455 finish_terminal(&terminal, DemandTerminal::Error(StreamError::Cancelled));
456 return Ok(IoResult::failed(bytes, StreamError::Cancelled));
457 }
458 finish_terminal(&terminal, DemandTerminal::Complete);
459 let _ = send_read_item(&items, DemandResponse::Complete, &mut cancel, &waker).await;
460 return Ok(IoResult::succeeded(bytes));
461 }
462 Ok(read) => {
463 bytes += read as u64;
464 if !send_read_chunks(
465 &items,
466 chunk_size,
467 &mut pending_tail,
468 &buffer[..read],
469 &mut cancel,
470 &waker,
471 )
472 .await
473 {
474 finish_terminal(&terminal, DemandTerminal::Error(StreamError::Cancelled));
475 return Ok(IoResult::failed(bytes, StreamError::Cancelled));
476 }
477 }
478 Err(error) => {
479 let error = io_error(error);
480 finish_terminal(&terminal, DemandTerminal::Error(error.clone()));
481 let _ = send_read_item(
482 &items,
483 DemandResponse::Error(error.clone()),
484 &mut cancel,
485 &waker,
486 )
487 .await;
488 return Ok(IoResult::failed(bytes, error));
489 }
490 }
491 }
492}
493
494async fn send_read_chunks(
495 sender: &mpsc::Sender<DemandResponse<Vec<u8>>>,
496 chunk_size: usize,
497 pending_tail: &mut Vec<u8>,
498 read_buffer: &[u8],
499 cancel: &mut watch::Receiver<bool>,
500 waker: &ConsumerWaker,
501) -> bool {
502 let mut offset = 0;
503 if !pending_tail.is_empty() {
504 let needed = chunk_size - pending_tail.len();
505 let take = needed.min(read_buffer.len());
506 pending_tail.extend_from_slice(&read_buffer[..take]);
507 offset += take;
508 if pending_tail.len() == chunk_size
509 && !send_read_item(
510 sender,
511 DemandResponse::Item(std::mem::take(pending_tail)),
512 cancel,
513 waker,
514 )
515 .await
516 {
517 return false;
518 }
519 }
520
521 while offset + chunk_size <= read_buffer.len() {
522 let next = offset + chunk_size;
523 if !send_read_item(
524 sender,
525 DemandResponse::Item(read_buffer[offset..next].to_vec()),
526 cancel,
527 waker,
528 )
529 .await
530 {
531 return false;
532 }
533 offset = next;
534 }
535
536 if offset < read_buffer.len() {
537 pending_tail.extend_from_slice(&read_buffer[offset..]);
538 }
539 true
540}
541
542async fn send_read_item<T>(
543 sender: &mpsc::Sender<DemandResponse<T>>,
544 item: DemandResponse<T>,
545 cancel: &mut watch::Receiver<bool>,
546 waker: &ConsumerWaker,
547) -> bool
548where
549 T: Send + 'static,
550{
551 let result = tokio::select! {
552 result = sender.send(item) => result,
553 changed = cancel.changed() => {
554 let _ = changed;
555 return false;
556 }
557 };
558 if result.is_ok() {
559 waker.unpark();
560 }
561 result.is_ok()
562}
563
564enum WriteCommand {
565 Chunk(Vec<u8>),
566 Finish(StreamResult<()>),
567}
568
569struct TokioCancelGuard {
570 cancel: watch::Sender<bool>,
571 armed: bool,
572}
573
574impl TokioCancelGuard {
575 fn new(cancel: watch::Sender<bool>) -> Self {
576 Self {
577 cancel,
578 armed: true,
579 }
580 }
581
582 fn disarm(&mut self) {
583 self.armed = false;
584 }
585}
586
587impl Drop for TokioCancelGuard {
588 fn drop(&mut self) {
589 if self.armed {
590 let _ = self.cancel.send(true);
591 }
592 }
593}
594
595fn async_write_sink<W, F, Fut>(open: F) -> TokioByteSink
596where
597 W: AsyncWrite + Unpin + Send + 'static,
598 F: Fn() -> Fut + Send + Sync + 'static,
599 Fut: Future<Output = std::io::Result<W>> + Send + 'static,
600{
601 let open = Arc::new(open);
602 Sink::from_runner(move |input, materializer| {
603 run_async_write_sink::<W, F, Fut>(input, materializer, Arc::clone(&open))
604 })
605}
606
607async fn run_async_write_task<W, Fut>(
608 open: Fut,
609 mut commands: mpsc::Receiver<WriteCommand>,
610 mut cancel: watch::Receiver<bool>,
611) -> StreamResult<IoResult>
612where
613 W: AsyncWrite + Unpin + Send + 'static,
614 Fut: Future<Output = std::io::Result<W>> + Send + 'static,
615{
616 let mut bytes = 0_u64;
617 let mut writer = tokio::select! {
618 writer = open => match writer {
619 Ok(writer) => writer,
620 Err(error) => return Ok(IoResult::failed(bytes, io_error(error))),
621 },
622 changed = cancel.changed() => {
623 let _ = changed;
624 return Ok(IoResult::failed(bytes, StreamError::Cancelled));
625 }
626 };
627
628 loop {
629 let command = tokio::select! {
630 command = commands.recv() => command,
631 changed = cancel.changed() => {
632 let _ = changed;
633 return Ok(IoResult::failed(bytes, StreamError::Cancelled));
634 }
635 };
636
637 match command {
638 Some(WriteCommand::Chunk(chunk)) => {
639 if let Err(error) = write_chunk(&mut writer, &chunk, &mut cancel, &mut bytes).await
640 {
641 return Ok(IoResult::failed(bytes, error));
642 }
643 }
644 Some(WriteCommand::Finish(upstream_status)) => {
645 let shutdown_status = shutdown_writer(&mut writer, &mut cancel).await;
646 return Ok(IoResult {
647 bytes,
648 status: upstream_status.and(shutdown_status),
649 });
650 }
651 None => {
652 let _ = shutdown_writer(&mut writer, &mut cancel).await;
653 return Ok(IoResult::failed(bytes, StreamError::Cancelled));
654 }
655 }
656 }
657}
658
659async fn write_chunk<W>(
660 writer: &mut W,
661 chunk: &[u8],
662 cancel: &mut watch::Receiver<bool>,
663 bytes: &mut u64,
664) -> StreamResult<()>
665where
666 W: AsyncWrite + Unpin,
667{
668 let mut offset = 0usize;
669 while offset < chunk.len() {
670 let written = tokio::select! {
671 written = writer.write(&chunk[offset..]) => written.map_err(io_error)?,
672 changed = cancel.changed() => {
673 let _ = changed;
674 return Err(StreamError::Cancelled);
675 }
676 };
677
678 if written == 0 {
679 return Err(write_zero_error());
680 }
681 offset += written;
682 *bytes += written as u64;
683 }
684 Ok(())
685}
686
687async fn shutdown_writer<W>(writer: &mut W, cancel: &mut watch::Receiver<bool>) -> StreamResult<()>
688where
689 W: AsyncWrite + Unpin,
690{
691 tokio::select! {
692 result = writer.flush() => result.map_err(io_error)?,
693 changed = cancel.changed() => {
694 let _ = changed;
695 return Err(StreamError::Cancelled);
696 }
697 }
698
699 tokio::select! {
700 result = writer.shutdown() => result.map_err(io_error),
701 changed = cancel.changed() => {
702 let _ = changed;
703 Err(StreamError::Cancelled)
704 }
705 }
706}
707
708fn feed_async_writer(
709 mut input: BoxStream<Vec<u8>>,
710 command_sender: mpsc::Sender<WriteCommand>,
711 done_receiver: std_mpsc::Receiver<StreamResult<IoResult>>,
712 cancelled: Arc<AtomicBool>,
713 cancel_sender: watch::Sender<bool>,
714) -> StreamResult<IoResult> {
715 let mut terminal = Ok(());
716 loop {
717 if cancelled.load(Ordering::SeqCst) {
718 terminal = Err(StreamError::Cancelled);
719 break;
720 }
721
722 match input.next() {
723 Some(Ok(chunk)) => {
724 if !send_write_command(&command_sender, WriteCommand::Chunk(chunk), &cancelled) {
725 break;
726 }
727 }
728 Some(Err(error)) => {
729 terminal = Err(error);
730 break;
731 }
732 None => break,
733 }
734 }
735
736 if cancelled.load(Ordering::SeqCst) {
737 let _ = cancel_sender.send(true);
738 } else {
739 let _ = send_write_command(&command_sender, WriteCommand::Finish(terminal), &cancelled);
740 }
741 drop(command_sender);
742
743 loop {
744 match done_receiver.recv_timeout(PARK_INTERVAL) {
745 Ok(result) => return result,
746 Err(std_mpsc::RecvTimeoutError::Timeout) => {
747 if cancelled.load(Ordering::SeqCst) {
748 let _ = cancel_sender.send(true);
752 }
753 }
754 Err(std_mpsc::RecvTimeoutError::Disconnected) => {
755 return Err(StreamError::AbruptTermination);
756 }
757 }
758 }
759}
760
761fn send_write_command(
762 sender: &mpsc::Sender<WriteCommand>,
763 mut command: WriteCommand,
764 cancelled: &AtomicBool,
765) -> bool {
766 let mut spins = 0usize;
767 loop {
768 if cancelled.load(Ordering::SeqCst) {
769 return false;
770 }
771
772 match sender.try_send(command) {
773 Ok(()) => return true,
774 Err(mpsc::error::TrySendError::Full(returned)) => {
775 command = returned;
776 backpressure_wait(&mut spins);
777 }
778 Err(mpsc::error::TrySendError::Closed(_)) => return false,
779 }
780 }
781}
782
783fn backpressure_wait(spins: &mut usize) {
784 if *spins < BACKPRESSURE_READY_SPINS {
785 *spins += 1;
786 thread::yield_now();
787 } else {
788 thread::park_timeout(BACKPRESSURE_PARK);
789 }
790}
791
792fn read_wait(spins: &mut usize) {
793 if *spins < READ_READY_SPINS {
794 *spins += 1;
795 thread::yield_now();
796 } else {
797 thread::park_timeout(PARK_INTERVAL);
798 }
799}
800
801fn tokio_file_read_source(
802 path: PathBuf,
803 chunk_size: usize,
804) -> (BoxStream<Vec<u8>>, StreamCompletion<IoResult>) {
805 async_read_source(
806 move || tokio::fs::File::open(path),
807 chunk_size,
808 FILE_INTERNAL_READ_SIZE,
809 FILE_READ_AHEAD_CHUNKS,
810 )
811}
812
813async fn tokio_file_write_open(path: Arc<PathBuf>) -> std::io::Result<tokio::fs::File> {
814 tokio::fs::OpenOptions::new()
815 .create(true)
816 .truncate(true)
817 .write(true)
818 .open(path.as_ref())
819 .await
820}
821
822fn run_async_write_sink<W, F, Fut>(
823 input: BoxStream<Vec<u8>>,
824 materializer: &crate::stream::Materializer,
825 open: Arc<F>,
826) -> StreamResult<StreamCompletion<IoResult>>
827where
828 W: AsyncWrite + Unpin + Send + 'static,
829 F: Fn() -> Fut + Send + Sync + 'static,
830 Fut: Future<Output = std::io::Result<W>> + Send + 'static,
831{
832 let (command_sender, command_receiver) = mpsc::channel(1);
833 let (cancel_sender, cancel_receiver) = watch::channel(false);
834 let (done_sender, done_receiver) = std_mpsc::sync_channel(1);
835
836 crate::stream::stream_tokio_runtime().spawn(async move {
837 let result = AssertUnwindSafe(run_async_write_task(
838 open(),
839 command_receiver,
840 cancel_receiver,
841 ))
842 .catch_unwind()
843 .await
844 .unwrap_or(Err(StreamError::AbruptTermination));
845 let _ = done_sender.send(result);
846 });
847
848 Ok(materializer.spawn_stream(move |cancelled| {
849 let mut guard = TokioCancelGuard::new(cancel_sender.clone());
850 let result = feed_async_writer(
851 input,
852 command_sender,
853 done_receiver,
854 cancelled,
855 cancel_sender,
856 );
857 guard.disarm();
858 result
859 }))
860}
861
862fn tokio_file_write_sink(path: PathBuf) -> TokioByteSink {
863 let path = Arc::new(path);
864 async_write_sink(move || tokio_file_write_open(Arc::clone(&path)))
865}
866
867pub struct TokioFileIO;
868
869impl TokioFileIO {
870 #[must_use]
880 pub fn from_path(path: impl Into<PathBuf>, chunk_size: usize) -> TokioByteSource {
881 assert!(chunk_size > 0, "chunk size must be greater than zero");
882 let path = path.into();
883 Source::from_materialized_factory(move |_materializer| {
884 let path = path.clone();
885 Ok(tokio_file_read_source(path, chunk_size))
886 })
887 }
888
889 #[must_use]
891 pub fn from_path_default(path: impl Into<PathBuf>) -> TokioByteSource {
892 Self::from_path(path, DEFAULT_CHUNK_SIZE)
893 }
894
895 #[must_use]
902 pub fn to_path(path: impl Into<PathBuf>) -> TokioByteSink {
903 tokio_file_write_sink(path.into())
904 }
905}
906
907#[cfg(feature = "io-uring-file")]
918pub struct UringFileIO;
919
920#[cfg(feature = "io-uring-file")]
921impl UringFileIO {
922 #[must_use]
929 pub fn from_path(path: impl Into<PathBuf>, chunk_size: usize) -> TokioByteSource {
930 assert!(chunk_size > 0, "chunk size must be greater than zero");
931 let path = path.into();
932 Source::from_materialized_factory(move |_materializer| {
933 let path = path.clone();
934 Ok(uring_file_read_source_or_fallback(path, chunk_size))
935 })
936 }
937
938 #[must_use]
940 pub fn from_path_default(path: impl Into<PathBuf>) -> TokioByteSource {
941 Self::from_path(path, DEFAULT_CHUNK_SIZE)
942 }
943
944 #[must_use]
949 pub fn to_path(path: impl Into<PathBuf>) -> TokioByteSink {
950 uring_file_write_sink_or_fallback(path.into())
951 }
952}
953
954#[cfg(all(feature = "io-uring-file", not(target_os = "linux")))]
955fn uring_file_read_source_or_fallback(
956 path: PathBuf,
957 chunk_size: usize,
958) -> (BoxStream<Vec<u8>>, StreamCompletion<IoResult>) {
959 tokio_file_read_source(path, chunk_size)
960}
961
962#[cfg(all(feature = "io-uring-file", not(target_os = "linux")))]
963fn uring_file_write_sink_or_fallback(path: PathBuf) -> TokioByteSink {
964 tokio_file_write_sink(path)
965}
966
967#[cfg(all(feature = "io-uring-file", target_os = "linux"))]
968type UringJob =
969 Box<dyn FnOnce() -> std::pin::Pin<Box<dyn Future<Output = ()> + 'static>> + Send + 'static>;
970
971#[cfg(all(feature = "io-uring-file", target_os = "linux"))]
972#[derive(Clone)]
973struct UringRuntimeHandle {
974 sender: mpsc::UnboundedSender<UringJob>,
975}
976
977#[cfg(all(feature = "io-uring-file", target_os = "linux"))]
978enum UringRuntimeInit {
979 Ready,
980 Failed(std::io::Error),
981}
982
983#[cfg(all(feature = "io-uring-file", target_os = "linux"))]
984fn uring_runtime_handle() -> Result<&'static UringRuntimeHandle, String> {
985 static HANDLE: std::sync::OnceLock<Result<UringRuntimeHandle, String>> =
986 std::sync::OnceLock::new();
987 HANDLE
988 .get_or_init(start_uring_runtime)
989 .as_ref()
990 .map_err(Clone::clone)
991}
992
993#[cfg(all(feature = "io-uring-file", target_os = "linux"))]
994fn start_uring_runtime() -> Result<UringRuntimeHandle, String> {
995 let (init_sender, init_receiver) = std_mpsc::sync_channel(1);
996 let (sender, receiver) = mpsc::unbounded_channel::<UringJob>();
997 thread::Builder::new()
998 .name("datum-uring-file".to_owned())
999 .spawn(
1000 move || match tokio_uring::Runtime::new(&tokio_uring::builder()) {
1001 Ok(runtime) => {
1002 let _ = init_sender.send(UringRuntimeInit::Ready);
1003 runtime.block_on(run_uring_jobs(receiver));
1004 }
1005 Err(error) => {
1006 let _ = init_sender.send(UringRuntimeInit::Failed(error));
1007 }
1008 },
1009 )
1010 .map_err(|error| error.to_string())?;
1011
1012 match init_receiver.recv() {
1013 Ok(UringRuntimeInit::Ready) => Ok(UringRuntimeHandle { sender }),
1014 Ok(UringRuntimeInit::Failed(error)) => Err(error.to_string()),
1015 Err(_) => Err("tokio-uring runtime thread exited".to_owned()),
1016 }
1017}
1018
1019#[cfg(all(feature = "io-uring-file", target_os = "linux"))]
1020async fn run_uring_jobs(mut receiver: mpsc::UnboundedReceiver<UringJob>) {
1021 while let Some(job) = receiver.recv().await {
1022 tokio_uring::spawn(job());
1023 }
1024}
1025
1026#[cfg(all(feature = "io-uring-file", target_os = "linux"))]
1027fn spawn_uring_job(job: UringJob) -> Result<(), String> {
1028 uring_runtime_handle()?
1029 .sender
1030 .send(job)
1031 .map_err(|_| "tokio-uring runtime thread exited".to_owned())
1032}
1033
1034#[cfg(all(feature = "io-uring-file", target_os = "linux"))]
1035fn uring_file_read_source_or_fallback(
1036 path: PathBuf,
1037 chunk_size: usize,
1038) -> (BoxStream<Vec<u8>>, StreamCompletion<IoResult>) {
1039 let internal_read_size = FILE_INTERNAL_READ_SIZE.max(chunk_size);
1040 let (item_sender, item_receiver) = mpsc::channel(FILE_READ_AHEAD_CHUNKS);
1041 let (cancel_sender, cancel_receiver) = watch::channel(false);
1042 let (mat_sender, mat_receiver) = oneshot::channel();
1043 let terminal = Arc::new(Mutex::new(None));
1044 let terminal_for_task = Arc::clone(&terminal);
1045 let waker = ConsumerWaker::new();
1046 let producer_waker = waker.clone();
1047 let uring_path = path.clone();
1048
1049 let result = spawn_uring_job(Box::new(move || {
1050 Box::pin(async move {
1051 let task_result = AssertUnwindSafe(run_uring_read_task(
1052 uring_path,
1053 chunk_size,
1054 internal_read_size,
1055 item_sender,
1056 cancel_receiver,
1057 Arc::clone(&terminal_for_task),
1058 producer_waker,
1059 ))
1060 .catch_unwind()
1061 .await
1062 .unwrap_or_else(|_| {
1063 finish_terminal(
1064 &terminal_for_task,
1065 DemandTerminal::Error(StreamError::AbruptTermination),
1066 );
1067 Err(StreamError::AbruptTermination)
1068 });
1069 let _ = mat_sender.send(task_result);
1070 })
1071 }));
1072
1073 if result.is_err() {
1074 return tokio_file_read_source(path, chunk_size);
1075 }
1076
1077 (
1078 Box::new(BoundedByteSourceStream {
1079 receiver: item_receiver,
1080 cancel: cancel_sender,
1081 terminal,
1082 done: false,
1083 waker,
1084 }) as BoxStream<Vec<u8>>,
1085 StreamCompletion::from_receiver(mat_receiver, None),
1086 )
1087}
1088
1089#[cfg(all(feature = "io-uring-file", target_os = "linux"))]
1090async fn run_uring_read_task(
1091 path: PathBuf,
1092 chunk_size: usize,
1093 internal_read_size: usize,
1094 items: mpsc::Sender<DemandResponse<Vec<u8>>>,
1095 mut cancel: watch::Receiver<bool>,
1096 terminal: Arc<Mutex<Option<DemandTerminal>>>,
1097 waker: ConsumerWaker,
1098) -> StreamResult<IoResult> {
1099 let mut bytes = 0_u64;
1100 let file = tokio::select! {
1101 file = tokio_uring::fs::File::open(path) => match file {
1102 Ok(file) => file,
1103 Err(error) => {
1104 let error = io_error(error);
1105 finish_terminal(&terminal, DemandTerminal::Error(error.clone()));
1106 let _ = send_read_item(&items, DemandResponse::Error(error.clone()), &mut cancel, &waker).await;
1107 return Ok(IoResult::failed(bytes, error));
1108 }
1109 },
1110 changed = cancel.changed() => {
1111 let _ = changed;
1112 finish_terminal(&terminal, DemandTerminal::Error(StreamError::Cancelled));
1113 return Ok(IoResult::failed(bytes, StreamError::Cancelled));
1114 }
1115 };
1116
1117 let mut offset = 0_u64;
1118 let mut buffer = Vec::with_capacity(internal_read_size);
1119 let mut pending_tail = Vec::with_capacity(chunk_size);
1120 loop {
1121 let read_buffer = std::mem::take(&mut buffer);
1122 let (read, returned_buffer) = tokio::select! {
1123 result = file.read_at(read_buffer, offset) => result,
1124 changed = cancel.changed() => {
1125 let _ = changed;
1126 finish_terminal(&terminal, DemandTerminal::Error(StreamError::Cancelled));
1127 return Ok(IoResult::failed(bytes, StreamError::Cancelled));
1128 }
1129 };
1130 buffer = returned_buffer;
1131
1132 match read {
1133 Ok(0) => {
1134 if !pending_tail.is_empty()
1135 && !send_read_item(
1136 &items,
1137 DemandResponse::Item(std::mem::take(&mut pending_tail)),
1138 &mut cancel,
1139 &waker,
1140 )
1141 .await
1142 {
1143 finish_terminal(&terminal, DemandTerminal::Error(StreamError::Cancelled));
1144 return Ok(IoResult::failed(bytes, StreamError::Cancelled));
1145 }
1146 finish_terminal(&terminal, DemandTerminal::Complete);
1147 let _ = send_read_item(&items, DemandResponse::Complete, &mut cancel, &waker).await;
1148 return Ok(IoResult::succeeded(bytes));
1149 }
1150 Ok(read) => {
1151 bytes += read as u64;
1152 offset += read as u64;
1153 if !send_read_chunks(
1154 &items,
1155 chunk_size,
1156 &mut pending_tail,
1157 &buffer[..read],
1158 &mut cancel,
1159 &waker,
1160 )
1161 .await
1162 {
1163 finish_terminal(&terminal, DemandTerminal::Error(StreamError::Cancelled));
1164 return Ok(IoResult::failed(bytes, StreamError::Cancelled));
1165 }
1166 buffer.clear();
1167 }
1168 Err(error) => {
1169 let error = io_error(error);
1170 finish_terminal(&terminal, DemandTerminal::Error(error.clone()));
1171 let _ = send_read_item(
1172 &items,
1173 DemandResponse::Error(error.clone()),
1174 &mut cancel,
1175 &waker,
1176 )
1177 .await;
1178 return Ok(IoResult::failed(bytes, error));
1179 }
1180 }
1181 }
1182}
1183
1184#[cfg(all(feature = "io-uring-file", target_os = "linux"))]
1185fn uring_file_write_sink_or_fallback(path: PathBuf) -> TokioByteSink {
1186 let path = Arc::new(path);
1187 Sink::from_runner(move |input, materializer| {
1188 let path = Arc::clone(&path);
1189 let (command_sender, command_receiver) = mpsc::channel(1);
1190 let (cancel_sender, cancel_receiver) = watch::channel(false);
1191 let (done_sender, done_receiver) = std_mpsc::sync_channel(1);
1192 let uring_path = Arc::clone(&path);
1193
1194 let result = spawn_uring_job(Box::new(move || {
1195 Box::pin(async move {
1196 let task_result = AssertUnwindSafe(run_uring_write_task(
1197 uring_path,
1198 command_receiver,
1199 cancel_receiver,
1200 ))
1201 .catch_unwind()
1202 .await
1203 .unwrap_or(Err(StreamError::AbruptTermination));
1204 let _ = done_sender.send(task_result);
1205 })
1206 }));
1207
1208 if result.is_err() {
1209 let fallback_path = Arc::clone(&path);
1210 return run_async_write_sink::<tokio::fs::File, _, _>(
1211 input,
1212 materializer,
1213 Arc::new(move || tokio_file_write_open(Arc::clone(&fallback_path))),
1214 );
1215 }
1216
1217 Ok(materializer.spawn_stream(move |cancelled| {
1218 let mut guard = TokioCancelGuard::new(cancel_sender.clone());
1219 let result = feed_async_writer(
1220 input,
1221 command_sender,
1222 done_receiver,
1223 cancelled,
1224 cancel_sender,
1225 );
1226 guard.disarm();
1227 result
1228 }))
1229 })
1230}
1231
1232#[cfg(all(feature = "io-uring-file", target_os = "linux"))]
1233async fn run_uring_write_task(
1234 path: Arc<PathBuf>,
1235 mut commands: mpsc::Receiver<WriteCommand>,
1236 mut cancel: watch::Receiver<bool>,
1237) -> StreamResult<IoResult> {
1238 let mut bytes = 0_u64;
1239 let file = tokio::select! {
1240 file = tokio_uring::fs::File::create(path.as_ref()) => match file {
1241 Ok(file) => file,
1242 Err(error) => return Ok(IoResult::failed(bytes, io_error(error))),
1243 },
1244 changed = cancel.changed() => {
1245 let _ = changed;
1246 return Ok(IoResult::failed(bytes, StreamError::Cancelled));
1247 }
1248 };
1249
1250 let mut offset = 0_u64;
1251 loop {
1252 let command = tokio::select! {
1253 command = commands.recv() => command,
1254 changed = cancel.changed() => {
1255 let _ = changed;
1256 return Ok(IoResult::failed(bytes, StreamError::Cancelled));
1257 }
1258 };
1259
1260 match command {
1261 Some(WriteCommand::Chunk(chunk)) => {
1262 if let Err(error) =
1263 write_uring_chunk(&file, chunk, &mut offset, &mut cancel, &mut bytes).await
1264 {
1265 return Ok(IoResult::failed(bytes, error));
1266 }
1267 }
1268 Some(WriteCommand::Finish(upstream_status)) => {
1269 let close_status = tokio::select! {
1270 result = file.close() => result.map_err(io_error),
1271 changed = cancel.changed() => {
1272 let _ = changed;
1273 Err(StreamError::Cancelled)
1274 }
1275 };
1276 return Ok(IoResult {
1277 bytes,
1278 status: upstream_status.and(close_status),
1279 });
1280 }
1281 None => {
1282 let _ = file.close().await;
1283 return Ok(IoResult::failed(bytes, StreamError::Cancelled));
1284 }
1285 }
1286 }
1287}
1288
1289#[cfg(all(feature = "io-uring-file", target_os = "linux"))]
1290async fn write_uring_chunk(
1291 file: &tokio_uring::fs::File,
1292 chunk: Vec<u8>,
1293 offset: &mut u64,
1294 cancel: &mut watch::Receiver<bool>,
1295 bytes: &mut u64,
1296) -> StreamResult<()> {
1297 use tokio_uring::buf::BoundedBuf;
1298
1299 let len = chunk.len();
1300 if len == 0 {
1301 return Ok(());
1302 }
1303
1304 let mut written = 0usize;
1305 let mut buffer = chunk;
1306 while written < len {
1307 let slice = buffer.slice(written..len);
1308 let (result, returned) = tokio::select! {
1309 result = file.write_at(slice, *offset).submit() => result,
1310 changed = cancel.changed() => {
1311 let _ = changed;
1312 return Err(StreamError::Cancelled);
1313 }
1314 };
1315 buffer = returned.into_inner();
1316
1317 match result {
1318 Ok(0) => return Err(write_zero_error()),
1319 Ok(n) => {
1320 written += n;
1321 *offset += n as u64;
1322 *bytes += n as u64;
1323 }
1324 Err(error) => return Err(io_error(error)),
1325 }
1326 }
1327
1328 Ok(())
1329}
1330
1331#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1334pub struct TcpConnection {
1335 pub local_addr: SocketAddr,
1337 pub remote_addr: SocketAddr,
1339}
1340
1341impl TcpConnection {
1342 #[must_use]
1343 pub fn local_addr(&self) -> SocketAddr {
1344 self.local_addr
1345 }
1346
1347 #[must_use]
1348 pub fn remote_addr(&self) -> SocketAddr {
1349 self.remote_addr
1350 }
1351}
1352
1353#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1356pub struct TcpBinding {
1357 pub local_addr: SocketAddr,
1359}
1360
1361impl TcpBinding {
1362 #[must_use]
1363 pub fn local_addr(&self) -> SocketAddr {
1364 self.local_addr
1365 }
1366}
1367
1368pub struct TcpIncomingConnection {
1375 connection: TcpConnection,
1376 source: TokioByteSource,
1377 sink: TokioByteSink,
1378}
1379
1380impl TcpIncomingConnection {
1381 #[must_use]
1382 pub fn local_addr(&self) -> SocketAddr {
1383 self.connection.local_addr
1384 }
1385
1386 #[must_use]
1387 pub fn remote_addr(&self) -> SocketAddr {
1388 self.connection.remote_addr
1389 }
1390
1391 #[must_use]
1392 pub fn connection(&self) -> TcpConnection {
1393 self.connection
1394 }
1395
1396 #[must_use]
1400 pub fn into_parts(self) -> (TokioByteSource, TokioByteSink) {
1401 (self.source, self.sink)
1402 }
1403
1404 #[must_use]
1407 pub fn into_flow(self) -> Flow<Vec<u8>, Vec<u8>, NotUsed> {
1408 Flow::from_sink_and_source_coupled(self.sink, self.source)
1409 .map_materialized_value(|_| NotUsed)
1410 }
1411}
1412
1413pub struct TokioTcp;
1414
1415impl TokioTcp {
1416 #[must_use]
1423 pub fn outgoing_connection<A>(
1424 addr: A,
1425 chunk_size: usize,
1426 ) -> Flow<Vec<u8>, Vec<u8>, StreamCompletion<TcpConnection>>
1427 where
1428 A: ToSocketAddrs + Clone + Send + Sync + 'static,
1429 {
1430 assert!(chunk_size > 0, "chunk size must be greater than zero");
1431 Flow::future_flow(move || {
1432 let addr = addr.clone();
1433 async move {
1434 let stream = TcpStream::connect(addr).await.map_err(io_error)?;
1435 tcp_flow_from_stream(stream, chunk_size)
1436 }
1437 })
1438 }
1439
1440 #[must_use]
1442 pub fn outgoing_connection_default<A>(
1443 addr: A,
1444 ) -> Flow<Vec<u8>, Vec<u8>, StreamCompletion<TcpConnection>>
1445 where
1446 A: ToSocketAddrs + Clone + Send + Sync + 'static,
1447 {
1448 Self::outgoing_connection(addr, DEFAULT_CHUNK_SIZE)
1449 }
1450
1451 #[must_use]
1458 pub fn bind<A>(
1459 addr: A,
1460 chunk_size: usize,
1461 ) -> Source<TcpIncomingConnection, StreamCompletion<TcpBinding>>
1462 where
1463 A: ToSocketAddrs + Clone + Send + Sync + 'static,
1464 {
1465 assert!(chunk_size > 0, "chunk size must be greater than zero");
1466 Source::from_materialized_factory(move |_materializer| {
1467 let (demand_sender, demand_receiver) = mpsc::channel(1);
1468 let (cancel_sender, cancel_receiver) = watch::channel(false);
1469 let (binding_sender, binding_receiver) = oneshot::channel();
1470 let terminal = Arc::new(Mutex::new(None));
1471 let terminal_for_task = Arc::clone(&terminal);
1472 let addr = addr.clone();
1473
1474 crate::stream::stream_tokio_runtime().spawn(async move {
1475 let result = AssertUnwindSafe(run_tcp_bind_task(
1476 addr,
1477 chunk_size,
1478 demand_receiver,
1479 cancel_receiver,
1480 binding_sender,
1481 Arc::clone(&terminal_for_task),
1482 ))
1483 .catch_unwind()
1484 .await;
1485 if result.is_err() {
1486 finish_terminal(
1487 &terminal_for_task,
1488 DemandTerminal::Error(StreamError::AbruptTermination),
1489 );
1490 }
1491 });
1492
1493 Ok((
1494 Box::new(DemandSourceStream {
1495 demands: demand_sender,
1496 cancel: cancel_sender,
1497 terminal,
1498 done: false,
1499 }) as BoxStream<TcpIncomingConnection>,
1500 StreamCompletion::from_receiver(binding_receiver, None),
1501 ))
1502 })
1503 }
1504
1505 #[must_use]
1507 pub fn bind_default<A>(addr: A) -> Source<TcpIncomingConnection, StreamCompletion<TcpBinding>>
1508 where
1509 A: ToSocketAddrs + Clone + Send + Sync + 'static,
1510 {
1511 Self::bind(addr, DEFAULT_CHUNK_SIZE)
1512 }
1513}
1514
1515fn tcp_flow_from_stream(
1516 stream: TcpStream,
1517 chunk_size: usize,
1518) -> StreamResult<Flow<Vec<u8>, Vec<u8>, TcpConnection>> {
1519 let connection = TcpConnection {
1525 local_addr: stream.local_addr().map_err(io_error)?,
1526 remote_addr: stream.peer_addr().map_err(io_error)?,
1527 };
1528 let (read_half, write_half) = stream.into_split();
1529 let source = single_use_async_read_source(read_half, chunk_size);
1530 let sink = single_use_async_write_sink(write_half);
1531 Ok(Flow::from_sink_and_source(sink, source).map_materialized_value(move |_| connection))
1538}
1539
1540fn single_use_async_read_source<R>(reader: R, chunk_size: usize) -> TokioByteSource
1541where
1542 R: AsyncRead + Unpin + Send + 'static,
1543{
1544 let reader = Arc::new(Mutex::new(Some(reader)));
1545 Source::from_materialized_factory(move |_materializer| {
1546 let reader = Arc::clone(&reader);
1547 Ok(async_read_source(
1548 move || async move {
1549 reader
1550 .lock()
1551 .expect("single-use async reader poisoned")
1552 .take()
1553 .ok_or_else(|| std::io::Error::other("async reader already materialized"))
1554 },
1555 chunk_size,
1556 chunk_size,
1557 TCP_READ_AHEAD_CHUNKS,
1558 ))
1559 })
1560}
1561
1562fn single_use_async_write_sink<W>(writer: W) -> TokioByteSink
1563where
1564 W: AsyncWrite + Unpin + Send + 'static,
1565{
1566 let writer = Arc::new(Mutex::new(Some(writer)));
1567 async_write_sink(move || {
1568 let writer = Arc::clone(&writer);
1569 async move {
1570 writer
1571 .lock()
1572 .expect("single-use async writer poisoned")
1573 .take()
1574 .ok_or_else(|| std::io::Error::other("async writer already materialized"))
1575 }
1576 })
1577}
1578
1579async fn run_tcp_bind_task<A>(
1580 addr: A,
1581 chunk_size: usize,
1582 mut demands: mpsc::Receiver<std_mpsc::Sender<DemandResponse<TcpIncomingConnection>>>,
1583 mut cancel: watch::Receiver<bool>,
1584 binding_sender: oneshot::Sender<StreamResult<TcpBinding>>,
1585 terminal: Arc<Mutex<Option<DemandTerminal>>>,
1586) where
1587 A: ToSocketAddrs + Send + 'static,
1588{
1589 let listener = match TcpListener::bind(addr).await {
1590 Ok(listener) => listener,
1591 Err(error) => {
1592 let error = io_error(error);
1593 finish_terminal(&terminal, DemandTerminal::Error(error.clone()));
1594 let _ = binding_sender.send(Err(error));
1595 return;
1596 }
1597 };
1598 let local_addr = match listener.local_addr() {
1599 Ok(local_addr) => local_addr,
1600 Err(error) => {
1601 let error = io_error(error);
1602 finish_terminal(&terminal, DemandTerminal::Error(error.clone()));
1603 let _ = binding_sender.send(Err(error));
1604 return;
1605 }
1606 };
1607 let _ = binding_sender.send(Ok(TcpBinding { local_addr }));
1608
1609 loop {
1610 let Some(reply) = next_demand(&mut demands, &mut cancel).await else {
1611 finish_terminal(&terminal, DemandTerminal::Error(StreamError::Cancelled));
1612 return;
1613 };
1614
1615 let (stream, remote_addr) = loop {
1616 let accepted = tokio::select! {
1617 accepted = listener.accept() => accepted,
1618 changed = cancel.changed() => {
1619 let _ = changed;
1620 finish_terminal(&terminal, DemandTerminal::Error(StreamError::Cancelled));
1621 return;
1622 }
1623 };
1624
1625 match accepted {
1626 Ok(accepted) => break accepted,
1627 Err(error) if is_transient_accept_error(&error) => continue,
1628 Err(error) => {
1629 let error = io_error(error);
1633 finish_terminal(&terminal, DemandTerminal::Error(error.clone()));
1634 let _ = reply.send(DemandResponse::Error(error));
1635 return;
1636 }
1637 }
1638 };
1639
1640 let incoming = tcp_incoming_connection(stream, remote_addr, local_addr, chunk_size);
1641 if reply.send(DemandResponse::Item(incoming)).is_err() {
1642 finish_terminal(&terminal, DemandTerminal::Error(StreamError::Cancelled));
1643 return;
1644 }
1645 }
1646}
1647
1648fn is_transient_accept_error(error: &std::io::Error) -> bool {
1649 matches!(
1650 error.kind(),
1651 std::io::ErrorKind::Interrupted
1652 | std::io::ErrorKind::ConnectionAborted
1653 | std::io::ErrorKind::ConnectionReset
1654 ) || error.raw_os_error().is_some_and(is_transient_accept_errno)
1655}
1656
1657#[cfg(target_os = "linux")]
1658fn is_transient_accept_errno(code: i32) -> bool {
1659 matches!(code, 4 | 103 | 104)
1660}
1661
1662#[cfg(not(target_os = "linux"))]
1663fn is_transient_accept_errno(_code: i32) -> bool {
1664 false
1665}
1666
1667fn tcp_incoming_connection(
1668 stream: TcpStream,
1669 remote_addr: SocketAddr,
1670 local_addr: SocketAddr,
1671 chunk_size: usize,
1672) -> TcpIncomingConnection {
1673 let connection = TcpConnection {
1674 local_addr,
1675 remote_addr,
1676 };
1677 let (read_half, write_half) = stream.into_split();
1678 let source = single_use_async_read_source(read_half, chunk_size);
1679 let sink = single_use_async_write_sink(write_half);
1680 TcpIncomingConnection {
1681 connection,
1682 source,
1683 sink,
1684 }
1685}
1686
1687#[cfg(test)]
1688mod tests {
1689 use super::*;
1690 use crate::testkit::TestSink;
1691 use crate::{Framing, Keep, Sink, Source};
1692 use std::pin::Pin;
1693 use std::sync::atomic::{AtomicBool as StdAtomicBool, Ordering as StdOrdering};
1694 use std::task::{Context, Poll};
1695 use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
1696
1697 fn unique_temp_path(name: &str) -> PathBuf {
1698 let nanos = SystemTime::now()
1699 .duration_since(UNIX_EPOCH)
1700 .expect("clock after epoch")
1701 .as_nanos();
1702 std::env::temp_dir().join(format!(
1703 "datum-wp12b-{name}-{}-{nanos}.bin",
1704 std::process::id()
1705 ))
1706 }
1707
1708 fn wait_until(timeout: Duration, condition: impl Fn() -> bool) -> bool {
1709 let deadline = Instant::now() + timeout;
1710 while Instant::now() < deadline {
1711 if condition() {
1712 return true;
1713 }
1714 thread::sleep(Duration::from_millis(5));
1715 }
1716 condition()
1717 }
1718
1719 struct PendingWriter {
1720 polled: Arc<StdAtomicBool>,
1721 dropped: Arc<StdAtomicBool>,
1722 }
1723
1724 impl AsyncWrite for PendingWriter {
1725 fn poll_write(
1726 self: Pin<&mut Self>,
1727 _cx: &mut Context<'_>,
1728 _buf: &[u8],
1729 ) -> Poll<std::io::Result<usize>> {
1730 self.polled.store(true, StdOrdering::SeqCst);
1731 Poll::Pending
1732 }
1733
1734 fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
1735 self.polled.store(true, StdOrdering::SeqCst);
1736 Poll::Pending
1737 }
1738
1739 fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
1740 self.polled.store(true, StdOrdering::SeqCst);
1741 Poll::Pending
1742 }
1743 }
1744
1745 impl Drop for PendingWriter {
1746 fn drop(&mut self) {
1747 self.dropped.store(true, StdOrdering::SeqCst);
1748 }
1749 }
1750
1751 #[test]
1752 fn tokio_file_io_round_trips_bytes_and_reports_counts() {
1753 let path = unique_temp_path("roundtrip");
1754 let write_completion = Source::from_iter([b"ab".to_vec(), b"cd".to_vec()])
1755 .run_with(TokioFileIO::to_path(path.clone()))
1756 .expect("tokio file sink materializes");
1757 let write_result = write_completion.wait().expect("tokio file write completes");
1758 assert_eq!(write_result.bytes(), 4);
1759 assert_eq!(write_result.status(), Ok(()));
1760
1761 let (read_completion, collected) = TokioFileIO::from_path(path.clone(), 2)
1762 .to_mat(Sink::collect(), Keep::both)
1763 .run()
1764 .expect("tokio file source materializes");
1765 assert_eq!(
1766 collected.wait().expect("collect completes"),
1767 vec![b"ab".to_vec(), b"cd".to_vec()]
1768 );
1769 let read_result = read_completion.wait().expect("read completion available");
1770 assert_eq!(read_result.bytes(), 4);
1771 assert_eq!(read_result.status(), Ok(()));
1772
1773 std::fs::remove_file(path).expect("remove roundtrip file");
1774 }
1775
1776 #[test]
1777 fn tokio_file_source_surfaces_open_failure() {
1778 let missing = unique_temp_path("missing");
1779 let (read_completion, collected) = TokioFileIO::from_path(missing, 4)
1780 .to_mat(Sink::collect(), Keep::both)
1781 .run()
1782 .expect("tokio file source materializes despite open failure");
1783 let stream_error = collected.wait().expect_err("collect fails");
1784 assert!(matches!(stream_error, StreamError::Failed(_)));
1785 let read_result = read_completion.wait().expect("io result available");
1786 assert_eq!(read_result.bytes(), 0);
1787 assert!(matches!(read_result.status(), Err(StreamError::Failed(_))));
1788 }
1789
1790 #[test]
1791 fn tokio_file_source_composes_with_framing_and_sink() {
1792 let path = unique_temp_path("framing");
1793 std::fs::write(&path, b"alpha\nbeta\ngamma\n").expect("write framed seed file");
1794
1795 let frames = TokioFileIO::from_path(path.clone(), 5)
1796 .via(Framing::delimiter(b"\n".to_vec(), 64, true))
1797 .run_with(Sink::collect())
1798 .expect("framed file stream materializes")
1799 .wait()
1800 .expect("framed file stream completes");
1801
1802 assert_eq!(
1803 frames,
1804 vec![b"alpha".to_vec(), b"beta".to_vec(), b"gamma".to_vec()]
1805 );
1806 std::fs::remove_file(path).expect("remove framed file");
1807 }
1808
1809 #[test]
1810 fn tokio_file_source_preserves_requested_chunk_boundaries() {
1811 let path = unique_temp_path("chunk-boundaries");
1812 let chunk_size = 8192;
1813 let tail_size = 13;
1814 let data_len = FILE_INTERNAL_READ_SIZE + tail_size;
1815 let data: Vec<u8> = (0..data_len).map(|index| (index % 251) as u8).collect();
1816 std::fs::write(&path, &data).expect("write chunk boundary seed file");
1817
1818 let (read_completion, chunks) = TokioFileIO::from_path(path.clone(), chunk_size)
1819 .to_mat(Sink::collect(), Keep::both)
1820 .run()
1821 .expect("tokio file source materializes");
1822 let chunks = chunks.wait().expect("chunk boundary stream completes");
1823
1824 assert!(chunks.len() > 1);
1825 for chunk in &chunks[..chunks.len() - 1] {
1826 assert_eq!(chunk.len(), chunk_size);
1827 }
1828 assert_eq!(chunks.last().expect("tail chunk exists").len(), tail_size);
1829 let reassembled: Vec<u8> = chunks.into_iter().flatten().collect();
1830 assert_eq!(reassembled, data);
1831 assert_eq!(
1832 read_completion
1833 .wait()
1834 .expect("read completion available")
1835 .bytes(),
1836 data_len as u64
1837 );
1838
1839 std::fs::remove_file(path).expect("remove chunk boundary file");
1840 }
1841
1842 #[cfg(all(feature = "io-uring-file", target_os = "linux"))]
1843 #[test]
1844 fn uring_file_io_round_trips_bytes_and_reports_counts() {
1845 let path = unique_temp_path("uring-roundtrip");
1846 let write_completion = Source::from_iter([b"ab".to_vec(), b"cd".to_vec()])
1847 .run_with(UringFileIO::to_path(path.clone()))
1848 .expect("uring file sink materializes");
1849 let write_result = write_completion.wait().expect("uring file write completes");
1850 assert_eq!(write_result.bytes(), 4);
1851 assert_eq!(write_result.status(), Ok(()));
1852
1853 let (read_completion, collected) = UringFileIO::from_path(path.clone(), 2)
1854 .to_mat(Sink::collect(), Keep::both)
1855 .run()
1856 .expect("uring file source materializes");
1857 assert_eq!(
1858 collected.wait().expect("collect completes"),
1859 vec![b"ab".to_vec(), b"cd".to_vec()]
1860 );
1861 let read_result = read_completion.wait().expect("read completion available");
1862 assert_eq!(read_result.bytes(), 4);
1863 assert_eq!(read_result.status(), Ok(()));
1864
1865 std::fs::remove_file(path).expect("remove uring roundtrip file");
1866 }
1867
1868 #[cfg(all(feature = "io-uring-file", target_os = "linux"))]
1869 #[test]
1870 fn uring_file_source_surfaces_open_failure() {
1871 let missing = unique_temp_path("uring-missing");
1872 let (read_completion, collected) = UringFileIO::from_path(missing, 4)
1873 .to_mat(Sink::collect(), Keep::both)
1874 .run()
1875 .expect("uring file source materializes despite open failure");
1876 let stream_error = collected.wait().expect_err("collect fails");
1877 assert!(matches!(stream_error, StreamError::Failed(_)));
1878 let read_result = read_completion.wait().expect("io result available");
1879 assert_eq!(read_result.bytes(), 0);
1880 assert!(matches!(read_result.status(), Err(StreamError::Failed(_))));
1881 }
1882
1883 #[cfg(all(feature = "io-uring-file", target_os = "linux"))]
1884 #[test]
1885 fn uring_file_sink_surfaces_open_failure() {
1886 let path = unique_temp_path("uring-sink-dir");
1887 std::fs::create_dir(&path).expect("create sink failure directory");
1888
1889 let completion = Source::single(b"blocked".to_vec())
1890 .run_with(UringFileIO::to_path(path.clone()))
1891 .expect("uring file sink materializes despite open failure");
1892 let result = completion.wait().expect("uring sink result available");
1893 assert_eq!(result.bytes(), 0);
1894 assert!(matches!(result.status(), Err(StreamError::Failed(_))));
1895
1896 std::fs::remove_dir(path).expect("remove sink failure directory");
1897 }
1898
1899 #[cfg(all(feature = "io-uring-file", target_os = "linux"))]
1900 #[test]
1901 fn uring_file_source_preserves_requested_chunk_boundaries() {
1902 let path = unique_temp_path("uring-chunk-boundaries");
1903 let chunk_size = 8192;
1904 let tail_size = 13;
1905 let data_len = FILE_INTERNAL_READ_SIZE + tail_size;
1906 let data: Vec<u8> = (0..data_len).map(|index| (index % 251) as u8).collect();
1907 std::fs::write(&path, &data).expect("write uring chunk boundary seed file");
1908
1909 let (read_completion, chunks) = UringFileIO::from_path(path.clone(), chunk_size)
1910 .to_mat(Sink::collect(), Keep::both)
1911 .run()
1912 .expect("uring file source materializes");
1913 let chunks = chunks.wait().expect("chunk boundary stream completes");
1914
1915 assert!(chunks.len() > 1);
1916 for chunk in &chunks[..chunks.len() - 1] {
1917 assert_eq!(chunk.len(), chunk_size);
1918 }
1919 assert_eq!(chunks.last().expect("tail chunk exists").len(), tail_size);
1920 let reassembled: Vec<u8> = chunks.into_iter().flatten().collect();
1921 assert_eq!(reassembled, data);
1922 assert_eq!(
1923 read_completion
1924 .wait()
1925 .expect("read completion available")
1926 .bytes(),
1927 data_len as u64
1928 );
1929
1930 std::fs::remove_file(path).expect("remove uring chunk boundary file");
1931 }
1932
1933 #[cfg(all(feature = "io-uring-file", target_os = "linux"))]
1934 #[test]
1935 fn uring_file_source_cancellation_reports_cancelled() {
1936 let path = unique_temp_path("uring-cancel");
1937 let data_len = FILE_INTERNAL_READ_SIZE * 4;
1938 std::fs::write(&path, vec![b'z'; data_len]).expect("write uring cancel seed file");
1939
1940 let (completion, ignored) = UringFileIO::from_path(path.clone(), 8192)
1941 .take(1)
1942 .to_mat(Sink::ignore(), Keep::both)
1943 .run()
1944 .expect("uring cancellation source materializes");
1945 ignored.wait().expect("downstream ignore completes");
1946 let result = completion.wait().expect("uring read completion available");
1947
1948 assert!(result.bytes() > 0);
1949 assert!(matches!(result.status(), Err(StreamError::Cancelled)));
1950
1951 std::fs::remove_file(path).expect("remove uring cancel file");
1952 }
1953
1954 #[test]
1955 fn tokio_sink_cancellation_unblocks_pending_writer_completion_wait() {
1956 let polled = Arc::new(StdAtomicBool::new(false));
1957 let dropped = Arc::new(StdAtomicBool::new(false));
1958 let completion = Source::single(b"blocked".to_vec())
1959 .run_with(async_write_sink({
1960 let polled = Arc::clone(&polled);
1961 let dropped = Arc::clone(&dropped);
1962 move || {
1963 let polled = Arc::clone(&polled);
1964 let dropped = Arc::clone(&dropped);
1965 async move { Ok(PendingWriter { polled, dropped }) }
1966 }
1967 }))
1968 .expect("pending writer sink materializes");
1969
1970 assert!(wait_until(Duration::from_secs(1), || {
1971 polled.load(StdOrdering::SeqCst)
1972 }));
1973 drop(completion);
1974 assert!(wait_until(Duration::from_secs(1), || {
1975 dropped.load(StdOrdering::SeqCst)
1976 }));
1977 }
1978
1979 #[test]
1980 fn tokio_tcp_accept_error_classifier_retries_only_connection_races() {
1981 assert!(is_transient_accept_error(&std::io::Error::new(
1982 std::io::ErrorKind::Interrupted,
1983 "interrupted"
1984 )));
1985 assert!(is_transient_accept_error(&std::io::Error::new(
1986 std::io::ErrorKind::ConnectionAborted,
1987 "aborted before accept"
1988 )));
1989 assert!(is_transient_accept_error(&std::io::Error::new(
1990 std::io::ErrorKind::ConnectionReset,
1991 "reset before accept"
1992 )));
1993 assert!(!is_transient_accept_error(&std::io::Error::other(
1994 "fd pressure"
1995 )));
1996 }
1997
1998 #[test]
1999 fn tokio_source_cancellation_observed_promptly_under_wake_on_send() {
2000 let cancelled_detected = Arc::new(AtomicBool::new(false));
2013 let detected = Arc::clone(&cancelled_detected);
2014
2015 let source = Source::from_materialized_factory(move |_materializer| {
2016 let d = Arc::clone(&detected);
2017 let mut i = 0_u64;
2018 let stream: BoxStream<Vec<u8>> = Box::new(std::iter::from_fn(move || {
2019 let is_cancelled = || {
2022 crate::stream::current_stream_cancelled()
2023 .as_ref()
2024 .is_some_and(|c| c.load(Ordering::SeqCst))
2025 };
2026 if is_cancelled() {
2027 d.store(true, Ordering::SeqCst);
2028 return Some(Err(StreamError::Cancelled));
2029 }
2030 if i < 4 {
2031 i += 1;
2032 return Some(Ok(vec![0_u8; 8192]));
2033 }
2034 std::thread::park_timeout(Duration::from_millis(50));
2037 if is_cancelled() {
2038 d.store(true, Ordering::SeqCst);
2039 return Some(Err(StreamError::Cancelled));
2040 }
2041 Some(Ok(vec![0_u8]))
2044 }));
2045 Ok((stream, NotUsed))
2046 });
2047
2048 let completion = source
2049 .run_with(Sink::ignore())
2050 .expect("cancellation source materializes");
2051
2052 let cancel_thread = thread::spawn(move || {
2053 thread::sleep(Duration::from_millis(20));
2054 drop(completion);
2055 });
2056
2057 let deadline = Instant::now() + Duration::from_secs(3);
2059 loop {
2060 if cancelled_detected.load(Ordering::SeqCst) {
2061 break;
2062 }
2063 assert!(
2064 Instant::now() < deadline,
2065 "cancellation not observed within 3 s; the park‑wake path may be broken"
2066 );
2067 thread::park_timeout(Duration::from_millis(10));
2068 }
2069 cancel_thread.join().expect("cancellation thread joins");
2070 }
2071
2072 #[test]
2073 fn tokio_tcp_bind_and_outgoing_connection_echo_round_trip() {
2074 let (binding_completion, incoming_completion) = TokioTcp::bind("127.0.0.1:0", 1024)
2075 .to_mat(Sink::head(), Keep::both)
2076 .run()
2077 .expect("tcp bind source materializes");
2078 let binding = binding_completion.wait().expect("tcp binding succeeds");
2079
2080 let client_completion = Source::single(b"ping".to_vec())
2081 .via(TokioTcp::outgoing_connection(binding.local_addr(), 1024))
2082 .run_with(Sink::head())
2083 .expect("client stream materializes");
2084
2085 let incoming = incoming_completion
2086 .wait()
2087 .expect("incoming connection accepted");
2088 let (incoming_source, incoming_sink) = incoming.into_parts();
2089 let server_read = incoming_source
2090 .run_with(Sink::head())
2091 .expect("server read materializes")
2092 .wait()
2093 .expect("server reads request");
2094 assert_eq!(server_read, b"ping".to_vec());
2095
2096 let server_write = Source::single(server_read)
2097 .run_with(incoming_sink)
2098 .expect("server write materializes");
2099 let write_result = server_write.wait().expect("server write completes");
2100 assert_eq!(write_result.bytes(), 4);
2101 assert_eq!(write_result.status(), Ok(()));
2102
2103 assert_eq!(
2104 client_completion.wait().expect("client receives echo"),
2105 b"ping".to_vec()
2106 );
2107 }
2108
2109 #[test]
2110 fn tokio_tcp_bind_accepts_multiple_connections_on_successive_demands() {
2111 let (binding_completion, mut incoming_probe) = TokioTcp::bind("127.0.0.1:0", 1024)
2112 .to_mat(TestSink::probe(), Keep::both)
2113 .run()
2114 .expect("tcp bind source materializes");
2115 let binding = binding_completion.wait().expect("tcp binding succeeds");
2116 incoming_probe.set_timeout(Duration::from_secs(30));
2121
2122 incoming_probe.request(1);
2123 let first_client =
2124 std::net::TcpStream::connect(binding.local_addr()).expect("first client connects");
2125 let first = incoming_probe.expect_next();
2126 assert_eq!(first.local_addr(), binding.local_addr());
2127 assert_eq!(
2128 first.remote_addr(),
2129 first_client.local_addr().expect("first client local addr")
2130 );
2131
2132 incoming_probe.request(1);
2133 let second_client =
2134 std::net::TcpStream::connect(binding.local_addr()).expect("second client connects");
2135 let second = incoming_probe.expect_next();
2136 assert_eq!(second.local_addr(), binding.local_addr());
2137 assert_eq!(
2138 second.remote_addr(),
2139 second_client
2140 .local_addr()
2141 .expect("second client local addr")
2142 );
2143 assert_ne!(first.remote_addr(), second.remote_addr());
2144
2145 incoming_probe.cancel();
2146 }
2147}