1use std::any::Any;
5use std::collections::VecDeque;
6use std::future::Future;
7use std::pin::Pin;
8use std::sync::Arc;
9use std::sync::atomic::AtomicUsize;
10use std::sync::atomic::Ordering;
11use std::task::Context;
12use std::task::Poll;
13
14use futures::FutureExt;
15use futures::Stream;
16use futures::StreamExt;
17use futures::channel::mpsc;
18use futures::channel::oneshot;
19use futures::future;
20use futures::future::BoxFuture;
21use futures::future::Shared;
22use futures::stream::BoxStream;
23use futures::stream::Fuse;
24use futures::stream::SelectAll;
25use parking_lot::Mutex;
26use vortex_array::buffer::BufferHandle;
27use vortex_buffer::Alignment;
28use vortex_buffer::ByteBuffer;
29use vortex_error::VortexExpect;
30use vortex_error::VortexResult;
31use vortex_error::vortex_err;
32use vortex_error::vortex_panic;
33use vortex_io::ReadAtRequest;
34use vortex_io::ReadAtStream;
35use vortex_io::VortexReadAt;
36use vortex_io::runtime::Handle;
37use vortex_io::runtime::JoinOutcome;
38use vortex_layout::segments::SegmentFuture;
39use vortex_layout::segments::SegmentId;
40use vortex_layout::segments::SegmentSource;
41use vortex_metrics::Counter;
42use vortex_metrics::Histogram;
43use vortex_metrics::Label;
44use vortex_metrics::MetricBuilder;
45use vortex_metrics::MetricsRegistry;
46
47use crate::SegmentSpec;
48use crate::read::IoRequest;
49use crate::read::IoRequestStream;
50use crate::read::ReadRequest;
51use crate::read::RequestId;
52
53#[derive(Debug)]
54pub enum ReadEvent {
56 Request(ReadRequest),
58 Polled(RequestId),
60 Dropped(RequestId),
62}
63
64type SharedDriver = Shared<BoxFuture<'static, ()>>;
92
93type DriverPanic = Arc<Mutex<Option<Box<dyn Any + Send>>>>;
96
97fn validate_read_result(
98 request: &IoRequest,
99 result: VortexResult<BufferHandle>,
100) -> VortexResult<BufferHandle> {
101 result.and_then(|buffer| {
102 if request.len() != buffer.len() {
103 return Err(vortex_err!(
104 "FileSegmentSource: expected buffer of length {} but received {}. {:?}",
105 request.len(),
106 buffer.len(),
107 request
108 ));
109 }
110 Ok(buffer)
111 })
112}
113
114type IoBatchStream = Fuse<BoxStream<'static, Vec<IoRequest>>>;
115
116enum ReadRangeResultsState {
117 Reading(ReadAtStream),
118 Missing,
119}
120
121struct ReadRangeResults {
123 state: ReadRangeResultsState,
124 remaining: Vec<Option<IoRequest>>,
125}
126
127impl ReadRangeResults {
128 fn new(results: ReadAtStream, requests: Vec<IoRequest>) -> Self {
129 Self {
130 state: ReadRangeResultsState::Reading(results),
131 remaining: requests.into_iter().map(Some).collect(),
132 }
133 }
134}
135
136impl Stream for ReadRangeResults {
137 type Item = (IoRequest, VortexResult<BufferHandle>);
138
139 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
140 loop {
141 match &mut self.state {
142 ReadRangeResultsState::Reading(results) => match results.poll_next_unpin(cx) {
143 Poll::Ready(Some((request, result))) => {
144 let Some(position) = self.remaining.iter().position(|req| {
145 req.as_ref().is_some_and(|req| {
146 req.offset() == request.offset
147 && req.len() == request.length
148 && req.alignment() == request.alignment
149 })
150 }) else {
151 tracing::warn!(?request, "reader returned an unknown range");
152 continue;
153 };
154 let req = self.remaining[position]
155 .take()
156 .vortex_expect("matched request is present");
157 return Poll::Ready(Some((req, result)));
158 }
159 Poll::Ready(None) => self.state = ReadRangeResultsState::Missing,
160 Poll::Pending => return Poll::Pending,
161 },
162 ReadRangeResultsState::Missing => {
163 let Some(req) = self.remaining.iter_mut().find_map(Option::take) else {
164 return Poll::Ready(None);
165 };
166 let error = vortex_err!(
167 "FileSegmentSource: read_ranges ended before resolving request. {:?}",
168 req
169 );
170 return Poll::Ready(Some((req, Err(error))));
171 }
172 }
173 }
174 }
175}
176
177struct ReadDriver<R> {
179 reader: Arc<R>,
180 batches: IoBatchStream,
181 pending: VecDeque<IoRequest>,
182 reads: SelectAll<ReadRangeResults>,
183 num_active: usize,
184 batches_done: bool,
185 concurrency: usize,
186 metrics: RequestMetrics,
187}
188
189impl<R: VortexReadAt> ReadDriver<R> {
190 fn new(
191 reader: R,
192 batches: BoxStream<'static, Vec<IoRequest>>,
193 concurrency: usize,
194 metrics: RequestMetrics,
195 ) -> Self {
196 Self {
197 reader: Arc::new(reader),
198 batches: batches.fuse(),
199 pending: VecDeque::new(),
200 reads: SelectAll::new(),
201 num_active: 0,
202 batches_done: false,
203 concurrency,
204 metrics,
205 }
206 }
207
208 fn submit_pending(&mut self) {
209 while self.num_active < self.concurrency && !self.pending.is_empty() {
210 let batch_len = (self.concurrency - self.num_active).min(self.pending.len());
211 let reqs = self.pending.drain(..batch_len).collect::<Vec<_>>();
212 self.num_active += batch_len;
213
214 self.metrics.read_ranges_calls.add(1);
215 self.metrics.read_ranges_num_ranges.update(batch_len as f64);
216 if batch_len > 1 {
217 self.metrics.read_ranges_multi.add(1);
218 }
219 tracing::trace!(
220 target: "vortex_file::read_ranges",
221 num_ranges = batch_len,
222 num_active = self.num_active,
223 "submitting positional read batch"
224 );
225
226 let requests = reqs
227 .iter()
228 .map(|req| ReadAtRequest::new(req.offset(), req.len(), req.alignment()))
229 .collect::<Vec<_>>()
230 .into();
231 let results = self.reader.read_ranges(requests);
232 self.reads.push(ReadRangeResults::new(results, reqs));
233 }
234 }
235}
236
237impl<R: VortexReadAt> Stream for ReadDriver<R> {
238 type Item = ();
239
240 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
241 let this = self.as_mut().get_mut();
242
243 if !this.batches_done {
245 loop {
246 match this.batches.poll_next_unpin(cx) {
247 Poll::Ready(Some(batch)) => this.pending.extend(batch),
248 Poll::Ready(None) => {
249 this.batches_done = true;
250 break;
251 }
252 Poll::Pending => break,
253 }
254 }
255 }
256
257 this.submit_pending();
258
259 if this.batches_done && this.num_active == 0 {
260 return Poll::Ready(None);
261 }
262
263 match this.reads.poll_next_unpin(cx) {
264 Poll::Ready(Some((req, result))) => {
265 this.num_active -= 1;
266 let result = validate_read_result(&req, result);
267 req.resolve(result);
268 Poll::Ready(Some(()))
269 }
270 Poll::Ready(None) if this.num_active == 0 => Poll::Pending,
271 Poll::Ready(None) => {
272 vortex_panic!("read result streams ended with active requests")
273 }
274 Poll::Pending => Poll::Pending,
275 }
276 }
277}
278
279pub struct FileSegmentSource {
280 segments: Arc<[SegmentSpec]>,
281 events: mpsc::UnboundedSender<ReadEvent>,
283 driver: SharedDriver,
285 driver_panic: DriverPanic,
287 next_id: Arc<AtomicUsize>,
289}
290
291impl FileSegmentSource {
292 pub fn open<R: VortexReadAt + Clone>(
297 segments: Arc<[SegmentSpec]>,
298 reader: R,
299 handle: Handle,
300 metrics: RequestMetrics,
301 ) -> Self {
302 let (send, recv) = mpsc::unbounded();
303
304 let max_alignment = segments
305 .iter()
306 .map(|segment| segment.alignment)
307 .max()
308 .unwrap_or_else(Alignment::none);
309 let coalesce_config = reader.coalesce_config().map(|mut config| {
310 let extra = (max_alignment.as_usize() as u64).saturating_sub(1);
313 config.max_size = config.max_size.saturating_add(extra);
314 config
315 });
316 let concurrency = reader.concurrency();
317 if concurrency == 0 {
318 vortex_panic!(
319 "VortexReadAt::concurrency returned 0 (uri={:?}); this would stall I/O",
320 reader.uri()
321 );
322 }
323
324 let stream = IoRequestStream::new(
325 StreamExt::boxed(recv),
326 coalesce_config,
327 max_alignment,
328 concurrency,
329 metrics.clone(),
330 )
331 .boxed();
332
333 let drive_fut = ReadDriver::new(reader, stream, concurrency, metrics).collect::<()>();
334
335 let mut task = handle.spawn(drive_fut);
338 let driver_panic: DriverPanic = Arc::new(Mutex::new(None));
339 let driver = {
340 let driver_panic = Arc::clone(&driver_panic);
341 async move {
342 if let JoinOutcome::Panicked(panic) = future::poll_fn(|cx| task.poll_join(cx)).await
346 {
347 *driver_panic.lock() = Some(panic);
348 }
349 }
350 .boxed()
351 .shared()
352 };
353
354 Self {
355 segments,
356 events: send,
357 driver,
358 driver_panic,
359 next_id: Arc::new(AtomicUsize::new(0)),
360 }
361 }
362}
363
364impl SegmentSource for FileSegmentSource {
365 fn request(&self, id: SegmentId) -> SegmentFuture {
366 let spec = *match self.segments.get(*id as usize) {
369 Some(spec) => spec,
370 None => {
371 return future::ready(Err(vortex_err!("Missing segment: {}", id))).boxed();
372 }
373 };
374
375 let SegmentSpec {
376 offset,
377 length,
378 alignment,
379 } = spec;
380
381 let (send, recv) = oneshot::channel();
382 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
383 let event = ReadEvent::Request(ReadRequest {
384 id,
385 offset,
386 length: length as usize,
387 alignment,
388 callback: send,
389 });
390
391 if let Err(e) = self.events.unbounded_send(event) {
393 return future::ready(Err(vortex_err!("Failed to submit read request: {e}"))).boxed();
394 }
395
396 let fut = ReadFuture {
397 id,
398 recv,
399 polled: false,
400 finished: false,
401 events: self.events.clone(),
402 driver: self.driver.clone(),
403 driver_panic: Arc::clone(&self.driver_panic),
404 };
405
406 fut.boxed()
408 }
409}
410
411struct ReadFuture {
416 id: usize,
417 recv: oneshot::Receiver<VortexResult<BufferHandle>>,
418 polled: bool,
419 finished: bool,
420 events: mpsc::UnboundedSender<ReadEvent>,
421 driver: SharedDriver,
422 driver_panic: DriverPanic,
423}
424
425impl Future for ReadFuture {
426 type Output = VortexResult<BufferHandle>;
427
428 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
429 match self.recv.poll_unpin(cx) {
430 Poll::Ready(Ok(result)) => {
434 self.finished = true;
435 Poll::Ready(result)
436 }
437 Poll::Ready(Err(e)) => match self.driver.poll_unpin(cx) {
441 Poll::Ready(()) => {
442 self.finished = true;
443 if let Some(panic) = self.driver_panic.lock().take() {
446 std::panic::resume_unwind(panic);
447 }
448 Poll::Ready(Err(vortex_err!("ReadRequest dropped by runtime: {e}")))
449 }
450 Poll::Pending => Poll::Pending,
451 },
452 Poll::Pending if !self.polled => {
453 self.polled = true;
454 match self.events.unbounded_send(ReadEvent::Polled(self.id)) {
456 Ok(()) => Poll::Pending,
457 Err(e) => Poll::Ready(Err(vortex_err!("ReadRequest dropped by runtime: {e}"))),
458 }
459 }
460 _ => Poll::Pending,
461 }
462 }
463}
464
465impl Drop for ReadFuture {
466 fn drop(&mut self) {
467 if self.finished {
469 return;
470 }
471
472 drop(self.events.unbounded_send(ReadEvent::Dropped(self.id)));
474 }
475}
476
477#[derive(Clone)]
479pub struct RequestMetrics {
480 pub individual_requests: Counter,
482 pub coalesced_requests: Counter,
484 pub num_requests_coalesced: Histogram,
486 pub read_ranges_calls: Counter,
488 pub read_ranges_multi: Counter,
490 pub read_ranges_num_ranges: Histogram,
492}
493
494impl RequestMetrics {
495 pub fn new(metrics_registry: &dyn MetricsRegistry, labels: Vec<Label>) -> Self {
497 Self {
498 individual_requests: MetricBuilder::new(metrics_registry)
499 .add_labels(labels.clone())
500 .counter("io.requests.individual"),
501 coalesced_requests: MetricBuilder::new(metrics_registry)
502 .add_labels(labels.clone())
503 .counter("io.requests.coalesced"),
504 num_requests_coalesced: MetricBuilder::new(metrics_registry)
505 .add_labels(labels.clone())
506 .histogram("io.requests.coalesced.num_coalesced"),
507 read_ranges_calls: MetricBuilder::new(metrics_registry)
508 .add_labels(labels.clone())
509 .counter("io.read_ranges.calls"),
510 read_ranges_multi: MetricBuilder::new(metrics_registry)
511 .add_labels(labels.clone())
512 .counter("io.read_ranges.multi_range_calls"),
513 read_ranges_num_ranges: MetricBuilder::new(metrics_registry)
514 .add_labels(labels)
515 .histogram("io.read_ranges.num_ranges"),
516 }
517 }
518}
519
520pub(crate) struct BufferSegmentSource {
525 buffer: ByteBuffer,
526 segments: Arc<[SegmentSpec]>,
527}
528
529impl BufferSegmentSource {
530 pub fn new(buffer: ByteBuffer, segments: Arc<[SegmentSpec]>) -> Self {
532 Self { buffer, segments }
533 }
534}
535
536impl SegmentSource for BufferSegmentSource {
537 fn request(&self, id: SegmentId) -> SegmentFuture {
538 let spec = match self.segments.get(*id as usize) {
539 Some(spec) => spec,
540 None => {
541 return future::ready(Err(vortex_err!("Missing segment: {}", id))).boxed();
542 }
543 };
544
545 let start = spec.offset as usize;
546 let end = start + spec.length as usize;
547 if end > self.buffer.len() {
548 return future::ready(Err(vortex_err!(
549 "Segment {} range {}..{} out of bounds for buffer of length {}",
550 *id,
551 start,
552 end,
553 self.buffer.len()
554 )))
555 .boxed();
556 }
557
558 let slice = self.buffer.slice(start..end).aligned(spec.alignment);
559 future::ready(Ok(BufferHandle::new_host(slice))).boxed()
560 }
561}
562
563#[cfg(test)]
564mod tests {
565 use std::panic::AssertUnwindSafe;
566 use std::sync::atomic::AtomicBool;
567
568 use futures::future::BoxFuture;
569 use futures::task::ArcWake;
570 use vortex_error::vortex_bail;
571 use vortex_io::runtime::tokio::TokioRuntime;
572 use vortex_layout::segments::SegmentSource;
573 use vortex_metrics::DefaultMetricsRegistry;
574
575 use super::*;
576
577 fn io_request(id: RequestId, offset: u64, length: usize) -> IoRequest {
578 let (callback, _receiver) = oneshot::channel();
579 IoRequest::new_single(ReadRequest {
580 id,
581 offset,
582 length,
583 alignment: Alignment::none(),
584 callback,
585 })
586 }
587
588 #[tokio::test]
589 async fn read_range_results_matches_results_and_reports_missing_requests() {
590 let requests = vec![io_request(0, 0, 4), io_request(1, 4, 4)];
591 let unknown = ReadAtRequest::new(8, 4, Alignment::none());
592 let returned = ReadAtRequest::new(4, 4, Alignment::none());
593 let buffer = BufferHandle::new_host(ByteBuffer::from(vec![0; 4]));
594 let results =
595 futures::stream::iter([(unknown, Ok(buffer.clone())), (returned, Ok(buffer))]).boxed();
596
597 let resolved = ReadRangeResults::new(results, requests)
598 .collect::<Vec<_>>()
599 .await;
600
601 assert_eq!(resolved.len(), 2);
602 assert_eq!(resolved[0].0.offset(), 4);
603 assert!(resolved[0].1.is_ok());
604 assert_eq!(resolved[1].0.offset(), 0);
605 assert!(resolved[1].1.is_err());
606 }
607
608 #[derive(Clone)]
609 struct PanickingReadAt;
610
611 impl VortexReadAt for PanickingReadAt {
612 fn concurrency(&self) -> usize {
613 1
614 }
615
616 fn size(&self) -> BoxFuture<'static, VortexResult<u64>> {
617 async { Ok(4) }.boxed()
618 }
619
620 fn read_at(
621 &self,
622 _offset: u64,
623 _length: usize,
624 _alignment: Alignment,
625 ) -> BoxFuture<'static, VortexResult<BufferHandle>> {
626 async {
627 panic!("read-at panic");
628 }
629 .boxed()
630 }
631 }
632
633 fn panicking_source() -> FileSegmentSource {
634 let segments: Arc<[SegmentSpec]> = Arc::from([SegmentSpec {
635 offset: 0,
636 length: 4,
637 alignment: Alignment::none(),
638 }]);
639 let metrics = DefaultMetricsRegistry::default();
640 FileSegmentSource::open(
641 segments,
642 PanickingReadAt,
643 TokioRuntime::current(),
644 RequestMetrics::new(&metrics, vec![]),
645 )
646 }
647
648 fn panic_message(payload: &(dyn Any + Send)) -> &str {
649 payload
650 .downcast_ref::<&str>()
651 .copied()
652 .or_else(|| payload.downcast_ref::<String>().map(String::as_str))
653 .unwrap_or("<non-string panic>")
654 }
655
656 #[tokio::test]
657 #[should_panic(expected = "read-at panic")]
658 async fn file_segment_source_propagates_read_driver_panic() {
659 let source = panicking_source();
660 let _result = source.request(SegmentId::from(0)).await;
661 }
662
663 #[tokio::test]
666 async fn file_segment_source_read_driver_panic_propagates_deterministically() {
667 for i in 0..100 {
668 let source = panicking_source();
669 let outcome = AssertUnwindSafe(source.request(SegmentId::from(0)))
670 .catch_unwind()
671 .await;
672 assert!(
673 outcome.is_err(),
674 "read-driver panic was not propagated on iteration {i}; \
675 the request resolved instead of panicking"
676 );
677 }
678 }
679
680 #[derive(Default)]
681 struct WakeFlag(AtomicBool);
682
683 impl ArcWake for WakeFlag {
684 fn wake_by_ref(arc_self: &Arc<Self>) {
685 arc_self.0.store(true, Ordering::SeqCst);
686 }
687 }
688
689 #[tokio::test]
694 async fn file_segment_source_panic_propagates_to_all_concurrent_readers() {
695 let source = panicking_source();
696
697 let mut readers: Vec<_> = (0..8)
698 .map(|_| {
699 (
700 source.request(SegmentId::from(0)),
701 Arc::new(WakeFlag::default()),
702 )
703 })
704 .collect();
705
706 let poll_reader = |reader: &mut SegmentFuture, flag: &Arc<WakeFlag>| {
707 let waker = futures::task::waker(Arc::clone(flag));
708 let mut cx = Context::from_waker(&waker);
709 std::panic::catch_unwind(AssertUnwindSafe(|| reader.poll_unpin(&mut cx)))
710 };
711
712 for (reader, flag) in &mut readers {
714 assert!(
715 matches!(poll_reader(reader, flag), Ok(Poll::Pending)),
716 "a reader resolved before the driver ran"
717 );
718 }
719
720 source.driver.clone().await;
721
722 let mut original_panics = 0;
723 for (i, (reader, flag)) in readers.iter_mut().enumerate() {
724 assert!(
725 flag.0.load(Ordering::SeqCst),
726 "reader {i} was not woken after the driver finished"
727 );
728
729 let outcome = match poll_reader(reader, flag) {
730 Ok(Poll::Ready(result)) => Ok(result),
731 Ok(Poll::Pending) => {
732 panic!("reader {i} is still pending after the driver finished")
733 }
734 Err(payload) => Err(payload),
735 };
736
737 match outcome {
738 Err(payload) => {
740 assert!(
741 panic_message(&*payload).contains("read-at panic"),
742 "got: {:?}",
743 panic_message(&*payload)
744 );
745 original_panics += 1;
746 }
747 Ok(result) => assert!(result.is_err(), "expected a dropped-by-runtime error"),
749 }
750 }
751
752 assert_eq!(
753 original_panics, 1,
754 "exactly one reader should re-raise the original driver panic"
755 );
756 }
757
758 #[derive(Clone)]
759 struct ReadRangesOnly {
760 calls: Arc<AtomicUsize>,
761 }
762
763 impl VortexReadAt for ReadRangesOnly {
764 fn concurrency(&self) -> usize {
765 4
766 }
767
768 fn size(&self) -> BoxFuture<'static, VortexResult<u64>> {
769 async { Ok(16) }.boxed()
770 }
771
772 fn read_at(
773 &self,
774 _offset: u64,
775 _length: usize,
776 _alignment: Alignment,
777 ) -> BoxFuture<'static, VortexResult<BufferHandle>> {
778 async { panic!("read_at should not be called") }.boxed()
779 }
780
781 fn read_ranges(&self, requests: Arc<[ReadAtRequest]>) -> ReadAtStream {
782 self.calls.fetch_add(1, Ordering::Relaxed);
783 let results = requests
784 .iter()
785 .copied()
786 .map(|request| {
787 let buffer = BufferHandle::new_host(
788 ByteBuffer::from(vec![0; request.length]).aligned(request.alignment),
789 );
790 (request, Ok(buffer))
791 })
792 .collect::<Vec<_>>();
793 futures::stream::iter(results).boxed()
794 }
795 }
796
797 #[tokio::test]
798 async fn read_driver_batches_ready_requests() -> VortexResult<()> {
799 let calls = Arc::new(AtomicUsize::new(0));
800 let segments: Arc<[SegmentSpec]> = (0..4)
801 .map(|i| SegmentSpec {
802 offset: i * 4,
803 length: 4,
804 alignment: Alignment::none(),
805 })
806 .collect();
807 let metrics = DefaultMetricsRegistry::default();
808 let request_metrics = RequestMetrics::new(&metrics, vec![]);
809 let source = FileSegmentSource::open(
810 segments,
811 ReadRangesOnly {
812 calls: Arc::clone(&calls),
813 },
814 TokioRuntime::current(),
815 request_metrics.clone(),
816 );
817
818 let results = future::join_all((0..4).map(|i| source.request(SegmentId::from(i)))).await;
819
820 for result in results {
821 assert_eq!(result?.len(), 4);
822 }
823 assert_eq!(calls.load(Ordering::Relaxed), 1);
824 assert_eq!(request_metrics.read_ranges_calls.value(), 1);
825 assert_eq!(request_metrics.read_ranges_multi.value(), 1);
826 assert_eq!(request_metrics.read_ranges_num_ranges.count(), 1);
827 assert_eq!(request_metrics.read_ranges_num_ranges.total(), 4.0);
828 Ok(())
829 }
830
831 #[derive(Clone)]
832 struct ControlledReadRanges {
833 active: Arc<AtomicUsize>,
834 max_active: Arc<AtomicUsize>,
835 batch_sizes: Arc<Mutex<Vec<usize>>>,
836 permits: Arc<tokio::sync::Semaphore>,
837 }
838
839 impl VortexReadAt for ControlledReadRanges {
840 fn concurrency(&self) -> usize {
841 4
842 }
843
844 fn size(&self) -> BoxFuture<'static, VortexResult<u64>> {
845 async { Ok(24) }.boxed()
846 }
847
848 fn read_at(
849 &self,
850 _offset: u64,
851 _length: usize,
852 _alignment: Alignment,
853 ) -> BoxFuture<'static, VortexResult<BufferHandle>> {
854 async { panic!("read_at should not be called") }.boxed()
855 }
856
857 fn read_ranges(&self, requests: Arc<[ReadAtRequest]>) -> ReadAtStream {
858 self.batch_sizes.lock().push(requests.len());
859 let active = self.active.fetch_add(requests.len(), Ordering::SeqCst) + requests.len();
860 self.max_active.fetch_max(active, Ordering::SeqCst);
861
862 let reads = requests
863 .iter()
864 .copied()
865 .map(|request| {
866 let active = Arc::clone(&self.active);
867 let permits = Arc::clone(&self.permits);
868 async move {
869 let Ok(permit) = permits.acquire_owned().await else {
870 vortex_panic!("test semaphore unexpectedly closed");
871 };
872 permit.forget();
873 active.fetch_sub(1, Ordering::SeqCst);
874 let buffer = BufferHandle::new_host(
875 ByteBuffer::from(vec![0; request.length]).aligned(request.alignment),
876 );
877 (request, Ok(buffer))
878 }
879 })
880 .collect::<Vec<_>>();
881 futures::stream::iter(reads).buffer_unordered(4).boxed()
882 }
883 }
884
885 #[tokio::test]
886 async fn read_driver_refills_global_concurrency_across_batches() -> VortexResult<()> {
887 let active = Arc::new(AtomicUsize::new(0));
888 let max_active = Arc::new(AtomicUsize::new(0));
889 let batch_sizes = Arc::new(Mutex::new(Vec::new()));
890 let permits = Arc::new(tokio::sync::Semaphore::new(0));
891 let segments: Arc<[SegmentSpec]> = (0..6)
892 .map(|i| SegmentSpec {
893 offset: i * 4,
894 length: 4,
895 alignment: Alignment::none(),
896 })
897 .collect();
898 let metrics = DefaultMetricsRegistry::default();
899 let source = FileSegmentSource::open(
900 segments,
901 ControlledReadRanges {
902 active: Arc::clone(&active),
903 max_active: Arc::clone(&max_active),
904 batch_sizes: Arc::clone(&batch_sizes),
905 permits: Arc::clone(&permits),
906 },
907 TokioRuntime::current(),
908 RequestMetrics::new(&metrics, vec![]),
909 );
910 let reads = TokioRuntime::current().spawn(async move {
911 future::join_all((0..6).map(|i| source.request(SegmentId::from(i)))).await
912 });
913
914 assert!(
915 tokio::time::timeout(std::time::Duration::from_secs(1), async {
916 while active.load(Ordering::SeqCst) != 4 {
917 tokio::task::yield_now().await;
918 }
919 })
920 .await
921 .is_ok()
922 );
923
924 permits.add_permits(1);
925 assert!(
926 tokio::time::timeout(std::time::Duration::from_secs(1), async {
927 while batch_sizes.lock().len() < 2 || active.load(Ordering::SeqCst) != 4 {
928 tokio::task::yield_now().await;
929 }
930 })
931 .await
932 .is_ok()
933 );
934 assert_eq!(batch_sizes.lock().as_slice(), [4, 1]);
935 assert_eq!(max_active.load(Ordering::SeqCst), 4);
936
937 permits.add_permits(5);
938 for result in reads.await {
939 assert_eq!(result?.len(), 4);
940 }
941 assert_eq!(max_active.load(Ordering::SeqCst), 4);
942 Ok(())
943 }
944
945 #[tokio::test]
946 async fn read_driver_keeps_slots_full_while_a_straggler_is_in_flight() -> VortexResult<()> {
947 let active = Arc::new(AtomicUsize::new(0));
948 let max_active = Arc::new(AtomicUsize::new(0));
949 let batch_sizes = Arc::new(Mutex::new(Vec::new()));
950 let permits = Arc::new(tokio::sync::Semaphore::new(0));
951 let segments: Arc<[SegmentSpec]> = (0..8)
952 .map(|i| SegmentSpec {
953 offset: i * 4,
954 length: 4,
955 alignment: Alignment::none(),
956 })
957 .collect();
958 let metrics = DefaultMetricsRegistry::default();
959 let source = FileSegmentSource::open(
960 segments,
961 ControlledReadRanges {
962 active: Arc::clone(&active),
963 max_active: Arc::clone(&max_active),
964 batch_sizes: Arc::clone(&batch_sizes),
965 permits: Arc::clone(&permits),
966 },
967 TokioRuntime::current(),
968 RequestMetrics::new(&metrics, vec![]),
969 );
970 let reads = TokioRuntime::current().spawn(async move {
971 future::join_all((0..8).map(|i| source.request(SegmentId::from(i)))).await
972 });
973
974 wait_for_active_reads(&active, 4).await;
975
976 for expected_calls in 2..=4 {
980 permits.add_permits(1);
981 assert!(
982 tokio::time::timeout(std::time::Duration::from_secs(1), async {
983 while batch_sizes.lock().len() < expected_calls
984 || active.load(Ordering::SeqCst) != 4
985 {
986 tokio::task::yield_now().await;
987 }
988 })
989 .await
990 .is_ok()
991 );
992 }
993
994 assert_eq!(batch_sizes.lock().as_slice(), [4, 1, 1, 1]);
995 assert_eq!(active.load(Ordering::SeqCst), 4);
996 assert_eq!(max_active.load(Ordering::SeqCst), 4);
997
998 permits.add_permits(5);
999 for result in reads.await {
1000 assert_eq!(result?.len(), 4);
1001 }
1002 Ok(())
1003 }
1004
1005 async fn wait_for_active_reads(active: &AtomicUsize, expected: usize) {
1006 assert!(
1007 tokio::time::timeout(std::time::Duration::from_secs(1), async {
1008 while active.load(Ordering::SeqCst) != expected {
1009 tokio::task::yield_now().await;
1010 }
1011 })
1012 .await
1013 .is_ok()
1014 );
1015 }
1016
1017 #[derive(Clone)]
1018 struct SlowErrReadAt;
1019
1020 impl VortexReadAt for SlowErrReadAt {
1021 fn concurrency(&self) -> usize {
1022 4
1023 }
1024
1025 fn size(&self) -> BoxFuture<'static, VortexResult<u64>> {
1026 async { Ok(1024) }.boxed()
1027 }
1028
1029 fn read_at(
1030 &self,
1031 offset: u64,
1032 _length: usize,
1033 _alignment: Alignment,
1034 ) -> BoxFuture<'static, VortexResult<BufferHandle>> {
1035 async move {
1036 for _ in 0..(offset as usize % 5 + 1) {
1038 tokio::task::yield_now().await;
1039 }
1040 vortex_bail!("slow read done")
1041 }
1042 .boxed()
1043 }
1044 }
1045
1046 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
1048 async fn read_driver_concurrent_reads_make_progress() {
1049 let n = 16u32;
1050 let segments: Arc<[SegmentSpec]> = (0..n)
1051 .map(|i| SegmentSpec {
1052 offset: u64::from(i) * 4,
1053 length: 4,
1054 alignment: Alignment::none(),
1055 })
1056 .collect();
1057 let metrics = DefaultMetricsRegistry::default();
1058 let source = Arc::new(FileSegmentSource::open(
1059 segments,
1060 SlowErrReadAt,
1061 TokioRuntime::current(),
1062 RequestMetrics::new(&metrics, vec![]),
1063 ));
1064
1065 let handle = TokioRuntime::current();
1066 let tasks: Vec<_> = (0..n)
1067 .map(|i| {
1068 let source = Arc::clone(&source);
1069 handle.spawn(async move { source.request(SegmentId::from(i)).await })
1070 })
1071 .collect();
1072
1073 let joined =
1074 tokio::time::timeout(std::time::Duration::from_secs(5), future::join_all(tasks)).await;
1075
1076 let results = joined.expect("concurrent reads stalled before completing");
1077 assert_eq!(results.len(), n as usize);
1078 for result in results {
1079 let err = result.expect_err("reader must forward the I/O error");
1080 assert!(err.to_string().contains("slow read done"), "{err}");
1081 }
1082 }
1083}