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
567 use futures::future::BoxFuture;
568 use vortex_error::vortex_bail;
569 use vortex_io::runtime::tokio::TokioRuntime;
570 use vortex_layout::segments::SegmentSource;
571 use vortex_metrics::DefaultMetricsRegistry;
572
573 use super::*;
574
575 fn io_request(id: RequestId, offset: u64, length: usize) -> IoRequest {
576 let (callback, _receiver) = oneshot::channel();
577 IoRequest::new_single(ReadRequest {
578 id,
579 offset,
580 length,
581 alignment: Alignment::none(),
582 callback,
583 })
584 }
585
586 #[tokio::test]
587 async fn read_range_results_matches_results_and_reports_missing_requests() {
588 let requests = vec![io_request(0, 0, 4), io_request(1, 4, 4)];
589 let unknown = ReadAtRequest::new(8, 4, Alignment::none());
590 let returned = ReadAtRequest::new(4, 4, Alignment::none());
591 let buffer = BufferHandle::new_host(ByteBuffer::from(vec![0; 4]));
592 let results =
593 futures::stream::iter([(unknown, Ok(buffer.clone())), (returned, Ok(buffer))]).boxed();
594
595 let resolved = ReadRangeResults::new(results, requests)
596 .collect::<Vec<_>>()
597 .await;
598
599 assert_eq!(resolved.len(), 2);
600 assert_eq!(resolved[0].0.offset(), 4);
601 assert!(resolved[0].1.is_ok());
602 assert_eq!(resolved[1].0.offset(), 0);
603 assert!(resolved[1].1.is_err());
604 }
605
606 #[derive(Clone)]
607 struct PanickingReadAt;
608
609 impl VortexReadAt for PanickingReadAt {
610 fn concurrency(&self) -> usize {
611 1
612 }
613
614 fn size(&self) -> BoxFuture<'static, VortexResult<u64>> {
615 async { Ok(4) }.boxed()
616 }
617
618 fn read_at(
619 &self,
620 _offset: u64,
621 _length: usize,
622 _alignment: Alignment,
623 ) -> BoxFuture<'static, VortexResult<BufferHandle>> {
624 async {
625 panic!("read-at panic");
626 }
627 .boxed()
628 }
629 }
630
631 fn panicking_source() -> FileSegmentSource {
632 let segments: Arc<[SegmentSpec]> = Arc::from([SegmentSpec {
633 offset: 0,
634 length: 4,
635 alignment: Alignment::none(),
636 }]);
637 let metrics = DefaultMetricsRegistry::default();
638 FileSegmentSource::open(
639 segments,
640 PanickingReadAt,
641 TokioRuntime::current(),
642 RequestMetrics::new(&metrics, vec![]),
643 )
644 }
645
646 fn panic_message(payload: &(dyn Any + Send)) -> &str {
647 payload
648 .downcast_ref::<&str>()
649 .copied()
650 .or_else(|| payload.downcast_ref::<String>().map(String::as_str))
651 .unwrap_or("<non-string panic>")
652 }
653
654 #[tokio::test]
655 #[should_panic(expected = "read-at panic")]
656 async fn file_segment_source_propagates_read_driver_panic() {
657 let source = panicking_source();
658 let _result = source.request(SegmentId::from(0)).await;
659 }
660
661 #[tokio::test]
664 async fn file_segment_source_read_driver_panic_propagates_deterministically() {
665 for i in 0..100 {
666 let source = panicking_source();
667 let outcome = AssertUnwindSafe(source.request(SegmentId::from(0)))
668 .catch_unwind()
669 .await;
670 assert!(
671 outcome.is_err(),
672 "read-driver panic was not propagated on iteration {i}; \
673 the request resolved instead of panicking"
674 );
675 }
676 }
677
678 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
683 async fn file_segment_source_panic_propagates_to_all_concurrent_readers() {
684 let source = Arc::new(panicking_source());
685 let handle = TokioRuntime::current();
686 let reader_count = 8;
687
688 let readers: Vec<_> = (0..reader_count)
689 .map(|_| {
690 let source = Arc::clone(&source);
691 handle.spawn(async move {
692 AssertUnwindSafe(source.request(SegmentId::from(0)))
693 .catch_unwind()
694 .await
695 })
696 })
697 .collect();
698
699 let joined =
700 tokio::time::timeout(std::time::Duration::from_secs(5), future::join_all(readers))
701 .await
702 .expect("a reader hung instead of observing the driver panic");
703
704 let mut original_panics = 0;
705 for reader in joined {
706 match reader {
707 Err(payload) => {
709 assert!(
710 panic_message(&*payload).contains("read-at panic"),
711 "got: {:?}",
712 panic_message(&*payload)
713 );
714 original_panics += 1;
715 }
716 Ok(result) => assert!(result.is_err(), "expected a dropped-by-runtime error"),
718 }
719 }
720
721 assert_eq!(
722 original_panics, 1,
723 "exactly one reader should re-raise the original driver panic"
724 );
725 }
726
727 #[derive(Clone)]
728 struct ReadRangesOnly {
729 calls: Arc<AtomicUsize>,
730 }
731
732 impl VortexReadAt for ReadRangesOnly {
733 fn concurrency(&self) -> usize {
734 4
735 }
736
737 fn size(&self) -> BoxFuture<'static, VortexResult<u64>> {
738 async { Ok(16) }.boxed()
739 }
740
741 fn read_at(
742 &self,
743 _offset: u64,
744 _length: usize,
745 _alignment: Alignment,
746 ) -> BoxFuture<'static, VortexResult<BufferHandle>> {
747 async { panic!("read_at should not be called") }.boxed()
748 }
749
750 fn read_ranges(&self, requests: Arc<[ReadAtRequest]>) -> ReadAtStream {
751 self.calls.fetch_add(1, Ordering::Relaxed);
752 let results = requests
753 .iter()
754 .copied()
755 .map(|request| {
756 let buffer = BufferHandle::new_host(
757 ByteBuffer::from(vec![0; request.length]).aligned(request.alignment),
758 );
759 (request, Ok(buffer))
760 })
761 .collect::<Vec<_>>();
762 futures::stream::iter(results).boxed()
763 }
764 }
765
766 #[tokio::test]
767 async fn read_driver_batches_ready_requests() -> VortexResult<()> {
768 let calls = Arc::new(AtomicUsize::new(0));
769 let segments: Arc<[SegmentSpec]> = (0..4)
770 .map(|i| SegmentSpec {
771 offset: i * 4,
772 length: 4,
773 alignment: Alignment::none(),
774 })
775 .collect();
776 let metrics = DefaultMetricsRegistry::default();
777 let request_metrics = RequestMetrics::new(&metrics, vec![]);
778 let source = FileSegmentSource::open(
779 segments,
780 ReadRangesOnly {
781 calls: Arc::clone(&calls),
782 },
783 TokioRuntime::current(),
784 request_metrics.clone(),
785 );
786
787 let results = future::join_all((0..4).map(|i| source.request(SegmentId::from(i)))).await;
788
789 for result in results {
790 assert_eq!(result?.len(), 4);
791 }
792 assert_eq!(calls.load(Ordering::Relaxed), 1);
793 assert_eq!(request_metrics.read_ranges_calls.value(), 1);
794 assert_eq!(request_metrics.read_ranges_multi.value(), 1);
795 assert_eq!(request_metrics.read_ranges_num_ranges.count(), 1);
796 assert_eq!(request_metrics.read_ranges_num_ranges.total(), 4.0);
797 Ok(())
798 }
799
800 #[derive(Clone)]
801 struct ControlledReadRanges {
802 active: Arc<AtomicUsize>,
803 max_active: Arc<AtomicUsize>,
804 batch_sizes: Arc<Mutex<Vec<usize>>>,
805 permits: Arc<tokio::sync::Semaphore>,
806 }
807
808 impl VortexReadAt for ControlledReadRanges {
809 fn concurrency(&self) -> usize {
810 4
811 }
812
813 fn size(&self) -> BoxFuture<'static, VortexResult<u64>> {
814 async { Ok(24) }.boxed()
815 }
816
817 fn read_at(
818 &self,
819 _offset: u64,
820 _length: usize,
821 _alignment: Alignment,
822 ) -> BoxFuture<'static, VortexResult<BufferHandle>> {
823 async { panic!("read_at should not be called") }.boxed()
824 }
825
826 fn read_ranges(&self, requests: Arc<[ReadAtRequest]>) -> ReadAtStream {
827 self.batch_sizes.lock().push(requests.len());
828 let active = self.active.fetch_add(requests.len(), Ordering::SeqCst) + requests.len();
829 self.max_active.fetch_max(active, Ordering::SeqCst);
830
831 let reads = requests
832 .iter()
833 .copied()
834 .map(|request| {
835 let active = Arc::clone(&self.active);
836 let permits = Arc::clone(&self.permits);
837 async move {
838 let Ok(permit) = permits.acquire_owned().await else {
839 vortex_panic!("test semaphore unexpectedly closed");
840 };
841 permit.forget();
842 active.fetch_sub(1, Ordering::SeqCst);
843 let buffer = BufferHandle::new_host(
844 ByteBuffer::from(vec![0; request.length]).aligned(request.alignment),
845 );
846 (request, Ok(buffer))
847 }
848 })
849 .collect::<Vec<_>>();
850 futures::stream::iter(reads).buffer_unordered(4).boxed()
851 }
852 }
853
854 #[tokio::test]
855 async fn read_driver_refills_global_concurrency_across_batches() -> VortexResult<()> {
856 let active = Arc::new(AtomicUsize::new(0));
857 let max_active = Arc::new(AtomicUsize::new(0));
858 let batch_sizes = Arc::new(Mutex::new(Vec::new()));
859 let permits = Arc::new(tokio::sync::Semaphore::new(0));
860 let segments: Arc<[SegmentSpec]> = (0..6)
861 .map(|i| SegmentSpec {
862 offset: i * 4,
863 length: 4,
864 alignment: Alignment::none(),
865 })
866 .collect();
867 let metrics = DefaultMetricsRegistry::default();
868 let source = FileSegmentSource::open(
869 segments,
870 ControlledReadRanges {
871 active: Arc::clone(&active),
872 max_active: Arc::clone(&max_active),
873 batch_sizes: Arc::clone(&batch_sizes),
874 permits: Arc::clone(&permits),
875 },
876 TokioRuntime::current(),
877 RequestMetrics::new(&metrics, vec![]),
878 );
879 let reads = TokioRuntime::current().spawn(async move {
880 future::join_all((0..6).map(|i| source.request(SegmentId::from(i)))).await
881 });
882
883 assert!(
884 tokio::time::timeout(std::time::Duration::from_secs(1), async {
885 while active.load(Ordering::SeqCst) != 4 {
886 tokio::task::yield_now().await;
887 }
888 })
889 .await
890 .is_ok()
891 );
892
893 permits.add_permits(1);
894 assert!(
895 tokio::time::timeout(std::time::Duration::from_secs(1), async {
896 while batch_sizes.lock().len() < 2 || active.load(Ordering::SeqCst) != 4 {
897 tokio::task::yield_now().await;
898 }
899 })
900 .await
901 .is_ok()
902 );
903 assert_eq!(batch_sizes.lock().as_slice(), [4, 1]);
904 assert_eq!(max_active.load(Ordering::SeqCst), 4);
905
906 permits.add_permits(5);
907 for result in reads.await {
908 assert_eq!(result?.len(), 4);
909 }
910 assert_eq!(max_active.load(Ordering::SeqCst), 4);
911 Ok(())
912 }
913
914 #[tokio::test]
915 async fn read_driver_keeps_slots_full_while_a_straggler_is_in_flight() -> VortexResult<()> {
916 let active = Arc::new(AtomicUsize::new(0));
917 let max_active = Arc::new(AtomicUsize::new(0));
918 let batch_sizes = Arc::new(Mutex::new(Vec::new()));
919 let permits = Arc::new(tokio::sync::Semaphore::new(0));
920 let segments: Arc<[SegmentSpec]> = (0..8)
921 .map(|i| SegmentSpec {
922 offset: i * 4,
923 length: 4,
924 alignment: Alignment::none(),
925 })
926 .collect();
927 let metrics = DefaultMetricsRegistry::default();
928 let source = FileSegmentSource::open(
929 segments,
930 ControlledReadRanges {
931 active: Arc::clone(&active),
932 max_active: Arc::clone(&max_active),
933 batch_sizes: Arc::clone(&batch_sizes),
934 permits: Arc::clone(&permits),
935 },
936 TokioRuntime::current(),
937 RequestMetrics::new(&metrics, vec![]),
938 );
939 let reads = TokioRuntime::current().spawn(async move {
940 future::join_all((0..8).map(|i| source.request(SegmentId::from(i)))).await
941 });
942
943 wait_for_active_reads(&active, 4).await;
944
945 for expected_calls in 2..=4 {
949 permits.add_permits(1);
950 assert!(
951 tokio::time::timeout(std::time::Duration::from_secs(1), async {
952 while batch_sizes.lock().len() < expected_calls
953 || active.load(Ordering::SeqCst) != 4
954 {
955 tokio::task::yield_now().await;
956 }
957 })
958 .await
959 .is_ok()
960 );
961 }
962
963 assert_eq!(batch_sizes.lock().as_slice(), [4, 1, 1, 1]);
964 assert_eq!(active.load(Ordering::SeqCst), 4);
965 assert_eq!(max_active.load(Ordering::SeqCst), 4);
966
967 permits.add_permits(5);
968 for result in reads.await {
969 assert_eq!(result?.len(), 4);
970 }
971 Ok(())
972 }
973
974 async fn wait_for_active_reads(active: &AtomicUsize, expected: usize) {
975 assert!(
976 tokio::time::timeout(std::time::Duration::from_secs(1), async {
977 while active.load(Ordering::SeqCst) != expected {
978 tokio::task::yield_now().await;
979 }
980 })
981 .await
982 .is_ok()
983 );
984 }
985
986 #[derive(Clone)]
987 struct SlowErrReadAt;
988
989 impl VortexReadAt for SlowErrReadAt {
990 fn concurrency(&self) -> usize {
991 4
992 }
993
994 fn size(&self) -> BoxFuture<'static, VortexResult<u64>> {
995 async { Ok(1024) }.boxed()
996 }
997
998 fn read_at(
999 &self,
1000 offset: u64,
1001 _length: usize,
1002 _alignment: Alignment,
1003 ) -> BoxFuture<'static, VortexResult<BufferHandle>> {
1004 async move {
1005 for _ in 0..(offset as usize % 5 + 1) {
1007 tokio::task::yield_now().await;
1008 }
1009 vortex_bail!("slow read done")
1010 }
1011 .boxed()
1012 }
1013 }
1014
1015 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
1017 async fn read_driver_concurrent_reads_make_progress() {
1018 let n = 16u32;
1019 let segments: Arc<[SegmentSpec]> = (0..n)
1020 .map(|i| SegmentSpec {
1021 offset: u64::from(i) * 4,
1022 length: 4,
1023 alignment: Alignment::none(),
1024 })
1025 .collect();
1026 let metrics = DefaultMetricsRegistry::default();
1027 let source = Arc::new(FileSegmentSource::open(
1028 segments,
1029 SlowErrReadAt,
1030 TokioRuntime::current(),
1031 RequestMetrics::new(&metrics, vec![]),
1032 ));
1033
1034 let handle = TokioRuntime::current();
1035 let tasks: Vec<_> = (0..n)
1036 .map(|i| {
1037 let source = Arc::clone(&source);
1038 handle.spawn(async move { source.request(SegmentId::from(i)).await })
1039 })
1040 .collect();
1041
1042 let joined =
1043 tokio::time::timeout(std::time::Duration::from_secs(5), future::join_all(tasks)).await;
1044
1045 assert!(joined.is_ok(), "concurrent reads stalled before completing");
1046 }
1047}