1use bytes::Bytes;
5use futures::channel::oneshot;
6use futures::{FutureExt, TryFutureExt};
7use object_store::path::Path;
8use std::collections::BinaryHeap;
9use std::fmt::Debug;
10use std::future::Future;
11use std::num::NonZero;
12use std::ops::Range;
13use std::sync::atomic::{AtomicU64, Ordering};
14use std::sync::{Arc, Mutex};
15use std::time::Instant;
16use tokio::sync::Notify;
17
18use lance_core::utils::io_stats::IoStatsRecorder;
19use lance_core::utils::parse::str_is_truthy;
20use lance_core::{Error, Result};
21
22use crate::object_store::ObjectStore;
23use crate::traits::Reader;
24use crate::utils::CachedFileSize;
25
26mod lite;
27
28const BACKPRESSURE_MIN: u64 = 5;
30const BACKPRESSURE_DEBOUNCE: u64 = 60;
32const SCHEDULER_STATE_EVENT_TARGET: &str = "lance_io::scheduler::state";
33
34static IOPS_COUNTER: AtomicU64 = AtomicU64::new(0);
36static BYTES_READ_COUNTER: AtomicU64 = AtomicU64::new(0);
38
39pub fn iops_counter() -> u64 {
40 IOPS_COUNTER.load(Ordering::Acquire)
41}
42
43pub fn bytes_read_counter() -> u64 {
44 BYTES_READ_COUNTER.load(Ordering::Acquire)
45}
46
47struct PrioritiesInFlight {
56 in_flight: Vec<u128>,
57}
58
59impl PrioritiesInFlight {
60 fn new(capacity: u32) -> Self {
61 Self {
62 in_flight: Vec::with_capacity(capacity as usize * 2),
63 }
64 }
65
66 fn min_in_flight(&self) -> u128 {
67 self.in_flight.first().copied().unwrap_or(u128::MAX)
68 }
69
70 fn contains(&self, prio: u128) -> bool {
71 self.in_flight.binary_search(&prio).is_ok()
72 }
73
74 fn push(&mut self, prio: u128) {
75 let pos = match self.in_flight.binary_search(&prio) {
76 Ok(pos) => pos,
77 Err(pos) => pos,
78 };
79 self.in_flight.insert(pos, prio);
80 }
81
82 fn remove(&mut self, prio: u128) {
83 if let Ok(pos) = self.in_flight.binary_search(&prio) {
84 self.in_flight.remove(pos);
85 }
86 }
87
88 fn len(&self) -> usize {
89 self.in_flight.len()
90 }
91
92 fn is_empty(&self) -> bool {
93 self.in_flight.is_empty()
94 }
95}
96
97struct IoQueueState {
98 io_capacity: u32,
100 iops_avail: u32,
102 io_buffer_size: u64,
104 bytes_avail: i64,
108 pending_requests: BinaryHeap<IoTask>,
110 priorities_in_flight: PrioritiesInFlight,
112 done_scheduling: bool,
115 start: Instant,
117 last_warn: AtomicU64,
119 no_backpressure: bool,
121}
122
123impl IoQueueState {
124 fn new(io_capacity: u32, io_buffer_size: u64) -> Self {
125 Self {
126 io_capacity,
127 iops_avail: io_capacity,
128 io_buffer_size,
129 bytes_avail: io_buffer_size as i64,
130 pending_requests: BinaryHeap::new(),
131 priorities_in_flight: PrioritiesInFlight::new(io_capacity),
132 done_scheduling: false,
133 start: Instant::now(),
134 last_warn: AtomicU64::from(0),
135 no_backpressure: io_buffer_size == 0,
136 }
137 }
138
139 fn scheduler_state_event(&self) -> Option<SchedulerStateEvent> {
140 if !tracing::enabled!(target: SCHEDULER_STATE_EVENT_TARGET, tracing::Level::TRACE) {
141 return None;
142 }
143
144 let pending_bytes = self
145 .pending_requests
146 .iter()
147 .map(IoTask::num_bytes)
148 .sum::<u64>();
149 let head_task = self.pending_requests.peek();
150 let min_in_flight_priority = if self.priorities_in_flight.is_empty() {
151 None
152 } else {
153 Some(self.priorities_in_flight.min_in_flight())
154 };
155 let head_task_priority_bypass = head_task.map(|task| {
156 self.no_backpressure
157 || task.bypass_backpressure
158 || task.priority <= self.priorities_in_flight.min_in_flight()
159 });
160 let head_task_blocked_by_iops = head_task.map(|_| self.iops_avail == 0);
161 let head_task_blocked_by_bytes = head_task.map(|task| {
162 let bypasses_bytes = self.no_backpressure
163 || task.bypass_backpressure
164 || task.priority <= self.priorities_in_flight.min_in_flight();
165 !bypasses_bytes && task.num_bytes() as i64 > self.bytes_avail
166 });
167 let head_task_can_deliver = head_task.map(|task| self.can_deliver_without_warning(task));
168 let head_task_bytes = head_task.map(IoTask::num_bytes);
169 let (head_task_priority_high, head_task_priority_low) =
170 split_priority(head_task.map(|task| task.priority));
171 let (min_in_flight_priority_high, min_in_flight_priority_low) =
172 split_priority(min_in_flight_priority);
173
174 Some(SchedulerStateEvent {
175 queue_kind: "standard",
176 io_capacity: u64::from(self.io_capacity),
177 iops_available: u64::from(self.iops_avail),
178 active_iops: u64::from(self.io_capacity.saturating_sub(self.iops_avail)),
179 pending_iops: self.pending_requests.len() as u64,
180 pending_bytes,
181 bytes_available: self.bytes_avail,
182 bytes_reserved: self.io_buffer_size as i64 - self.bytes_avail,
183 io_buffer_size_bytes: self.io_buffer_size,
184 priorities_in_flight: self.priorities_in_flight.len() as u64,
185 no_backpressure: self.no_backpressure,
186 head_task_bytes,
187 head_task_priority_high,
188 head_task_priority_low,
189 min_in_flight_priority_high,
190 min_in_flight_priority_low,
191 head_task_can_deliver,
192 head_task_priority_bypass,
193 head_task_blocked_by_iops,
194 head_task_blocked_by_bytes,
195 })
196 }
197
198 fn warn_if_needed(&self) {
199 let seconds_elapsed = self.start.elapsed().as_secs();
200 let last_warn = self.last_warn.load(Ordering::Acquire);
201 let since_last_warn = seconds_elapsed - last_warn;
202 if (last_warn == 0
203 && seconds_elapsed > BACKPRESSURE_MIN
204 && seconds_elapsed < BACKPRESSURE_DEBOUNCE)
205 || since_last_warn > BACKPRESSURE_DEBOUNCE
206 {
207 tracing::event!(tracing::Level::DEBUG, "Backpressure throttle exceeded");
208 log::debug!(
209 "Backpressure throttle is full, I/O will pause until buffer is drained. Max I/O bandwidth will not be achieved because CPU is falling behind"
210 );
211 self.last_warn
212 .store(seconds_elapsed.max(1), Ordering::Release);
213 }
214 }
215
216 fn can_deliver(&self, task: &IoTask) -> bool {
217 let can_deliver = self.can_deliver_without_warning(task);
218 if !can_deliver
219 && self.iops_avail > 0
220 && !(self.no_backpressure
221 || task.bypass_backpressure
222 || task.priority <= self.priorities_in_flight.min_in_flight())
223 && task.num_bytes() as i64 > self.bytes_avail
224 {
225 self.warn_if_needed();
226 }
227 can_deliver
228 }
229
230 fn can_deliver_without_warning(&self, task: &IoTask) -> bool {
231 if self.iops_avail == 0 {
232 false
233 } else if self.no_backpressure
234 || task.bypass_backpressure
235 || task.priority <= self.priorities_in_flight.min_in_flight()
236 || self.priorities_in_flight.contains(task.priority)
240 {
241 true
242 } else {
243 task.num_bytes() as i64 <= self.bytes_avail
244 }
245 }
246
247 fn next_task(&mut self) -> Option<IoTask> {
248 let task = self.pending_requests.peek()?;
249 if self.can_deliver(task) {
250 let skip_bytes_accounting = self.no_backpressure || task.bypass_backpressure;
251 self.priorities_in_flight.push(task.priority);
252 self.iops_avail -= 1;
253 if !skip_bytes_accounting {
254 self.bytes_avail -= task.num_bytes() as i64;
255 if self.bytes_avail < 0 {
256 log::debug!(
258 "Backpressure throttle temporarily exceeded by {} bytes due to priority I/O",
259 -self.bytes_avail
260 );
261 }
262 }
263 Some(self.pending_requests.pop().unwrap())
264 } else {
265 None
266 }
267 }
268}
269
270struct IoQueue {
275 state: Mutex<IoQueueState>,
277 notify: Notify,
279 stats: IoStats,
280}
281
282impl IoQueue {
283 fn new(io_capacity: u32, io_buffer_size: u64, stats: IoStats) -> Self {
284 Self {
285 state: Mutex::new(IoQueueState::new(io_capacity, io_buffer_size)),
286 notify: Notify::new(),
287 stats,
288 }
289 }
290
291 fn push(&self, task: IoTask) {
292 log::trace!(
293 "Inserting I/O request for {} bytes with priority ({},{}) into I/O queue",
294 task.num_bytes(),
295 task.priority >> 64,
296 task.priority & 0xFFFFFFFFFFFFFFFF
297 );
298 let event = {
299 let mut state = self.state.lock().unwrap();
300 state.pending_requests.push(task);
301 state.scheduler_state_event()
302 };
303 emit_scheduler_state_event(event, &self.stats);
304
305 self.notify.notify_one();
306 }
307
308 async fn pop(&self) -> Option<IoTask> {
309 loop {
310 {
311 let mut state = self.state.lock().unwrap();
312 if let Some(task) = state.next_task() {
313 let event = state.scheduler_state_event();
314 drop(state);
315 emit_scheduler_state_event(event, &self.stats);
316 return Some(task);
317 }
318
319 if state.done_scheduling {
320 return None;
321 }
322 }
323
324 self.notify.notified().await;
325 }
326 }
327
328 fn on_iop_complete(&self) {
329 let event = {
330 let mut state = self.state.lock().unwrap();
331 state.iops_avail += 1;
332 state.scheduler_state_event()
333 };
334 emit_scheduler_state_event(event, &self.stats);
335
336 self.notify.notify_one();
337 }
338
339 fn on_bytes_consumed(&self, bytes: u64, priority: u128, num_reqs: usize) {
340 let event = {
341 let mut state = self.state.lock().unwrap();
342 state.bytes_avail += bytes as i64;
343 for _ in 0..num_reqs {
344 state.priorities_in_flight.remove(priority);
345 }
346 state.scheduler_state_event()
347 };
348 emit_scheduler_state_event(event, &self.stats);
349
350 self.notify.notify_one();
351 }
352
353 fn close(&self) {
354 let (pending_requests, event) = {
355 let mut state = self.state.lock().unwrap();
356 state.done_scheduling = true;
357 let pending_requests = std::mem::take(&mut state.pending_requests);
358 let event = state.scheduler_state_event();
359 (pending_requests, event)
360 };
361 emit_scheduler_state_event(event, &self.stats);
362 for request in pending_requests {
363 request.cancel();
364 }
365
366 self.notify.notify_one();
367 }
368}
369
370struct MutableBatch<F: FnOnce(Response) + Send> {
375 when_done: Option<F>,
376 data_buffers: Vec<Bytes>,
377 num_bytes: u64,
378 priority: u128,
379 num_reqs: usize,
380 num_delivered: usize,
381 err: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
382 bypass_backpressure: bool,
384 io_queue: Arc<IoQueue>,
387}
388
389impl<F: FnOnce(Response) + Send> MutableBatch<F> {
390 fn new(
391 when_done: F,
392 num_data_buffers: u32,
393 priority: u128,
394 num_reqs: usize,
395 bypass_backpressure: bool,
396 io_queue: Arc<IoQueue>,
397 ) -> Self {
398 Self {
399 when_done: Some(when_done),
400 data_buffers: vec![Bytes::default(); num_data_buffers as usize],
401 num_bytes: 0,
402 priority,
403 num_reqs,
404 num_delivered: 0,
405 err: None,
406 bypass_backpressure,
407 io_queue,
408 }
409 }
410}
411
412impl<F: FnOnce(Response) + Send> Drop for MutableBatch<F> {
417 fn drop(&mut self) {
418 let result = if let Some(err) = self.err.take() {
420 Err(Error::wrapped(err))
421 } else if self.num_delivered < self.data_buffers.len() {
422 Err(Error::io(format!(
424 "I/O request was dropped before completion ({} of {} reads delivered)",
425 self.num_delivered,
426 self.data_buffers.len()
427 )))
428 } else {
429 let mut data = Vec::new();
430 std::mem::swap(&mut data, &mut self.data_buffers);
431 Ok(data)
432 };
433 let response = Response {
436 data: Some(result),
437 io_queue: self.io_queue.clone(),
438 num_bytes: if self.bypass_backpressure {
440 0
441 } else {
442 self.num_bytes
443 },
444 priority: self.priority,
445 num_reqs: self.num_reqs,
446 };
447 (self.when_done.take().unwrap())(response);
448 }
449}
450
451struct DataChunk {
452 task_idx: usize,
453 num_bytes: u64,
454 data: Result<Bytes>,
455}
456
457trait DataSink: Send {
458 fn deliver_data(&mut self, data: DataChunk);
459}
460
461impl<F: FnOnce(Response) + Send> DataSink for MutableBatch<F> {
462 fn deliver_data(&mut self, data: DataChunk) {
464 self.num_bytes += data.num_bytes;
465 self.num_delivered += 1;
466 match data.data {
467 Ok(data_bytes) => {
468 self.data_buffers[data.task_idx] = data_bytes;
469 }
470 Err(err) => {
471 self.err.get_or_insert(Box::new(err));
473 }
474 }
475 }
476}
477
478struct IoTask {
479 reader: Arc<dyn Reader>,
480 to_read: Range<u64>,
481 when_done: Box<dyn FnOnce(Result<Bytes>) + Send>,
482 priority: u128,
483 bypass_backpressure: bool,
484}
485
486impl Eq for IoTask {}
487
488impl PartialEq for IoTask {
489 fn eq(&self, other: &Self) -> bool {
490 self.bypass_backpressure == other.bypass_backpressure && self.priority == other.priority
491 }
492}
493
494impl PartialOrd for IoTask {
495 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
496 Some(self.cmp(other))
497 }
498}
499
500impl Ord for IoTask {
501 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
502 self.bypass_backpressure
505 .cmp(&other.bypass_backpressure)
506 .then(other.priority.cmp(&self.priority))
507 }
508}
509
510impl IoTask {
511 fn num_bytes(&self) -> u64 {
512 self.to_read.end - self.to_read.start
513 }
514 fn cancel(self) {
515 (self.when_done)(Err(Error::internal(
516 "Scheduler closed before I/O was completed".to_string(),
517 )));
518 }
519
520 async fn run(self) {
521 let file_path = self.reader.path().as_ref();
522 let num_bytes = self.num_bytes();
523 let bytes = if self.to_read.start == self.to_read.end {
524 Ok(Bytes::new())
525 } else {
526 let bytes_fut = self
527 .reader
528 .get_range(self.to_read.start as usize..self.to_read.end as usize);
529 IOPS_COUNTER.fetch_add(1, Ordering::Release);
530 let num_bytes = self.num_bytes();
531 bytes_fut
532 .inspect(move |_| {
533 BYTES_READ_COUNTER.fetch_add(num_bytes, Ordering::Release);
534 })
535 .await
536 .map_err(Error::from)
537 };
538 tracing::trace!(
540 file = file_path,
541 bytes_read = num_bytes,
542 requests = 1,
543 range_start = self.to_read.start,
544 range_end = self.to_read.end,
545 "File I/O completed"
546 );
547 (self.when_done)(bytes);
548 }
549}
550
551async fn run_io_loop(tasks: Arc<IoQueue>) {
554 loop {
557 let next_task = tasks.pop().await;
558 match next_task {
559 Some(task) => {
560 tokio::spawn(task.run());
561 }
562 None => {
563 return;
565 }
566 }
567 }
568}
569
570#[derive(Debug)]
571struct StatsCollector {
572 iops: AtomicU64,
573 requests: AtomicU64,
574 bytes_read: AtomicU64,
575}
576
577impl StatsCollector {
578 fn new() -> Self {
579 Self {
580 iops: AtomicU64::new(0),
581 requests: AtomicU64::new(0),
582 bytes_read: AtomicU64::new(0),
583 }
584 }
585
586 fn iops(&self) -> u64 {
587 self.iops.load(Ordering::Relaxed)
588 }
589
590 fn bytes_read(&self) -> u64 {
591 self.bytes_read.load(Ordering::Relaxed)
592 }
593
594 fn requests(&self) -> u64 {
595 self.requests.load(Ordering::Relaxed)
596 }
597
598 fn record_request(&self, request: &[Range<u64>]) {
599 self.requests.fetch_add(1, Ordering::Relaxed);
600 self.iops.fetch_add(request.len() as u64, Ordering::Relaxed);
601 self.bytes_read.fetch_add(
602 request.iter().map(|r| r.end - r.start).sum::<u64>(),
603 Ordering::Relaxed,
604 );
605 }
606
607 fn add(&self, iops: u64, requests: u64, bytes_read: u64) {
610 self.iops.fetch_add(iops, Ordering::Relaxed);
611 self.requests.fetch_add(requests, Ordering::Relaxed);
612 self.bytes_read.fetch_add(bytes_read, Ordering::Relaxed);
613 }
614}
615
616impl IoStatsRecorder for StatsCollector {
617 fn record_request(&self, request: &[Range<u64>]) {
618 Self::record_request(self, request)
621 }
622}
623
624#[derive(Debug, Clone, Copy, Default)]
625pub struct ScanStats {
626 pub iops: u64,
627 pub requests: u64,
628 pub bytes_read: u64,
629}
630
631impl ScanStats {
632 fn new(stats: &StatsCollector) -> Self {
633 Self {
634 iops: stats.iops(),
635 requests: stats.requests(),
636 bytes_read: stats.bytes_read(),
637 }
638 }
639}
640
641fn split_priority(priority: Option<u128>) -> (Option<u64>, Option<u64>) {
642 priority
643 .map(|priority| ((priority >> 64) as u64, priority as u64))
644 .unzip()
645}
646
647#[derive(Debug, Clone, Copy)]
648pub(super) struct SchedulerStateEvent {
649 pub(super) queue_kind: &'static str,
650 pub(super) io_capacity: u64,
651 pub(super) iops_available: u64,
652 pub(super) active_iops: u64,
653 pub(super) pending_iops: u64,
654 pub(super) pending_bytes: u64,
655 pub(super) bytes_available: i64,
656 pub(super) bytes_reserved: i64,
657 pub(super) io_buffer_size_bytes: u64,
658 pub(super) priorities_in_flight: u64,
659 pub(super) no_backpressure: bool,
660 pub(super) head_task_bytes: Option<u64>,
661 pub(super) head_task_priority_high: Option<u64>,
662 pub(super) head_task_priority_low: Option<u64>,
663 pub(super) min_in_flight_priority_high: Option<u64>,
664 pub(super) min_in_flight_priority_low: Option<u64>,
665 pub(super) head_task_can_deliver: Option<bool>,
666 pub(super) head_task_priority_bypass: Option<bool>,
667 pub(super) head_task_blocked_by_iops: Option<bool>,
668 pub(super) head_task_blocked_by_bytes: Option<bool>,
669}
670
671impl SchedulerStateEvent {
672 fn trace(self, stats: ScanStats) {
673 tracing::event!(
674 target: SCHEDULER_STATE_EVENT_TARGET,
675 tracing::Level::TRACE,
676 queue_kind = self.queue_kind,
677 scheduler_iops = stats.iops,
678 scheduler_requests = stats.requests,
679 scheduler_bytes_read = stats.bytes_read,
680 io_capacity = self.io_capacity,
681 iops_available = self.iops_available,
682 active_iops = self.active_iops,
683 pending_iops = self.pending_iops,
684 pending_bytes = self.pending_bytes,
685 bytes_available = self.bytes_available,
686 bytes_reserved = self.bytes_reserved,
687 io_buffer_size_bytes = self.io_buffer_size_bytes,
688 priorities_in_flight = self.priorities_in_flight,
689 no_backpressure = self.no_backpressure,
690 head_task_bytes_present = self.head_task_bytes.is_some(),
691 head_task_bytes = self.head_task_bytes.unwrap_or_default(),
692 head_task_priority_high_present = self.head_task_priority_high.is_some(),
693 head_task_priority_high = self.head_task_priority_high.unwrap_or_default(),
694 head_task_priority_low_present = self.head_task_priority_low.is_some(),
695 head_task_priority_low = self.head_task_priority_low.unwrap_or_default(),
696 min_in_flight_priority_high_present = self.min_in_flight_priority_high.is_some(),
697 min_in_flight_priority_high = self.min_in_flight_priority_high.unwrap_or_default(),
698 min_in_flight_priority_low_present = self.min_in_flight_priority_low.is_some(),
699 min_in_flight_priority_low = self.min_in_flight_priority_low.unwrap_or_default(),
700 head_task_can_deliver_present = self.head_task_can_deliver.is_some(),
701 head_task_can_deliver = self.head_task_can_deliver.unwrap_or(false),
702 head_task_priority_bypass_present = self.head_task_priority_bypass.is_some(),
703 head_task_priority_bypass = self.head_task_priority_bypass.unwrap_or(false),
704 head_task_blocked_by_iops_present = self.head_task_blocked_by_iops.is_some(),
705 head_task_blocked_by_iops = self.head_task_blocked_by_iops.unwrap_or(false),
706 head_task_blocked_by_bytes_present = self.head_task_blocked_by_bytes.is_some(),
707 head_task_blocked_by_bytes = self.head_task_blocked_by_bytes.unwrap_or(false),
708 "Scheduler state"
709 );
710 }
711}
712
713pub(super) fn emit_scheduler_state_event(event: Option<SchedulerStateEvent>, stats: &IoStats) {
714 if let Some(event) = event {
715 event.trace(stats.snapshot());
716 }
717}
718
719#[derive(Debug, Clone)]
730pub struct IoStats(Arc<StatsCollector>);
731
732impl IoStats {
733 pub fn new() -> Self {
734 Self(Arc::new(StatsCollector::new()))
735 }
736
737 pub fn record_request(&self, request: &[Range<u64>]) {
741 self.0.record_request(request);
742 }
743
744 pub fn snapshot(&self) -> ScanStats {
746 ScanStats::new(self.0.as_ref())
747 }
748
749 pub fn recorder(&self) -> Arc<dyn IoStatsRecorder> {
753 self.0.clone()
754 }
755
756 pub fn add_scan_stats(&self, stats: &ScanStats) {
760 self.0.add(stats.iops, stats.requests, stats.bytes_read);
761 }
762}
763
764impl Default for IoStats {
765 fn default() -> Self {
766 Self::new()
767 }
768}
769
770enum IoQueueType {
771 Standard(Arc<IoQueue>),
772 Lite(Arc<lite::IoQueue>),
773}
774
775pub struct ScanScheduler {
784 object_store: Arc<ObjectStore>,
785 io_queue: IoQueueType,
786 stats: IoStats,
787}
788
789impl Debug for ScanScheduler {
790 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
791 f.debug_struct("ScanScheduler")
792 .field("object_store", &self.object_store)
793 .finish()
794 }
795}
796
797struct Response {
798 data: Option<Result<Vec<Bytes>>>,
801 io_queue: Arc<IoQueue>,
802 priority: u128,
803 num_reqs: usize,
804 num_bytes: u64,
805}
806
807impl Drop for Response {
811 fn drop(&mut self) {
812 self.io_queue
813 .on_bytes_consumed(self.num_bytes, self.priority, self.num_reqs);
814 }
815}
816
817#[derive(Debug, Clone, Copy)]
818pub struct SchedulerConfig {
819 pub io_buffer_size_bytes: u64,
823 pub use_lite_scheduler: Option<bool>,
829}
830
831impl SchedulerConfig {
832 pub fn new(io_buffer_size_bytes: u64) -> Self {
833 Self {
834 io_buffer_size_bytes,
835 use_lite_scheduler: std::env::var("LANCE_USE_LITE_SCHEDULER")
836 .ok()
837 .map(|v| str_is_truthy(v.trim())),
838 }
839 }
840
841 pub fn default_for_testing() -> Self {
843 Self {
844 io_buffer_size_bytes: 256 * 1024 * 1024,
845 use_lite_scheduler: None,
846 }
847 }
848
849 pub fn max_bandwidth(store: &ObjectStore) -> Self {
852 Self::new(32 * 1024 * 1024 * store.io_parallelism() as u64)
853 }
854
855 pub fn with_lite_scheduler(self) -> Self {
856 Self {
857 use_lite_scheduler: Some(true),
858 ..self
859 }
860 }
861}
862
863impl ScanScheduler {
864 pub fn new(object_store: Arc<ObjectStore>, config: SchedulerConfig) -> Arc<Self> {
871 let io_capacity = object_store.io_parallelism();
872 let stats = IoStats::new();
873 let use_lite = config
874 .use_lite_scheduler
875 .unwrap_or_else(|| object_store.prefers_lite_scheduler());
876 let io_queue = if use_lite {
877 let io_queue = Arc::new(lite::IoQueue::new(
878 io_capacity as u64,
879 config.io_buffer_size_bytes,
880 stats.clone(),
881 ));
882 IoQueueType::Lite(io_queue)
883 } else {
884 let io_queue = Arc::new(IoQueue::new(
885 io_capacity as u32,
886 config.io_buffer_size_bytes,
887 stats.clone(),
888 ));
889 let io_queue_clone = io_queue.clone();
890 tokio::task::spawn(async move { run_io_loop(io_queue_clone).await });
894 IoQueueType::Standard(io_queue)
895 };
896 Arc::new(Self {
897 object_store,
898 io_queue,
899 stats,
900 })
901 }
902
903 pub async fn open_file_with_priority(
912 self: &Arc<Self>,
913 path: &Path,
914 base_priority: u64,
915 file_size_bytes: &CachedFileSize,
916 ) -> Result<FileScheduler> {
917 let file_size_bytes = if let Some(size) = file_size_bytes.get() {
918 u64::from(size)
919 } else {
920 let size = self.object_store.size(path).await?;
921 if let Some(size) = NonZero::new(size) {
922 file_size_bytes.set(size);
923 }
924 size
925 };
926 let reader = self
927 .object_store
928 .open_with_size(path, file_size_bytes as usize)
929 .await?;
930 let block_size = self.object_store.block_size() as u64;
931 let max_iop_size = self.object_store.max_iop_size();
932 Ok(FileScheduler {
933 reader: reader.into(),
934 block_size,
935 root: self.clone(),
936 base_priority,
937 max_iop_size,
938 bypass_backpressure: false,
939 extra_stats: None,
940 })
941 }
942
943 pub async fn open_file(
947 self: &Arc<Self>,
948 path: &Path,
949 file_size_bytes: &CachedFileSize,
950 ) -> Result<FileScheduler> {
951 self.open_file_with_priority(path, 0, file_size_bytes).await
952 }
953
954 pub fn open_reader(self: &Arc<Self>, reader: Arc<dyn Reader>) -> FileScheduler {
965 FileScheduler {
966 reader,
967 block_size: self.object_store.block_size() as u64,
968 root: self.clone(),
969 base_priority: 0,
970 max_iop_size: self.object_store.max_iop_size(),
971 bypass_backpressure: false,
972 extra_stats: None,
973 }
974 }
975
976 fn do_submit_request(
977 &self,
978 reader: Arc<dyn Reader>,
979 request: Vec<Range<u64>>,
980 tx: oneshot::Sender<Response>,
981 priority: u128,
982 io_queue: &Arc<IoQueue>,
983 bypass_backpressure: bool,
984 ) {
985 let num_iops = request.len() as u32;
986
987 let when_all_io_done = move |bytes_and_permits| {
988 let _ = tx.send(bytes_and_permits);
990 };
991
992 let dest = Arc::new(Mutex::new(Box::new(MutableBatch::new(
993 when_all_io_done,
994 num_iops,
995 priority,
996 request.len(),
997 bypass_backpressure,
998 io_queue.clone(),
999 ))));
1000
1001 for (task_idx, iop) in request.into_iter().enumerate() {
1002 let dest = dest.clone();
1003 let io_queue_clone = io_queue.clone();
1004 let num_bytes = iop.end - iop.start;
1005 let task = IoTask {
1006 reader: reader.clone(),
1007 to_read: iop,
1008 priority,
1009 bypass_backpressure,
1010 when_done: Box::new(move |data| {
1011 io_queue_clone.on_iop_complete();
1012 let mut dest = dest.lock().unwrap();
1013 let chunk = DataChunk {
1014 data,
1015 task_idx,
1016 num_bytes,
1017 };
1018 dest.deliver_data(chunk);
1019 }),
1020 };
1021 io_queue.push(task);
1022 }
1023 }
1024
1025 fn submit_request_standard(
1026 &self,
1027 reader: Arc<dyn Reader>,
1028 request: Vec<Range<u64>>,
1029 priority: u128,
1030 io_queue: &Arc<IoQueue>,
1031 bypass_backpressure: bool,
1032 ) -> impl Future<Output = Result<Vec<Bytes>>> + Send + use<> {
1033 let (tx, rx) = oneshot::channel::<Response>();
1034
1035 self.do_submit_request(reader, request, tx, priority, io_queue, bypass_backpressure);
1036
1037 rx.map(|wrapped_rsp| {
1038 let mut rsp = wrapped_rsp.unwrap();
1041 rsp.data.take().unwrap()
1042 })
1043 }
1044
1045 fn submit_request_lite(
1046 &self,
1047 reader: Arc<dyn Reader>,
1048 request: Vec<Range<u64>>,
1049 priority: u128,
1050 io_queue: &Arc<lite::IoQueue>,
1051 bypass_backpressure: bool,
1052 ) -> impl Future<Output = Result<Vec<Bytes>>> + Send + use<> {
1053 let maybe_tasks = request
1055 .into_iter()
1056 .map(|task| {
1057 let reader = reader.clone();
1058 let queue = io_queue.clone();
1059 let run_fn = Box::new(move || {
1060 reader
1061 .get_range(task.start as usize..task.end as usize)
1062 .map_err(Error::from)
1063 .boxed()
1064 });
1065 queue.submit(task, priority, run_fn, bypass_backpressure)
1066 })
1067 .collect::<Result<Vec<_>>>();
1068 match maybe_tasks {
1069 Ok(tasks) => async move {
1070 let mut results = Vec::with_capacity(tasks.len());
1071 for task in tasks {
1072 results.push(task.await?);
1073 }
1074 Ok(results)
1075 }
1076 .boxed(),
1077 Err(e) => async move { Err(e) }.boxed(),
1078 }
1079 }
1080
1081 pub fn submit_request(
1082 &self,
1083 reader: Arc<dyn Reader>,
1084 request: Vec<Range<u64>>,
1085 priority: u128,
1086 bypass_backpressure: bool,
1087 ) -> impl Future<Output = Result<Vec<Bytes>>> + Send + use<> {
1088 match &self.io_queue {
1089 IoQueueType::Standard(io_queue) => {
1090 futures::future::Either::Left(self.submit_request_standard(
1091 reader,
1092 request,
1093 priority,
1094 io_queue,
1095 bypass_backpressure,
1096 ))
1097 }
1098 IoQueueType::Lite(io_queue) => futures::future::Either::Right(
1099 self.submit_request_lite(reader, request, priority, io_queue, bypass_backpressure),
1100 ),
1101 }
1102 }
1103
1104 pub fn stats(&self) -> ScanStats {
1105 self.stats.snapshot()
1106 }
1107
1108 #[cfg(test)]
1109 fn uses_lite_scheduler(&self) -> bool {
1110 matches!(self.io_queue, IoQueueType::Lite(_))
1111 }
1112}
1113
1114impl Drop for ScanScheduler {
1115 fn drop(&mut self) {
1116 match &self.io_queue {
1128 IoQueueType::Standard(io_queue) => io_queue.close(),
1129 IoQueueType::Lite(io_queue) => io_queue.close(),
1130 }
1131 }
1132}
1133
1134#[derive(Clone, Debug)]
1136pub struct FileScheduler {
1137 reader: Arc<dyn Reader>,
1138 root: Arc<ScanScheduler>,
1139 block_size: u64,
1140 base_priority: u64,
1141 max_iop_size: u64,
1142 bypass_backpressure: bool,
1143 extra_stats: Option<Arc<dyn IoStatsRecorder>>,
1147}
1148
1149fn is_close_together(range1: &Range<u64>, range2: &Range<u64>, block_size: u64) -> bool {
1150 range2.start <= (range1.end + block_size)
1152}
1153
1154fn is_overlapping(range1: &Range<u64>, range2: &Range<u64>) -> bool {
1155 range1.start < range2.end && range2.start < range1.end
1156}
1157
1158impl FileScheduler {
1159 pub fn submit_request(
1172 &self,
1173 request: Vec<Range<u64>>,
1174 priority: u64,
1175 ) -> impl Future<Output = Result<Vec<Bytes>>> + Send + use<> {
1176 let priority = ((self.base_priority as u128) << 64) + priority as u128;
1178
1179 let mut merged_requests = Vec::with_capacity(request.len());
1180
1181 if !request.is_empty() {
1182 let mut curr_interval = request[0].clone();
1183
1184 for req in request.iter().skip(1) {
1185 if is_close_together(&curr_interval, req, self.block_size) {
1186 curr_interval.end = curr_interval.end.max(req.end);
1187 } else {
1188 merged_requests.push(curr_interval);
1189 curr_interval = req.clone();
1190 }
1191 }
1192
1193 merged_requests.push(curr_interval);
1194 }
1195
1196 let mut updated_requests = Vec::with_capacity(merged_requests.len());
1197 for req in merged_requests {
1198 if req.is_empty() {
1199 updated_requests.push(req);
1200 } else {
1201 let num_requests = (req.end - req.start).div_ceil(self.max_iop_size);
1202 let bytes_per_request = (req.end - req.start) / num_requests;
1203 for i in 0..num_requests {
1204 let start = req.start + i * bytes_per_request;
1205 let end = if i == num_requests - 1 {
1206 req.end
1208 } else {
1209 start + bytes_per_request
1210 };
1211 updated_requests.push(start..end);
1212 }
1213 }
1214 }
1215
1216 self.root.stats.record_request(&updated_requests);
1217 if let Some(extra_stats) = &self.extra_stats {
1218 extra_stats.record_request(&updated_requests);
1219 }
1220
1221 let bytes_vec_fut = self.root.submit_request(
1222 self.reader.clone(),
1223 updated_requests.clone(),
1224 priority,
1225 self.bypass_backpressure,
1226 );
1227
1228 let mut updated_index = 0;
1229 let mut final_bytes = Vec::with_capacity(request.len());
1230
1231 async move {
1232 let bytes_vec = bytes_vec_fut.await?;
1233
1234 let mut orig_index = 0;
1235 while (updated_index < updated_requests.len()) && (orig_index < request.len()) {
1236 let updated_range = &updated_requests[updated_index];
1237 let orig_range = &request[orig_index];
1238 let byte_offset = updated_range.start as usize;
1239
1240 if is_overlapping(updated_range, orig_range) {
1241 let start = orig_range.start as usize - byte_offset;
1243 if orig_range.end <= updated_range.end {
1244 let end = orig_range.end as usize - byte_offset;
1247 final_bytes.push(bytes_vec[updated_index].slice(start..end));
1248 } else {
1249 let orig_size = orig_range.end - orig_range.start;
1252 let mut merged_bytes = Vec::with_capacity(orig_size as usize);
1253 merged_bytes.extend_from_slice(&bytes_vec[updated_index].slice(start..));
1254 let mut copy_offset = merged_bytes.len() as u64;
1255 while copy_offset < orig_size {
1256 updated_index += 1;
1257 let next_range = &updated_requests[updated_index];
1258 let bytes_to_take =
1259 (orig_size - copy_offset).min(next_range.end - next_range.start);
1260 merged_bytes.extend_from_slice(
1261 &bytes_vec[updated_index].slice(0..bytes_to_take as usize),
1262 );
1263 copy_offset += bytes_to_take;
1264 }
1265 final_bytes.push(Bytes::from(merged_bytes));
1266 }
1267 orig_index += 1;
1268 } else {
1269 updated_index += 1;
1270 }
1271 }
1272
1273 Ok(final_bytes)
1274 }
1275 }
1276
1277 pub fn with_priority(&self, priority: u64) -> Self {
1278 Self {
1279 reader: self.reader.clone(),
1280 root: self.root.clone(),
1281 block_size: self.block_size,
1282 max_iop_size: self.max_iop_size,
1283 base_priority: priority,
1284 bypass_backpressure: self.bypass_backpressure,
1285 extra_stats: self.extra_stats.clone(),
1286 }
1287 }
1288
1289 pub fn with_io_stats(&self, stats: Arc<dyn IoStatsRecorder>) -> Self {
1299 Self {
1300 extra_stats: Some(stats),
1301 ..self.clone()
1302 }
1303 }
1304
1305 pub fn with_bypass_backpressure(&self) -> Self {
1310 Self {
1311 bypass_backpressure: true,
1312 ..self.clone()
1313 }
1314 }
1315
1316 pub fn submit_single(
1323 &self,
1324 range: Range<u64>,
1325 priority: u64,
1326 ) -> impl Future<Output = Result<Bytes>> + Send {
1327 self.submit_request(vec![range], priority)
1328 .map_ok(|vec_bytes| vec_bytes.into_iter().next().unwrap())
1329 }
1330
1331 pub fn reader(&self) -> &Arc<dyn Reader> {
1337 &self.reader
1338 }
1339}
1340
1341#[cfg(test)]
1342mod tests {
1343 use std::{collections::VecDeque, time::Duration};
1344
1345 use futures::poll;
1346 use lance_core::utils::tempfile::TempObjFile;
1347 use rand::RngCore;
1348
1349 use object_store::{GetRange, ObjectStore as OSObjectStore, ObjectStoreExt, memory::InMemory};
1350 use tokio::{runtime::Handle, time::timeout};
1351 use url::Url;
1352
1353 use crate::{
1354 object_store::{DEFAULT_DOWNLOAD_RETRY_COUNT, DEFAULT_MAX_IOP_SIZE},
1355 testing::MockObjectStore,
1356 };
1357
1358 use super::*;
1359
1360 fn make_task(priority: u128, bypass_backpressure: bool) -> IoTask {
1361 IoTask {
1362 reader: Arc::new(TrackingReader {
1363 get_range_count: Arc::new(AtomicU64::new(0)),
1364 path: Path::parse("test").unwrap(),
1365 }),
1366 to_read: 0..1,
1367 when_done: Box::new(|_| {}),
1368 priority,
1369 bypass_backpressure,
1370 }
1371 }
1372
1373 #[test]
1374 fn test_scheduler_state_event_fields() {
1375 use tracing_mock::{expect, subscriber};
1376
1377 let event = expect::event()
1378 .with_target(SCHEDULER_STATE_EVENT_TARGET)
1379 .at_level(tracing::Level::TRACE)
1380 .with_fields(
1381 expect::field("queue_kind")
1382 .with_value(&"standard")
1383 .and(expect::field("scheduler_iops").with_value(&7u64))
1384 .and(expect::field("scheduler_requests").with_value(&3u64))
1385 .and(expect::field("scheduler_bytes_read").with_value(&4096u64))
1386 .and(expect::field("io_capacity").with_value(&4u64))
1387 .and(expect::field("pending_iops").with_value(&1u64))
1388 .and(expect::field("bytes_available").with_value(&128i64))
1389 .and(expect::field("head_task_bytes_present").with_value(&true))
1390 .and(expect::field("head_task_bytes").with_value(&1u64))
1391 .and(expect::field("head_task_can_deliver_present").with_value(&true))
1392 .and(expect::field("head_task_can_deliver").with_value(&true)),
1393 );
1394 let (subscriber, handle) = subscriber::mock().event(event).run_with_handle();
1395
1396 let stats = IoStats::new();
1397 stats.add_scan_stats(&ScanStats {
1398 iops: 7,
1399 requests: 3,
1400 bytes_read: 4096,
1401 });
1402 let mut state = IoQueueState::new(4, 192);
1403 state.iops_avail = 2;
1404 state.bytes_avail = 128;
1405 state.pending_requests.push(make_task(1, false));
1406
1407 tracing::subscriber::with_default(subscriber, || {
1408 emit_scheduler_state_event(state.scheduler_state_event(), &stats);
1409 });
1410
1411 handle.assert_finished();
1412 }
1413
1414 #[test]
1415 fn test_iotask_ordering() {
1416 let mut heap = BinaryHeap::new();
1419 heap.push(make_task(10, false)); heap.push(make_task(1, false)); heap.push(make_task(20, true)); heap.push(make_task(5, true)); let order: Vec<(u128, bool)> = std::iter::from_fn(|| heap.pop())
1425 .map(|t| (t.priority, t.bypass_backpressure))
1426 .collect();
1427
1428 assert_eq!(order, vec![(5, true), (20, true), (1, false), (10, false)]);
1429 }
1430
1431 #[test]
1432 fn test_batch_with_undelivered_slot_is_error() {
1433 let response = Arc::new(Mutex::new(None));
1434 let response_clone = response.clone();
1435 let io_queue = Arc::new(IoQueue::new(1, 1024, IoStats::new()));
1436 let batch = MutableBatch::new(
1437 move |rsp| *response_clone.lock().unwrap() = Some(rsp),
1438 2, 0, 2, false,
1442 io_queue,
1443 );
1444 drop(batch);
1445
1446 let mut rsp = response.lock().unwrap().take().unwrap();
1447 let data = rsp.data.take().unwrap();
1448 assert!(
1449 data.is_err(),
1450 "undelivered slot must yield an error, got {data:?}",
1451 );
1452 }
1453
1454 #[tokio::test]
1455 async fn test_full_seq_read() {
1456 let tmp_file = TempObjFile::default();
1457
1458 let obj_store = Arc::new(ObjectStore::local());
1459
1460 const DATA_SIZE: u64 = 1024 * 1024;
1462 let mut some_data = vec![0; DATA_SIZE as usize];
1463 rand::rng().fill_bytes(&mut some_data);
1464 obj_store.put(&tmp_file, &some_data).await.unwrap();
1465
1466 let config = SchedulerConfig::default_for_testing();
1467
1468 let scheduler = ScanScheduler::new(obj_store, config);
1469
1470 let file_scheduler = scheduler
1471 .open_file(&tmp_file, &CachedFileSize::unknown())
1472 .await
1473 .unwrap();
1474
1475 const READ_SIZE: u64 = 4 * 1024;
1477 let mut reqs = VecDeque::new();
1478 let mut offset = 0;
1479 while offset < DATA_SIZE {
1480 reqs.push_back(
1481 #[allow(clippy::single_range_in_vec_init)]
1482 file_scheduler
1483 .submit_request(vec![offset..offset + READ_SIZE], 0)
1484 .await
1485 .unwrap(),
1486 );
1487 offset += READ_SIZE;
1488 }
1489
1490 offset = 0;
1491 while offset < DATA_SIZE {
1493 let data = reqs.pop_front().unwrap();
1494 let actual = &data[0];
1495 let expected = &some_data[offset as usize..(offset + READ_SIZE) as usize];
1496 assert_eq!(expected, actual);
1497 offset += READ_SIZE;
1498 }
1499 }
1500
1501 #[tokio::test]
1502 async fn test_open_reader_bridge() {
1503 let tmp_file = TempObjFile::default();
1504
1505 let obj_store = Arc::new(ObjectStore::local());
1506
1507 const DATA_SIZE: u64 = 64 * 1024;
1508 let mut some_data = vec![0; DATA_SIZE as usize];
1509 rand::rng().fill_bytes(&mut some_data);
1510 obj_store.put(&tmp_file, &some_data).await.unwrap();
1511
1512 let config = SchedulerConfig::default_for_testing();
1513 let scheduler = ScanScheduler::new(obj_store.clone(), config);
1514
1515 let reader: Arc<dyn Reader> = obj_store.open(&tmp_file).await.unwrap().into();
1517 let file_scheduler = scheduler.open_reader(reader);
1518
1519 let bytes = file_scheduler
1520 .submit_request(vec![0..DATA_SIZE], 0)
1521 .await
1522 .unwrap();
1523 assert_eq!(bytes[0], some_data);
1524 }
1525
1526 #[tokio::test]
1527 async fn test_split_coalesce() {
1528 let tmp_file = TempObjFile::default();
1529
1530 let obj_store = Arc::new(ObjectStore::local());
1531
1532 const DATA_SIZE: u64 = 75 * 1024 * 1024;
1534 let mut some_data = vec![0; DATA_SIZE as usize];
1535 rand::rng().fill_bytes(&mut some_data);
1536 obj_store.put(&tmp_file, &some_data).await.unwrap();
1537
1538 let config = SchedulerConfig::default_for_testing();
1539
1540 let scheduler = ScanScheduler::new(obj_store, config);
1541
1542 let file_scheduler = scheduler
1543 .open_file(&tmp_file, &CachedFileSize::unknown())
1544 .await
1545 .unwrap();
1546
1547 let req =
1550 file_scheduler.submit_request(vec![50_000..51_000, 52_000..53_000, 54_000..55_000], 0);
1551
1552 let bytes = req.await.unwrap();
1553
1554 assert_eq!(bytes[0], &some_data[50_000..51_000]);
1555 assert_eq!(bytes[1], &some_data[52_000..53_000]);
1556 assert_eq!(bytes[2], &some_data[54_000..55_000]);
1557
1558 assert_eq!(1, scheduler.stats().iops);
1559
1560 let req = file_scheduler.submit_request(vec![0..DATA_SIZE], 0);
1562 let bytes = req.await.unwrap();
1563 assert!(bytes[0] == some_data, "data is not the same");
1564
1565 assert_eq!(6, scheduler.stats().iops);
1566
1567 let chunk_size = *DEFAULT_MAX_IOP_SIZE;
1571 let req = file_scheduler.submit_request(
1572 vec![
1573 10..chunk_size,
1574 chunk_size + 10..(chunk_size * 2) - 20,
1575 chunk_size * 2..(chunk_size * 2) + 10,
1576 ],
1577 0,
1578 );
1579
1580 let bytes = req.await.unwrap();
1581 let chunk_size = chunk_size as usize;
1582 assert!(
1583 bytes[0] == some_data[10..chunk_size],
1584 "data is not the same"
1585 );
1586 assert!(
1587 bytes[1] == some_data[chunk_size + 10..(chunk_size * 2) - 20],
1588 "data is not the same"
1589 );
1590 assert!(
1591 bytes[2] == some_data[chunk_size * 2..(chunk_size * 2) + 10],
1592 "data is not the same"
1593 );
1594 assert_eq!(8, scheduler.stats().iops);
1595
1596 let reads = (0..44)
1597 .map(|i| i * 1_000_000..(i + 1) * 1_000_000)
1598 .collect::<Vec<_>>();
1599 let req = file_scheduler.submit_request(reads, 0);
1600 let bytes = req.await.unwrap();
1601 for (i, bytes) in bytes.iter().enumerate() {
1602 assert!(
1603 bytes == &some_data[i * 1_000_000..(i + 1) * 1_000_000],
1604 "data is not the same"
1605 );
1606 }
1607 assert_eq!(11, scheduler.stats().iops);
1608 }
1609
1610 #[tokio::test]
1611 async fn test_io_stats_sink() {
1612 let tmp_file = TempObjFile::default();
1613 let obj_store = Arc::new(ObjectStore::local());
1614
1615 const DATA_SIZE: u64 = 1024 * 1024;
1616 let mut some_data = vec![0; DATA_SIZE as usize];
1617 rand::rng().fill_bytes(&mut some_data);
1618 obj_store.put(&tmp_file, &some_data).await.unwrap();
1619
1620 let scheduler = ScanScheduler::new(obj_store, SchedulerConfig::default_for_testing());
1621
1622 let sink = IoStats::new();
1624 let file_scheduler = scheduler
1625 .open_file(&tmp_file, &CachedFileSize::unknown())
1626 .await
1627 .unwrap()
1628 .with_io_stats(sink.recorder());
1629
1630 file_scheduler
1634 .submit_request(vec![50_000..51_000, 52_000..53_000, 54_000..55_000], 0)
1635 .await
1636 .unwrap();
1637
1638 let global = scheduler.stats();
1639 let scoped = sink.snapshot();
1640 assert_eq!(1, scoped.iops);
1641 assert_eq!(1, scoped.requests);
1642 assert_eq!(5000, scoped.bytes_read);
1644 assert_eq!(global.iops, scoped.iops);
1645 assert_eq!(global.requests, scoped.requests);
1646 assert_eq!(global.bytes_read, scoped.bytes_read);
1647
1648 let other = scheduler
1651 .open_file(&tmp_file, &CachedFileSize::unknown())
1652 .await
1653 .unwrap();
1654 other.submit_request(vec![0..1000], 0).await.unwrap();
1655
1656 let global_after = scheduler.stats();
1657 let scoped_after = sink.snapshot();
1658 assert_eq!(global.bytes_read + 1000, global_after.bytes_read);
1659 assert_eq!(scoped.bytes_read, scoped_after.bytes_read);
1660 assert_eq!(scoped.iops, scoped_after.iops);
1661 }
1662
1663 #[tokio::test]
1664 async fn test_priority() {
1665 let some_path = Path::parse("foo").unwrap();
1666 let base_store = Arc::new(InMemory::new());
1667 base_store
1668 .put(&some_path, vec![0; 1000].into())
1669 .await
1670 .unwrap();
1671
1672 let semaphore = Arc::new(tokio::sync::Semaphore::new(0));
1673 let mut obj_store = MockObjectStore::default();
1674 let semaphore_copy = semaphore.clone();
1675 obj_store
1676 .expect_get_opts()
1677 .returning(move |location, options| {
1678 let semaphore = semaphore.clone();
1679 let base_store = base_store.clone();
1680 let location = location.clone();
1681 async move {
1682 semaphore.acquire().await.unwrap().forget();
1683 base_store.get_opts(&location, options).await
1684 }
1685 .boxed()
1686 });
1687 let obj_store = Arc::new(ObjectStore::new(
1688 Arc::new(obj_store),
1689 Url::parse("mem://").unwrap(),
1690 Some(500),
1691 None,
1692 false,
1693 false,
1694 1,
1695 DEFAULT_DOWNLOAD_RETRY_COUNT,
1696 None,
1697 ));
1698
1699 let config = SchedulerConfig {
1700 io_buffer_size_bytes: 1024 * 1024,
1701 use_lite_scheduler: None,
1702 };
1703
1704 let scan_scheduler = ScanScheduler::new(obj_store, config);
1705
1706 let file_scheduler = scan_scheduler
1707 .open_file(&Path::parse("foo").unwrap(), &CachedFileSize::new(1000))
1708 .await
1709 .unwrap();
1710
1711 let first_fut = timeout(
1715 Duration::from_secs(10),
1716 file_scheduler.submit_single(0..10, 0),
1717 )
1718 .boxed();
1719
1720 let mut second_fut = timeout(
1722 Duration::from_secs(10),
1723 file_scheduler.submit_single(0..20, 100),
1724 )
1725 .boxed();
1726
1727 let mut third_fut = timeout(
1730 Duration::from_secs(10),
1731 file_scheduler.submit_single(0..30, 0),
1732 )
1733 .boxed();
1734
1735 semaphore_copy.add_permits(1);
1737 assert!(first_fut.await.unwrap().unwrap().len() == 10);
1738 assert!(poll!(&mut second_fut).is_pending());
1740 assert!(poll!(&mut third_fut).is_pending());
1741
1742 semaphore_copy.add_permits(1);
1744 assert!(third_fut.await.unwrap().unwrap().len() == 30);
1745 assert!(poll!(&mut second_fut).is_pending());
1746
1747 semaphore_copy.add_permits(1);
1749 assert!(second_fut.await.unwrap().unwrap().len() == 20);
1750 }
1751
1752 #[tokio::test]
1753 async fn test_standard_scheduler_state_tracks_queue_state() {
1754 let some_path = Path::parse("foo").unwrap();
1755 let base_store = Arc::new(InMemory::new());
1756 base_store
1757 .put(&some_path, vec![0; 1000].into())
1758 .await
1759 .unwrap();
1760
1761 let semaphore = Arc::new(tokio::sync::Semaphore::new(0));
1762 let mut obj_store = MockObjectStore::default();
1763 let semaphore_copy = semaphore.clone();
1764 obj_store
1765 .expect_get_opts()
1766 .returning(move |location, options| {
1767 let semaphore = semaphore.clone();
1768 let base_store = base_store.clone();
1769 let location = location.clone();
1770 async move {
1771 semaphore.acquire().await.unwrap().forget();
1772 base_store.get_opts(&location, options).await
1773 }
1774 .boxed()
1775 });
1776 let obj_store = Arc::new(ObjectStore::new(
1777 Arc::new(obj_store),
1778 Url::parse("mem://").unwrap(),
1779 Some(500),
1780 None,
1781 false,
1782 false,
1783 1,
1784 DEFAULT_DOWNLOAD_RETRY_COUNT,
1785 None,
1786 ));
1787
1788 let scheduler = ScanScheduler::new(
1789 obj_store,
1790 SchedulerConfig {
1791 io_buffer_size_bytes: 1024 * 1024,
1792 use_lite_scheduler: Some(false),
1793 },
1794 );
1795 let file_scheduler = scheduler
1796 .open_file(&Path::parse("foo").unwrap(), &CachedFileSize::new(1000))
1797 .await
1798 .unwrap();
1799
1800 let first_fut = timeout(
1801 Duration::from_secs(10),
1802 file_scheduler.submit_single(0..10, 0),
1803 )
1804 .boxed();
1805 let second_fut = timeout(
1806 Duration::from_secs(10),
1807 file_scheduler.submit_single(0..20, 100),
1808 )
1809 .boxed();
1810 let third_fut = timeout(
1811 Duration::from_secs(10),
1812 file_scheduler.submit_single(0..30, 0),
1813 )
1814 .boxed();
1815
1816 let io_queue = match &scheduler.io_queue {
1817 IoQueueType::Standard(io_queue) => io_queue.clone(),
1818 IoQueueType::Lite(_) => unreachable!("test forces the standard scheduler"),
1819 };
1820 let (
1821 io_capacity,
1822 iops_available,
1823 pending_bytes,
1824 bytes_reserved,
1825 priorities_in_flight,
1826 head_task_bytes,
1827 head_task_blocked_by_iops,
1828 head_task_blocked_by_bytes,
1829 ) = timeout(Duration::from_secs(5), async {
1830 loop {
1831 let observed = {
1832 let state = io_queue.state.lock().unwrap();
1833 let active_iops = state.io_capacity.saturating_sub(state.iops_avail);
1834 if active_iops == 1 && state.pending_requests.len() == 2 {
1835 let pending_bytes = state
1836 .pending_requests
1837 .iter()
1838 .map(IoTask::num_bytes)
1839 .sum::<u64>();
1840 let head_task = state.pending_requests.peek().unwrap();
1841 let bypasses_bytes = state.no_backpressure
1842 || head_task.bypass_backpressure
1843 || head_task.priority <= state.priorities_in_flight.min_in_flight();
1844 Some((
1845 state.io_capacity,
1846 state.iops_avail,
1847 pending_bytes,
1848 state.io_buffer_size as i64 - state.bytes_avail,
1849 state.priorities_in_flight.len(),
1850 head_task.num_bytes(),
1851 state.iops_avail == 0,
1852 !bypasses_bytes && head_task.num_bytes() as i64 > state.bytes_avail,
1853 ))
1854 } else {
1855 None
1856 }
1857 };
1858 if let Some(observed) = observed {
1859 break observed;
1860 }
1861 tokio::task::yield_now().await;
1862 }
1863 })
1864 .await
1865 .unwrap();
1866
1867 assert_eq!(io_capacity, 1);
1868 assert_eq!(iops_available, 0);
1869 assert_eq!(pending_bytes, 50);
1870 assert_eq!(bytes_reserved, 10);
1871 assert_eq!(priorities_in_flight, 1);
1872 assert_eq!(head_task_bytes, 30);
1873 assert!(head_task_blocked_by_iops);
1874 assert!(!head_task_blocked_by_bytes);
1875
1876 semaphore_copy.add_permits(3);
1877 assert_eq!(first_fut.await.unwrap().unwrap().len(), 10);
1878 assert_eq!(third_fut.await.unwrap().unwrap().len(), 30);
1879 assert_eq!(second_fut.await.unwrap().unwrap().len(), 20);
1880 }
1881
1882 #[tokio::test(flavor = "multi_thread")]
1883 async fn test_backpressure() {
1884 let some_path = Path::parse("foo").unwrap();
1885 let base_store = Arc::new(InMemory::new());
1886 base_store
1887 .put(&some_path, vec![0; 100000].into())
1888 .await
1889 .unwrap();
1890
1891 let bytes_read = Arc::new(AtomicU64::from(0));
1892 let mut obj_store = MockObjectStore::default();
1893 let bytes_read_copy = bytes_read.clone();
1894 obj_store
1896 .expect_get_opts()
1897 .returning(move |location, options| {
1898 let range = options.range.as_ref().unwrap();
1899 let num_bytes = match range {
1900 GetRange::Bounded(bounded) => bounded.end - bounded.start,
1901 _ => panic!(),
1902 };
1903 bytes_read_copy.fetch_add(num_bytes, Ordering::Release);
1904 let location = location.clone();
1905 let base_store = base_store.clone();
1906 async move { base_store.get_opts(&location, options).await }.boxed()
1907 });
1908 let obj_store = Arc::new(ObjectStore::new(
1909 Arc::new(obj_store),
1910 Url::parse("mem://").unwrap(),
1911 Some(500),
1912 None,
1913 false,
1914 false,
1915 1,
1916 DEFAULT_DOWNLOAD_RETRY_COUNT,
1917 None,
1918 ));
1919
1920 let config = SchedulerConfig {
1921 io_buffer_size_bytes: 10,
1922 use_lite_scheduler: None,
1923 };
1924
1925 let scan_scheduler = ScanScheduler::new(obj_store.clone(), config);
1926
1927 let file_scheduler = scan_scheduler
1928 .open_file(&Path::parse("foo").unwrap(), &CachedFileSize::new(100000))
1929 .await
1930 .unwrap();
1931
1932 let wait_for_idle = || async move {
1933 let handle = Handle::current();
1934 while handle.metrics().num_alive_tasks() != 1 {
1935 tokio::time::sleep(Duration::from_millis(10)).await;
1936 }
1937 };
1938 let wait_for_bytes_read_and_idle = |target_bytes: u64| {
1939 let bytes_read = &bytes_read;
1941 async move {
1942 let bytes_read_copy = bytes_read.clone();
1943 while bytes_read_copy.load(Ordering::Acquire) < target_bytes {
1944 tokio::time::sleep(Duration::from_millis(10)).await;
1945 }
1946 wait_for_idle().await;
1947 }
1948 };
1949
1950 let first_fut = file_scheduler.submit_single(0..5, 0);
1952 let second_fut = file_scheduler.submit_single(0..5, 0);
1954 let third_fut = file_scheduler.submit_single(0..3, 0);
1956 wait_for_bytes_read_and_idle(10).await;
1958
1959 assert_eq!(first_fut.await.unwrap().len(), 5);
1960 wait_for_bytes_read_and_idle(13).await;
1962
1963 let fourth_fut = file_scheduler.submit_single(0..5, 0);
1965 wait_for_bytes_read_and_idle(13).await;
1966
1967 assert_eq!(third_fut.await.unwrap().len(), 3);
1969 wait_for_bytes_read_and_idle(18).await;
1970
1971 assert_eq!(second_fut.await.unwrap().len(), 5);
1972 let fifth_fut = file_scheduler.submit_request(vec![0..3, 90000..90007], 0);
1979 wait_for_bytes_read_and_idle(21).await;
1980
1981 let fifth_bytes = tokio::time::timeout(Duration::from_secs(10), fifth_fut)
1983 .await
1984 .unwrap();
1985 assert_eq!(
1986 fifth_bytes.unwrap().iter().map(|b| b.len()).sum::<usize>(),
1987 10
1988 );
1989
1990 assert_eq!(fourth_fut.await.unwrap().len(), 5);
1992 wait_for_bytes_read_and_idle(28).await;
1993
1994 let config = SchedulerConfig {
1996 io_buffer_size_bytes: 10,
1997 use_lite_scheduler: None,
1998 };
1999
2000 let scan_scheduler = ScanScheduler::new(obj_store, config);
2001 let file_scheduler = scan_scheduler
2002 .open_file(&Path::parse("foo").unwrap(), &CachedFileSize::new(100000))
2003 .await
2004 .unwrap();
2005
2006 let first_fut = file_scheduler.submit_single(0..10, 0);
2007 let second_fut = file_scheduler.submit_single(0..10, 0);
2008
2009 std::thread::sleep(Duration::from_millis(100));
2010 assert_eq!(first_fut.await.unwrap().len(), 10);
2011 assert_eq!(second_fut.await.unwrap().len(), 10);
2012 }
2013
2014 #[derive(Debug)]
2015 struct BlockingReader {
2016 semaphore: Arc<tokio::sync::Semaphore>,
2017 get_range_count: Arc<AtomicU64>,
2018 path: Path,
2019 }
2020
2021 impl lance_core::deepsize::DeepSizeOf for BlockingReader {
2022 fn deep_size_of_children(&self, _context: &mut lance_core::deepsize::Context) -> usize {
2023 0
2024 }
2025 }
2026
2027 impl Reader for BlockingReader {
2028 fn path(&self) -> &Path {
2029 &self.path
2030 }
2031
2032 fn block_size(&self) -> usize {
2033 4096
2034 }
2035
2036 fn io_parallelism(&self) -> usize {
2037 1
2038 }
2039
2040 fn size(&self) -> futures::future::BoxFuture<'_, object_store::Result<usize>> {
2041 Box::pin(async { Ok(1_000_000) })
2042 }
2043
2044 fn get_range(
2045 &self,
2046 range: Range<usize>,
2047 ) -> futures::future::BoxFuture<'static, object_store::Result<Bytes>> {
2048 self.get_range_count.fetch_add(1, Ordering::Release);
2049 let semaphore = self.semaphore.clone();
2050 let num_bytes = range.end - range.start;
2051 Box::pin(async move {
2052 semaphore.acquire().await.unwrap().forget();
2053 Ok(Bytes::from(vec![0u8; num_bytes]))
2054 })
2055 }
2056
2057 fn get_all(&self) -> futures::future::BoxFuture<'_, object_store::Result<Bytes>> {
2058 Box::pin(async { Ok(Bytes::from(vec![0u8; 1_000_000])) })
2059 }
2060 }
2061
2062 #[tokio::test(flavor = "multi_thread")]
2063 async fn test_same_priority_chunks_continue_after_higher_priority_request() {
2064 let obj_store = Arc::new(ObjectStore::new(
2065 Arc::new(InMemory::new()),
2066 Url::parse("mem://").unwrap(),
2067 Some(4096),
2068 None,
2069 false,
2070 false,
2071 1,
2072 DEFAULT_DOWNLOAD_RETRY_COUNT,
2073 None,
2074 ));
2075 let scheduler = ScanScheduler::new(
2076 obj_store,
2077 SchedulerConfig {
2078 io_buffer_size_bytes: 10,
2079 use_lite_scheduler: Some(false),
2080 },
2081 );
2082 let semaphore = Arc::new(tokio::sync::Semaphore::new(0));
2083 let reader: Arc<dyn Reader> = Arc::new(BlockingReader {
2084 semaphore: semaphore.clone(),
2085 get_range_count: Arc::new(AtomicU64::new(0)),
2086 path: Path::parse("test").unwrap(),
2087 });
2088
2089 let low_priority =
2090 scheduler.submit_request(reader.clone(), vec![0..6, 100..106], 10, false);
2091 let high_priority = scheduler.submit_request(reader, vec![200..204], 0, false);
2092
2093 semaphore.add_permits(3);
2094 let low_priority = timeout(Duration::from_secs(5), low_priority)
2095 .await
2096 .unwrap()
2097 .unwrap();
2098 assert_eq!(
2099 low_priority.iter().map(|bytes| bytes.len()).sum::<usize>(),
2100 12
2101 );
2102
2103 let high_priority = timeout(Duration::from_secs(5), high_priority)
2104 .await
2105 .unwrap()
2106 .unwrap();
2107 assert_eq!(high_priority[0].len(), 4);
2108 }
2109
2110 #[derive(Debug)]
2112 struct TrackingReader {
2113 get_range_count: Arc<AtomicU64>,
2114 path: Path,
2115 }
2116
2117 impl lance_core::deepsize::DeepSizeOf for TrackingReader {
2118 fn deep_size_of_children(&self, _context: &mut lance_core::deepsize::Context) -> usize {
2119 0
2120 }
2121 }
2122
2123 impl Reader for TrackingReader {
2124 fn path(&self) -> &Path {
2125 &self.path
2126 }
2127
2128 fn block_size(&self) -> usize {
2129 4096
2130 }
2131
2132 fn io_parallelism(&self) -> usize {
2133 1
2134 }
2135
2136 fn size(&self) -> futures::future::BoxFuture<'_, object_store::Result<usize>> {
2137 Box::pin(async { Ok(1_000_000) })
2138 }
2139
2140 fn get_range(
2141 &self,
2142 range: Range<usize>,
2143 ) -> futures::future::BoxFuture<'static, object_store::Result<Bytes>> {
2144 self.get_range_count.fetch_add(1, Ordering::Release);
2145 let num_bytes = range.end - range.start;
2146 Box::pin(async move { Ok(Bytes::from(vec![0u8; num_bytes])) })
2147 }
2148
2149 fn get_all(&self) -> futures::future::BoxFuture<'_, object_store::Result<Bytes>> {
2150 Box::pin(async { Ok(Bytes::from(vec![0u8; 1_000_000])) })
2151 }
2152 }
2153
2154 #[tokio::test]
2155 async fn test_lite_scheduler_submits_eagerly() {
2156 let obj_store = Arc::new(ObjectStore::memory());
2157 let config = SchedulerConfig::default_for_testing().with_lite_scheduler();
2158 let scheduler = ScanScheduler::new(obj_store, config);
2159
2160 let get_range_count = Arc::new(AtomicU64::new(0));
2161 let reader: Arc<dyn Reader> = Arc::new(TrackingReader {
2162 get_range_count: get_range_count.clone(),
2163 path: Path::parse("test").unwrap(),
2164 });
2165
2166 let fut1 = scheduler.submit_request(reader.clone(), vec![0..100], 0, false);
2169 let fut2 = scheduler.submit_request(reader.clone(), vec![100..200], 10, false);
2170 let fut3 = scheduler.submit_request(reader.clone(), vec![200..300], 20, false);
2171
2172 assert_eq!(get_range_count.load(Ordering::Acquire), 3);
2174
2175 assert_eq!(fut1.await.unwrap()[0].len(), 100);
2177 assert_eq!(fut2.await.unwrap()[0].len(), 100);
2178 assert_eq!(fut3.await.unwrap()[0].len(), 100);
2179 }
2180
2181 #[tokio::test]
2182 async fn test_object_store_selects_scheduler() {
2183 let memory_store = Arc::new(ObjectStore::memory());
2185 assert!(!memory_store.prefers_lite_scheduler());
2186 let config = SchedulerConfig {
2187 io_buffer_size_bytes: 256 * 1024 * 1024,
2188 use_lite_scheduler: None,
2189 };
2190 let scheduler = ScanScheduler::new(memory_store.clone(), config);
2191 assert!(!scheduler.uses_lite_scheduler());
2192
2193 let uring_store = Arc::new(ObjectStore::new(
2195 Arc::new(InMemory::new()),
2196 Url::parse("file+uring:///tmp").unwrap(),
2197 None,
2198 None,
2199 false,
2200 false,
2201 8,
2202 DEFAULT_DOWNLOAD_RETRY_COUNT,
2203 None,
2204 ));
2205 assert!(uring_store.prefers_lite_scheduler());
2206 let config = SchedulerConfig {
2207 io_buffer_size_bytes: 256 * 1024 * 1024,
2208 use_lite_scheduler: None,
2209 };
2210 let scheduler = ScanScheduler::new(uring_store.clone(), config);
2211 assert!(scheduler.uses_lite_scheduler());
2212
2213 let config = SchedulerConfig {
2215 io_buffer_size_bytes: 256 * 1024 * 1024,
2216 use_lite_scheduler: Some(false),
2217 };
2218 let scheduler = ScanScheduler::new(uring_store, config);
2219 assert!(!scheduler.uses_lite_scheduler());
2220
2221 let config = SchedulerConfig {
2223 io_buffer_size_bytes: 256 * 1024 * 1024,
2224 use_lite_scheduler: Some(true),
2225 };
2226 let scheduler = ScanScheduler::new(memory_store, config);
2227 assert!(scheduler.uses_lite_scheduler());
2228 }
2229
2230 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2231 async fn stress_backpressure() {
2232 let some_path = Path::parse("foo").unwrap();
2236 let obj_store = Arc::new(ObjectStore::memory());
2237 obj_store
2238 .put(&some_path, vec![0; 100000].as_slice())
2239 .await
2240 .unwrap();
2241
2242 let config = SchedulerConfig {
2244 io_buffer_size_bytes: 1,
2245 use_lite_scheduler: None,
2246 };
2247 let scan_scheduler = ScanScheduler::new(obj_store.clone(), config);
2248 let file_scheduler = scan_scheduler
2249 .open_file(&some_path, &CachedFileSize::unknown())
2250 .await
2251 .unwrap();
2252
2253 let mut futs = Vec::with_capacity(10000);
2254 for idx in 0..10000 {
2255 futs.push(file_scheduler.submit_single(idx..idx + 1, idx));
2256 }
2257
2258 for fut in futs {
2259 fut.await.unwrap();
2260 }
2261 }
2262
2263 #[tokio::test(flavor = "multi_thread")]
2264 async fn test_zero_buffer_size_no_backpressure() {
2265 let obj_store = Arc::new(ObjectStore::memory());
2268 let config = SchedulerConfig {
2269 io_buffer_size_bytes: 0,
2270 use_lite_scheduler: Some(false),
2271 };
2272 let scheduler = ScanScheduler::new(obj_store, config);
2273
2274 let get_range_count = Arc::new(AtomicU64::new(0));
2275 let reader: Arc<dyn Reader> = Arc::new(TrackingReader {
2276 get_range_count: get_range_count.clone(),
2277 path: Path::parse("test").unwrap(),
2278 });
2279
2280 let fut1 = scheduler.submit_request(reader.clone(), vec![0..1000], 0, false);
2283 let fut2 = scheduler.submit_request(reader.clone(), vec![1000..2000], 1, false);
2284 let fut3 = scheduler.submit_request(reader.clone(), vec![2000..3000], 2, false);
2285
2286 let bytes1 = timeout(Duration::from_secs(5), fut1)
2287 .await
2288 .unwrap()
2289 .unwrap();
2290 let bytes2 = timeout(Duration::from_secs(5), fut2)
2291 .await
2292 .unwrap()
2293 .unwrap();
2294 let bytes3 = timeout(Duration::from_secs(5), fut3)
2295 .await
2296 .unwrap()
2297 .unwrap();
2298 assert_eq!(bytes1[0].len(), 1000);
2299 assert_eq!(bytes2[0].len(), 1000);
2300 assert_eq!(bytes3[0].len(), 1000);
2301 assert_eq!(get_range_count.load(Ordering::Acquire), 3);
2302 }
2303
2304 #[tokio::test(flavor = "multi_thread")]
2305 async fn test_file_scheduler_bypass_backpressure() {
2306 let some_path = Path::parse("foo").unwrap();
2309 let base_store = Arc::new(InMemory::new());
2310 base_store
2311 .put(&some_path, vec![0u8; 1000].into())
2312 .await
2313 .unwrap();
2314
2315 let bytes_dispatched = Arc::new(AtomicU64::from(0));
2316 let mut obj_store = MockObjectStore::default();
2317 let bytes_dispatched_copy = bytes_dispatched.clone();
2318 obj_store
2319 .expect_get_opts()
2320 .returning(move |location, options| {
2321 let range = options.range.as_ref().unwrap();
2322 let num_bytes = match range {
2323 GetRange::Bounded(bounded) => bounded.end - bounded.start,
2324 _ => panic!(),
2325 };
2326 bytes_dispatched_copy.fetch_add(num_bytes, Ordering::Release);
2327 let location = location.clone();
2328 let base_store = base_store.clone();
2329 async move { base_store.get_opts(&location, options).await }.boxed()
2330 });
2331 let obj_store = Arc::new(ObjectStore::new(
2332 Arc::new(obj_store),
2333 Url::parse("mem://").unwrap(),
2334 Some(500),
2335 None,
2336 false,
2337 false,
2338 1,
2339 DEFAULT_DOWNLOAD_RETRY_COUNT,
2340 None,
2341 ));
2342
2343 let config = SchedulerConfig {
2345 io_buffer_size_bytes: 10,
2346 use_lite_scheduler: Some(false),
2347 };
2348 let scan_scheduler = ScanScheduler::new(obj_store, config);
2349 let file_scheduler = scan_scheduler
2350 .open_file(&Path::parse("foo").unwrap(), &CachedFileSize::new(1000))
2351 .await
2352 .unwrap();
2353 let bypass_scheduler = file_scheduler.with_bypass_backpressure();
2354
2355 let blocker_fut = file_scheduler.submit_single(0..10, 0);
2357 while bytes_dispatched.load(Ordering::Acquire) < 10 {
2358 tokio::time::sleep(Duration::from_millis(1)).await;
2359 }
2360
2361 let normal_fut = file_scheduler.submit_single(0..10, 2);
2364 let bypass_fut = bypass_scheduler.submit_single(0..10, 1);
2365
2366 while bytes_dispatched.load(Ordering::Acquire) < 20 {
2368 tokio::time::sleep(Duration::from_millis(1)).await;
2369 }
2370 tokio::time::sleep(Duration::from_millis(20)).await;
2371 assert_eq!(
2372 bytes_dispatched.load(Ordering::Acquire),
2373 20,
2374 "normal read should still be blocked while budget is exhausted"
2375 );
2376
2377 timeout(Duration::from_secs(5), blocker_fut)
2379 .await
2380 .unwrap()
2381 .unwrap();
2382 timeout(Duration::from_secs(5), bypass_fut)
2383 .await
2384 .unwrap()
2385 .unwrap();
2386 timeout(Duration::from_secs(5), normal_fut)
2387 .await
2388 .unwrap()
2389 .unwrap();
2390 assert_eq!(bytes_dispatched.load(Ordering::Acquire), 30);
2391 }
2392
2393 async fn run_caller_drop_scenario(use_lite_scheduler: bool) -> (bool, Duration) {
2399 let obj_store = Arc::new(ObjectStore::new(
2400 Arc::new(InMemory::new()),
2401 Url::parse("mem://").unwrap(),
2402 Some(4096),
2403 None,
2404 false,
2405 false,
2406 1,
2407 DEFAULT_DOWNLOAD_RETRY_COUNT,
2408 None,
2409 ));
2410 let scheduler = ScanScheduler::new(
2411 obj_store,
2412 SchedulerConfig {
2413 io_buffer_size_bytes: 100,
2414 use_lite_scheduler: Some(use_lite_scheduler),
2415 },
2416 );
2417
2418 let semaphore = Arc::new(tokio::sync::Semaphore::new(0));
2419 let get_range_count = Arc::new(AtomicU64::new(0));
2420 let reader: Arc<dyn Reader> = Arc::new(BlockingReader {
2421 semaphore: semaphore.clone(),
2422 get_range_count: get_range_count.clone(),
2423 path: Path::parse("test").unwrap(),
2424 });
2425
2426 let fut1 = scheduler.submit_request(reader.clone(), vec![0..50], 0, false);
2430 let handle = tokio::spawn(async move {
2431 let _ = fut1.await;
2432 });
2433
2434 while get_range_count.load(Ordering::Acquire) == 0 {
2438 tokio::time::sleep(Duration::from_millis(1)).await;
2439 }
2440
2441 handle.abort();
2443 let _ = handle.await;
2444
2445 semaphore.add_permits(1);
2448 tokio::time::sleep(Duration::from_millis(50)).await;
2451
2452 semaphore.add_permits(1);
2455 let fut2 = scheduler.submit_request(reader, vec![100..160], 1, false);
2456
2457 let start = std::time::Instant::now();
2458 let outcome = timeout(Duration::from_secs(2), fut2).await;
2459 let elapsed = start.elapsed();
2460 match outcome {
2461 Ok(res) => {
2462 assert_eq!(res.unwrap().iter().map(|b| b.len()).sum::<usize>(), 60);
2463 (true, elapsed)
2464 }
2465 Err(_) => (false, elapsed),
2466 }
2467 }
2468
2469 #[tokio::test(flavor = "multi_thread")]
2473 async fn standard_scheduler_refunds_reservation_on_caller_drop() {
2474 let (completed, elapsed) = run_caller_drop_scenario(false).await;
2475 assert!(
2476 completed,
2477 "standard scheduler deadlocked the follow-up request (elapsed {elapsed:?}); \
2478 the dropped request's reservation was not refunded"
2479 );
2480 }
2481
2482 #[tokio::test(flavor = "multi_thread")]
2485 async fn lite_scheduler_refunds_reservation_on_caller_drop() {
2486 let (completed, elapsed) = run_caller_drop_scenario(true).await;
2487 assert!(
2488 completed,
2489 "lite scheduler deadlocked the follow-up request (elapsed {elapsed:?}); \
2490 the dropped request's reservation was not refunded"
2491 );
2492 }
2493}