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 err: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
381 bypass_backpressure: bool,
383 io_queue: Arc<IoQueue>,
386}
387
388impl<F: FnOnce(Response) + Send> MutableBatch<F> {
389 fn new(
390 when_done: F,
391 num_data_buffers: u32,
392 priority: u128,
393 num_reqs: usize,
394 bypass_backpressure: bool,
395 io_queue: Arc<IoQueue>,
396 ) -> Self {
397 Self {
398 when_done: Some(when_done),
399 data_buffers: vec![Bytes::default(); num_data_buffers as usize],
400 num_bytes: 0,
401 priority,
402 num_reqs,
403 err: None,
404 bypass_backpressure,
405 io_queue,
406 }
407 }
408}
409
410impl<F: FnOnce(Response) + Send> Drop for MutableBatch<F> {
415 fn drop(&mut self) {
416 let result = if self.err.is_some() {
418 Err(Error::wrapped(self.err.take().unwrap()))
419 } else {
420 let mut data = Vec::new();
421 std::mem::swap(&mut data, &mut self.data_buffers);
422 Ok(data)
423 };
424 let response = Response {
427 data: Some(result),
428 io_queue: self.io_queue.clone(),
429 num_bytes: if self.bypass_backpressure {
431 0
432 } else {
433 self.num_bytes
434 },
435 priority: self.priority,
436 num_reqs: self.num_reqs,
437 };
438 (self.when_done.take().unwrap())(response);
439 }
440}
441
442struct DataChunk {
443 task_idx: usize,
444 num_bytes: u64,
445 data: Result<Bytes>,
446}
447
448trait DataSink: Send {
449 fn deliver_data(&mut self, data: DataChunk);
450}
451
452impl<F: FnOnce(Response) + Send> DataSink for MutableBatch<F> {
453 fn deliver_data(&mut self, data: DataChunk) {
455 self.num_bytes += data.num_bytes;
456 match data.data {
457 Ok(data_bytes) => {
458 self.data_buffers[data.task_idx] = data_bytes;
459 }
460 Err(err) => {
461 self.err.get_or_insert(Box::new(err));
463 }
464 }
465 }
466}
467
468struct IoTask {
469 reader: Arc<dyn Reader>,
470 to_read: Range<u64>,
471 when_done: Box<dyn FnOnce(Result<Bytes>) + Send>,
472 priority: u128,
473 bypass_backpressure: bool,
474}
475
476impl Eq for IoTask {}
477
478impl PartialEq for IoTask {
479 fn eq(&self, other: &Self) -> bool {
480 self.bypass_backpressure == other.bypass_backpressure && self.priority == other.priority
481 }
482}
483
484impl PartialOrd for IoTask {
485 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
486 Some(self.cmp(other))
487 }
488}
489
490impl Ord for IoTask {
491 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
492 self.bypass_backpressure
495 .cmp(&other.bypass_backpressure)
496 .then(other.priority.cmp(&self.priority))
497 }
498}
499
500impl IoTask {
501 fn num_bytes(&self) -> u64 {
502 self.to_read.end - self.to_read.start
503 }
504 fn cancel(self) {
505 (self.when_done)(Err(Error::internal(
506 "Scheduler closed before I/O was completed".to_string(),
507 )));
508 }
509
510 async fn run(self) {
511 let file_path = self.reader.path().as_ref();
512 let num_bytes = self.num_bytes();
513 let bytes = if self.to_read.start == self.to_read.end {
514 Ok(Bytes::new())
515 } else {
516 let bytes_fut = self
517 .reader
518 .get_range(self.to_read.start as usize..self.to_read.end as usize);
519 IOPS_COUNTER.fetch_add(1, Ordering::Release);
520 let num_bytes = self.num_bytes();
521 bytes_fut
522 .inspect(move |_| {
523 BYTES_READ_COUNTER.fetch_add(num_bytes, Ordering::Release);
524 })
525 .await
526 .map_err(Error::from)
527 };
528 tracing::trace!(
530 file = file_path,
531 bytes_read = num_bytes,
532 requests = 1,
533 range_start = self.to_read.start,
534 range_end = self.to_read.end,
535 "File I/O completed"
536 );
537 (self.when_done)(bytes);
538 }
539}
540
541async fn run_io_loop(tasks: Arc<IoQueue>) {
544 loop {
547 let next_task = tasks.pop().await;
548 match next_task {
549 Some(task) => {
550 tokio::spawn(task.run());
551 }
552 None => {
553 return;
555 }
556 }
557 }
558}
559
560#[derive(Debug)]
561struct StatsCollector {
562 iops: AtomicU64,
563 requests: AtomicU64,
564 bytes_read: AtomicU64,
565}
566
567impl StatsCollector {
568 fn new() -> Self {
569 Self {
570 iops: AtomicU64::new(0),
571 requests: AtomicU64::new(0),
572 bytes_read: AtomicU64::new(0),
573 }
574 }
575
576 fn iops(&self) -> u64 {
577 self.iops.load(Ordering::Relaxed)
578 }
579
580 fn bytes_read(&self) -> u64 {
581 self.bytes_read.load(Ordering::Relaxed)
582 }
583
584 fn requests(&self) -> u64 {
585 self.requests.load(Ordering::Relaxed)
586 }
587
588 fn record_request(&self, request: &[Range<u64>]) {
589 self.requests.fetch_add(1, Ordering::Relaxed);
590 self.iops.fetch_add(request.len() as u64, Ordering::Relaxed);
591 self.bytes_read.fetch_add(
592 request.iter().map(|r| r.end - r.start).sum::<u64>(),
593 Ordering::Relaxed,
594 );
595 }
596
597 fn add(&self, iops: u64, requests: u64, bytes_read: u64) {
600 self.iops.fetch_add(iops, Ordering::Relaxed);
601 self.requests.fetch_add(requests, Ordering::Relaxed);
602 self.bytes_read.fetch_add(bytes_read, Ordering::Relaxed);
603 }
604}
605
606impl IoStatsRecorder for StatsCollector {
607 fn record_request(&self, request: &[Range<u64>]) {
608 Self::record_request(self, request)
611 }
612}
613
614#[derive(Debug, Clone, Copy, Default)]
615pub struct ScanStats {
616 pub iops: u64,
617 pub requests: u64,
618 pub bytes_read: u64,
619}
620
621impl ScanStats {
622 fn new(stats: &StatsCollector) -> Self {
623 Self {
624 iops: stats.iops(),
625 requests: stats.requests(),
626 bytes_read: stats.bytes_read(),
627 }
628 }
629}
630
631fn split_priority(priority: Option<u128>) -> (Option<u64>, Option<u64>) {
632 priority
633 .map(|priority| ((priority >> 64) as u64, priority as u64))
634 .unzip()
635}
636
637#[derive(Debug, Clone, Copy)]
638pub(super) struct SchedulerStateEvent {
639 pub(super) queue_kind: &'static str,
640 pub(super) io_capacity: u64,
641 pub(super) iops_available: u64,
642 pub(super) active_iops: u64,
643 pub(super) pending_iops: u64,
644 pub(super) pending_bytes: u64,
645 pub(super) bytes_available: i64,
646 pub(super) bytes_reserved: i64,
647 pub(super) io_buffer_size_bytes: u64,
648 pub(super) priorities_in_flight: u64,
649 pub(super) no_backpressure: bool,
650 pub(super) head_task_bytes: Option<u64>,
651 pub(super) head_task_priority_high: Option<u64>,
652 pub(super) head_task_priority_low: Option<u64>,
653 pub(super) min_in_flight_priority_high: Option<u64>,
654 pub(super) min_in_flight_priority_low: Option<u64>,
655 pub(super) head_task_can_deliver: Option<bool>,
656 pub(super) head_task_priority_bypass: Option<bool>,
657 pub(super) head_task_blocked_by_iops: Option<bool>,
658 pub(super) head_task_blocked_by_bytes: Option<bool>,
659}
660
661impl SchedulerStateEvent {
662 fn trace(self, stats: ScanStats) {
663 tracing::event!(
664 target: SCHEDULER_STATE_EVENT_TARGET,
665 tracing::Level::TRACE,
666 queue_kind = self.queue_kind,
667 scheduler_iops = stats.iops,
668 scheduler_requests = stats.requests,
669 scheduler_bytes_read = stats.bytes_read,
670 io_capacity = self.io_capacity,
671 iops_available = self.iops_available,
672 active_iops = self.active_iops,
673 pending_iops = self.pending_iops,
674 pending_bytes = self.pending_bytes,
675 bytes_available = self.bytes_available,
676 bytes_reserved = self.bytes_reserved,
677 io_buffer_size_bytes = self.io_buffer_size_bytes,
678 priorities_in_flight = self.priorities_in_flight,
679 no_backpressure = self.no_backpressure,
680 head_task_bytes_present = self.head_task_bytes.is_some(),
681 head_task_bytes = self.head_task_bytes.unwrap_or_default(),
682 head_task_priority_high_present = self.head_task_priority_high.is_some(),
683 head_task_priority_high = self.head_task_priority_high.unwrap_or_default(),
684 head_task_priority_low_present = self.head_task_priority_low.is_some(),
685 head_task_priority_low = self.head_task_priority_low.unwrap_or_default(),
686 min_in_flight_priority_high_present = self.min_in_flight_priority_high.is_some(),
687 min_in_flight_priority_high = self.min_in_flight_priority_high.unwrap_or_default(),
688 min_in_flight_priority_low_present = self.min_in_flight_priority_low.is_some(),
689 min_in_flight_priority_low = self.min_in_flight_priority_low.unwrap_or_default(),
690 head_task_can_deliver_present = self.head_task_can_deliver.is_some(),
691 head_task_can_deliver = self.head_task_can_deliver.unwrap_or(false),
692 head_task_priority_bypass_present = self.head_task_priority_bypass.is_some(),
693 head_task_priority_bypass = self.head_task_priority_bypass.unwrap_or(false),
694 head_task_blocked_by_iops_present = self.head_task_blocked_by_iops.is_some(),
695 head_task_blocked_by_iops = self.head_task_blocked_by_iops.unwrap_or(false),
696 head_task_blocked_by_bytes_present = self.head_task_blocked_by_bytes.is_some(),
697 head_task_blocked_by_bytes = self.head_task_blocked_by_bytes.unwrap_or(false),
698 "Scheduler state"
699 );
700 }
701}
702
703pub(super) fn emit_scheduler_state_event(event: Option<SchedulerStateEvent>, stats: &IoStats) {
704 if let Some(event) = event {
705 event.trace(stats.snapshot());
706 }
707}
708
709#[derive(Debug, Clone)]
720pub struct IoStats(Arc<StatsCollector>);
721
722impl IoStats {
723 pub fn new() -> Self {
724 Self(Arc::new(StatsCollector::new()))
725 }
726
727 pub fn record_request(&self, request: &[Range<u64>]) {
731 self.0.record_request(request);
732 }
733
734 pub fn snapshot(&self) -> ScanStats {
736 ScanStats::new(self.0.as_ref())
737 }
738
739 pub fn recorder(&self) -> Arc<dyn IoStatsRecorder> {
743 self.0.clone()
744 }
745
746 pub fn add_scan_stats(&self, stats: &ScanStats) {
750 self.0.add(stats.iops, stats.requests, stats.bytes_read);
751 }
752}
753
754impl Default for IoStats {
755 fn default() -> Self {
756 Self::new()
757 }
758}
759
760enum IoQueueType {
761 Standard(Arc<IoQueue>),
762 Lite(Arc<lite::IoQueue>),
763}
764
765pub struct ScanScheduler {
774 object_store: Arc<ObjectStore>,
775 io_queue: IoQueueType,
776 stats: IoStats,
777}
778
779impl Debug for ScanScheduler {
780 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
781 f.debug_struct("ScanScheduler")
782 .field("object_store", &self.object_store)
783 .finish()
784 }
785}
786
787struct Response {
788 data: Option<Result<Vec<Bytes>>>,
791 io_queue: Arc<IoQueue>,
792 priority: u128,
793 num_reqs: usize,
794 num_bytes: u64,
795}
796
797impl Drop for Response {
801 fn drop(&mut self) {
802 self.io_queue
803 .on_bytes_consumed(self.num_bytes, self.priority, self.num_reqs);
804 }
805}
806
807#[derive(Debug, Clone, Copy)]
808pub struct SchedulerConfig {
809 pub io_buffer_size_bytes: u64,
813 pub use_lite_scheduler: Option<bool>,
819}
820
821impl SchedulerConfig {
822 pub fn new(io_buffer_size_bytes: u64) -> Self {
823 Self {
824 io_buffer_size_bytes,
825 use_lite_scheduler: std::env::var("LANCE_USE_LITE_SCHEDULER")
826 .ok()
827 .map(|v| str_is_truthy(v.trim())),
828 }
829 }
830
831 pub fn default_for_testing() -> Self {
833 Self {
834 io_buffer_size_bytes: 256 * 1024 * 1024,
835 use_lite_scheduler: None,
836 }
837 }
838
839 pub fn max_bandwidth(store: &ObjectStore) -> Self {
842 Self::new(32 * 1024 * 1024 * store.io_parallelism() as u64)
843 }
844
845 pub fn with_lite_scheduler(self) -> Self {
846 Self {
847 use_lite_scheduler: Some(true),
848 ..self
849 }
850 }
851}
852
853impl ScanScheduler {
854 pub fn new(object_store: Arc<ObjectStore>, config: SchedulerConfig) -> Arc<Self> {
861 let io_capacity = object_store.io_parallelism();
862 let stats = IoStats::new();
863 let use_lite = config
864 .use_lite_scheduler
865 .unwrap_or_else(|| object_store.prefers_lite_scheduler());
866 let io_queue = if use_lite {
867 let io_queue = Arc::new(lite::IoQueue::new(
868 io_capacity as u64,
869 config.io_buffer_size_bytes,
870 stats.clone(),
871 ));
872 IoQueueType::Lite(io_queue)
873 } else {
874 let io_queue = Arc::new(IoQueue::new(
875 io_capacity as u32,
876 config.io_buffer_size_bytes,
877 stats.clone(),
878 ));
879 let io_queue_clone = io_queue.clone();
880 tokio::task::spawn(async move { run_io_loop(io_queue_clone).await });
884 IoQueueType::Standard(io_queue)
885 };
886 Arc::new(Self {
887 object_store,
888 io_queue,
889 stats,
890 })
891 }
892
893 pub async fn open_file_with_priority(
902 self: &Arc<Self>,
903 path: &Path,
904 base_priority: u64,
905 file_size_bytes: &CachedFileSize,
906 ) -> Result<FileScheduler> {
907 let file_size_bytes = if let Some(size) = file_size_bytes.get() {
908 u64::from(size)
909 } else {
910 let size = self.object_store.size(path).await?;
911 if let Some(size) = NonZero::new(size) {
912 file_size_bytes.set(size);
913 }
914 size
915 };
916 let reader = self
917 .object_store
918 .open_with_size(path, file_size_bytes as usize)
919 .await?;
920 let block_size = self.object_store.block_size() as u64;
921 let max_iop_size = self.object_store.max_iop_size();
922 Ok(FileScheduler {
923 reader: reader.into(),
924 block_size,
925 root: self.clone(),
926 base_priority,
927 max_iop_size,
928 bypass_backpressure: false,
929 extra_stats: None,
930 })
931 }
932
933 pub async fn open_file(
937 self: &Arc<Self>,
938 path: &Path,
939 file_size_bytes: &CachedFileSize,
940 ) -> Result<FileScheduler> {
941 self.open_file_with_priority(path, 0, file_size_bytes).await
942 }
943
944 pub fn open_reader(self: &Arc<Self>, reader: Arc<dyn Reader>) -> FileScheduler {
955 FileScheduler {
956 reader,
957 block_size: self.object_store.block_size() as u64,
958 root: self.clone(),
959 base_priority: 0,
960 max_iop_size: self.object_store.max_iop_size(),
961 bypass_backpressure: false,
962 extra_stats: None,
963 }
964 }
965
966 fn do_submit_request(
967 &self,
968 reader: Arc<dyn Reader>,
969 request: Vec<Range<u64>>,
970 tx: oneshot::Sender<Response>,
971 priority: u128,
972 io_queue: &Arc<IoQueue>,
973 bypass_backpressure: bool,
974 ) {
975 let num_iops = request.len() as u32;
976
977 let when_all_io_done = move |bytes_and_permits| {
978 let _ = tx.send(bytes_and_permits);
980 };
981
982 let dest = Arc::new(Mutex::new(Box::new(MutableBatch::new(
983 when_all_io_done,
984 num_iops,
985 priority,
986 request.len(),
987 bypass_backpressure,
988 io_queue.clone(),
989 ))));
990
991 for (task_idx, iop) in request.into_iter().enumerate() {
992 let dest = dest.clone();
993 let io_queue_clone = io_queue.clone();
994 let num_bytes = iop.end - iop.start;
995 let task = IoTask {
996 reader: reader.clone(),
997 to_read: iop,
998 priority,
999 bypass_backpressure,
1000 when_done: Box::new(move |data| {
1001 io_queue_clone.on_iop_complete();
1002 let mut dest = dest.lock().unwrap();
1003 let chunk = DataChunk {
1004 data,
1005 task_idx,
1006 num_bytes,
1007 };
1008 dest.deliver_data(chunk);
1009 }),
1010 };
1011 io_queue.push(task);
1012 }
1013 }
1014
1015 fn submit_request_standard(
1016 &self,
1017 reader: Arc<dyn Reader>,
1018 request: Vec<Range<u64>>,
1019 priority: u128,
1020 io_queue: &Arc<IoQueue>,
1021 bypass_backpressure: bool,
1022 ) -> impl Future<Output = Result<Vec<Bytes>>> + Send + use<> {
1023 let (tx, rx) = oneshot::channel::<Response>();
1024
1025 self.do_submit_request(reader, request, tx, priority, io_queue, bypass_backpressure);
1026
1027 rx.map(|wrapped_rsp| {
1028 let mut rsp = wrapped_rsp.unwrap();
1031 rsp.data.take().unwrap()
1032 })
1033 }
1034
1035 fn submit_request_lite(
1036 &self,
1037 reader: Arc<dyn Reader>,
1038 request: Vec<Range<u64>>,
1039 priority: u128,
1040 io_queue: &Arc<lite::IoQueue>,
1041 bypass_backpressure: bool,
1042 ) -> impl Future<Output = Result<Vec<Bytes>>> + Send + use<> {
1043 let maybe_tasks = request
1045 .into_iter()
1046 .map(|task| {
1047 let reader = reader.clone();
1048 let queue = io_queue.clone();
1049 let run_fn = Box::new(move || {
1050 reader
1051 .get_range(task.start as usize..task.end as usize)
1052 .map_err(Error::from)
1053 .boxed()
1054 });
1055 queue.submit(task, priority, run_fn, bypass_backpressure)
1056 })
1057 .collect::<Result<Vec<_>>>();
1058 match maybe_tasks {
1059 Ok(tasks) => async move {
1060 let mut results = Vec::with_capacity(tasks.len());
1061 for task in tasks {
1062 results.push(task.await?);
1063 }
1064 Ok(results)
1065 }
1066 .boxed(),
1067 Err(e) => async move { Err(e) }.boxed(),
1068 }
1069 }
1070
1071 pub fn submit_request(
1072 &self,
1073 reader: Arc<dyn Reader>,
1074 request: Vec<Range<u64>>,
1075 priority: u128,
1076 bypass_backpressure: bool,
1077 ) -> impl Future<Output = Result<Vec<Bytes>>> + Send + use<> {
1078 match &self.io_queue {
1079 IoQueueType::Standard(io_queue) => {
1080 futures::future::Either::Left(self.submit_request_standard(
1081 reader,
1082 request,
1083 priority,
1084 io_queue,
1085 bypass_backpressure,
1086 ))
1087 }
1088 IoQueueType::Lite(io_queue) => futures::future::Either::Right(
1089 self.submit_request_lite(reader, request, priority, io_queue, bypass_backpressure),
1090 ),
1091 }
1092 }
1093
1094 pub fn stats(&self) -> ScanStats {
1095 self.stats.snapshot()
1096 }
1097
1098 #[cfg(test)]
1099 fn uses_lite_scheduler(&self) -> bool {
1100 matches!(self.io_queue, IoQueueType::Lite(_))
1101 }
1102}
1103
1104impl Drop for ScanScheduler {
1105 fn drop(&mut self) {
1106 match &self.io_queue {
1118 IoQueueType::Standard(io_queue) => io_queue.close(),
1119 IoQueueType::Lite(io_queue) => io_queue.close(),
1120 }
1121 }
1122}
1123
1124#[derive(Clone, Debug)]
1126pub struct FileScheduler {
1127 reader: Arc<dyn Reader>,
1128 root: Arc<ScanScheduler>,
1129 block_size: u64,
1130 base_priority: u64,
1131 max_iop_size: u64,
1132 bypass_backpressure: bool,
1133 extra_stats: Option<Arc<dyn IoStatsRecorder>>,
1137}
1138
1139fn is_close_together(range1: &Range<u64>, range2: &Range<u64>, block_size: u64) -> bool {
1140 range2.start <= (range1.end + block_size)
1142}
1143
1144fn is_overlapping(range1: &Range<u64>, range2: &Range<u64>) -> bool {
1145 range1.start < range2.end && range2.start < range1.end
1146}
1147
1148impl FileScheduler {
1149 pub fn submit_request(
1162 &self,
1163 request: Vec<Range<u64>>,
1164 priority: u64,
1165 ) -> impl Future<Output = Result<Vec<Bytes>>> + Send + use<> {
1166 let priority = ((self.base_priority as u128) << 64) + priority as u128;
1168
1169 let mut merged_requests = Vec::with_capacity(request.len());
1170
1171 if !request.is_empty() {
1172 let mut curr_interval = request[0].clone();
1173
1174 for req in request.iter().skip(1) {
1175 if is_close_together(&curr_interval, req, self.block_size) {
1176 curr_interval.end = curr_interval.end.max(req.end);
1177 } else {
1178 merged_requests.push(curr_interval);
1179 curr_interval = req.clone();
1180 }
1181 }
1182
1183 merged_requests.push(curr_interval);
1184 }
1185
1186 let mut updated_requests = Vec::with_capacity(merged_requests.len());
1187 for req in merged_requests {
1188 if req.is_empty() {
1189 updated_requests.push(req);
1190 } else {
1191 let num_requests = (req.end - req.start).div_ceil(self.max_iop_size);
1192 let bytes_per_request = (req.end - req.start) / num_requests;
1193 for i in 0..num_requests {
1194 let start = req.start + i * bytes_per_request;
1195 let end = if i == num_requests - 1 {
1196 req.end
1198 } else {
1199 start + bytes_per_request
1200 };
1201 updated_requests.push(start..end);
1202 }
1203 }
1204 }
1205
1206 self.root.stats.record_request(&updated_requests);
1207 if let Some(extra_stats) = &self.extra_stats {
1208 extra_stats.record_request(&updated_requests);
1209 }
1210
1211 let bytes_vec_fut = self.root.submit_request(
1212 self.reader.clone(),
1213 updated_requests.clone(),
1214 priority,
1215 self.bypass_backpressure,
1216 );
1217
1218 let mut updated_index = 0;
1219 let mut final_bytes = Vec::with_capacity(request.len());
1220
1221 async move {
1222 let bytes_vec = bytes_vec_fut.await?;
1223
1224 let mut orig_index = 0;
1225 while (updated_index < updated_requests.len()) && (orig_index < request.len()) {
1226 let updated_range = &updated_requests[updated_index];
1227 let orig_range = &request[orig_index];
1228 let byte_offset = updated_range.start as usize;
1229
1230 if is_overlapping(updated_range, orig_range) {
1231 let start = orig_range.start as usize - byte_offset;
1233 if orig_range.end <= updated_range.end {
1234 let end = orig_range.end as usize - byte_offset;
1237 final_bytes.push(bytes_vec[updated_index].slice(start..end));
1238 } else {
1239 let orig_size = orig_range.end - orig_range.start;
1242 let mut merged_bytes = Vec::with_capacity(orig_size as usize);
1243 merged_bytes.extend_from_slice(&bytes_vec[updated_index].slice(start..));
1244 let mut copy_offset = merged_bytes.len() as u64;
1245 while copy_offset < orig_size {
1246 updated_index += 1;
1247 let next_range = &updated_requests[updated_index];
1248 let bytes_to_take =
1249 (orig_size - copy_offset).min(next_range.end - next_range.start);
1250 merged_bytes.extend_from_slice(
1251 &bytes_vec[updated_index].slice(0..bytes_to_take as usize),
1252 );
1253 copy_offset += bytes_to_take;
1254 }
1255 final_bytes.push(Bytes::from(merged_bytes));
1256 }
1257 orig_index += 1;
1258 } else {
1259 updated_index += 1;
1260 }
1261 }
1262
1263 Ok(final_bytes)
1264 }
1265 }
1266
1267 pub fn with_priority(&self, priority: u64) -> Self {
1268 Self {
1269 reader: self.reader.clone(),
1270 root: self.root.clone(),
1271 block_size: self.block_size,
1272 max_iop_size: self.max_iop_size,
1273 base_priority: priority,
1274 bypass_backpressure: self.bypass_backpressure,
1275 extra_stats: self.extra_stats.clone(),
1276 }
1277 }
1278
1279 pub fn with_io_stats(&self, stats: Arc<dyn IoStatsRecorder>) -> Self {
1289 Self {
1290 extra_stats: Some(stats),
1291 ..self.clone()
1292 }
1293 }
1294
1295 pub fn with_bypass_backpressure(&self) -> Self {
1300 Self {
1301 bypass_backpressure: true,
1302 ..self.clone()
1303 }
1304 }
1305
1306 pub fn submit_single(
1313 &self,
1314 range: Range<u64>,
1315 priority: u64,
1316 ) -> impl Future<Output = Result<Bytes>> + Send {
1317 self.submit_request(vec![range], priority)
1318 .map_ok(|vec_bytes| vec_bytes.into_iter().next().unwrap())
1319 }
1320
1321 pub fn reader(&self) -> &Arc<dyn Reader> {
1327 &self.reader
1328 }
1329}
1330
1331#[cfg(test)]
1332mod tests {
1333 use std::{collections::VecDeque, time::Duration};
1334
1335 use futures::poll;
1336 use lance_core::utils::tempfile::TempObjFile;
1337 use rand::RngCore;
1338
1339 use object_store::{GetRange, ObjectStore as OSObjectStore, ObjectStoreExt, memory::InMemory};
1340 use tokio::{runtime::Handle, time::timeout};
1341 use url::Url;
1342
1343 use crate::{
1344 object_store::{DEFAULT_DOWNLOAD_RETRY_COUNT, DEFAULT_MAX_IOP_SIZE},
1345 testing::MockObjectStore,
1346 };
1347
1348 use super::*;
1349
1350 fn make_task(priority: u128, bypass_backpressure: bool) -> IoTask {
1351 IoTask {
1352 reader: Arc::new(TrackingReader {
1353 get_range_count: Arc::new(AtomicU64::new(0)),
1354 path: Path::parse("test").unwrap(),
1355 }),
1356 to_read: 0..1,
1357 when_done: Box::new(|_| {}),
1358 priority,
1359 bypass_backpressure,
1360 }
1361 }
1362
1363 #[test]
1364 fn test_scheduler_state_event_fields() {
1365 use tracing_mock::{expect, subscriber};
1366
1367 let event = expect::event()
1368 .with_target(SCHEDULER_STATE_EVENT_TARGET)
1369 .at_level(tracing::Level::TRACE)
1370 .with_fields(
1371 expect::field("queue_kind")
1372 .with_value(&"standard")
1373 .and(expect::field("scheduler_iops").with_value(&7u64))
1374 .and(expect::field("scheduler_requests").with_value(&3u64))
1375 .and(expect::field("scheduler_bytes_read").with_value(&4096u64))
1376 .and(expect::field("io_capacity").with_value(&4u64))
1377 .and(expect::field("pending_iops").with_value(&1u64))
1378 .and(expect::field("bytes_available").with_value(&128i64))
1379 .and(expect::field("head_task_bytes_present").with_value(&true))
1380 .and(expect::field("head_task_bytes").with_value(&1u64))
1381 .and(expect::field("head_task_can_deliver_present").with_value(&true))
1382 .and(expect::field("head_task_can_deliver").with_value(&true)),
1383 );
1384 let (subscriber, handle) = subscriber::mock().event(event).run_with_handle();
1385
1386 let stats = IoStats::new();
1387 stats.add_scan_stats(&ScanStats {
1388 iops: 7,
1389 requests: 3,
1390 bytes_read: 4096,
1391 });
1392 let mut state = IoQueueState::new(4, 192);
1393 state.iops_avail = 2;
1394 state.bytes_avail = 128;
1395 state.pending_requests.push(make_task(1, false));
1396
1397 tracing::subscriber::with_default(subscriber, || {
1398 emit_scheduler_state_event(state.scheduler_state_event(), &stats);
1399 });
1400
1401 handle.assert_finished();
1402 }
1403
1404 #[test]
1405 fn test_iotask_ordering() {
1406 let mut heap = BinaryHeap::new();
1409 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())
1415 .map(|t| (t.priority, t.bypass_backpressure))
1416 .collect();
1417
1418 assert_eq!(order, vec![(5, true), (20, true), (1, false), (10, false)]);
1419 }
1420
1421 #[tokio::test]
1422 async fn test_full_seq_read() {
1423 let tmp_file = TempObjFile::default();
1424
1425 let obj_store = Arc::new(ObjectStore::local());
1426
1427 const DATA_SIZE: u64 = 1024 * 1024;
1429 let mut some_data = vec![0; DATA_SIZE as usize];
1430 rand::rng().fill_bytes(&mut some_data);
1431 obj_store.put(&tmp_file, &some_data).await.unwrap();
1432
1433 let config = SchedulerConfig::default_for_testing();
1434
1435 let scheduler = ScanScheduler::new(obj_store, config);
1436
1437 let file_scheduler = scheduler
1438 .open_file(&tmp_file, &CachedFileSize::unknown())
1439 .await
1440 .unwrap();
1441
1442 const READ_SIZE: u64 = 4 * 1024;
1444 let mut reqs = VecDeque::new();
1445 let mut offset = 0;
1446 while offset < DATA_SIZE {
1447 reqs.push_back(
1448 #[allow(clippy::single_range_in_vec_init)]
1449 file_scheduler
1450 .submit_request(vec![offset..offset + READ_SIZE], 0)
1451 .await
1452 .unwrap(),
1453 );
1454 offset += READ_SIZE;
1455 }
1456
1457 offset = 0;
1458 while offset < DATA_SIZE {
1460 let data = reqs.pop_front().unwrap();
1461 let actual = &data[0];
1462 let expected = &some_data[offset as usize..(offset + READ_SIZE) as usize];
1463 assert_eq!(expected, actual);
1464 offset += READ_SIZE;
1465 }
1466 }
1467
1468 #[tokio::test]
1469 async fn test_open_reader_bridge() {
1470 let tmp_file = TempObjFile::default();
1471
1472 let obj_store = Arc::new(ObjectStore::local());
1473
1474 const DATA_SIZE: u64 = 64 * 1024;
1475 let mut some_data = vec![0; DATA_SIZE as usize];
1476 rand::rng().fill_bytes(&mut some_data);
1477 obj_store.put(&tmp_file, &some_data).await.unwrap();
1478
1479 let config = SchedulerConfig::default_for_testing();
1480 let scheduler = ScanScheduler::new(obj_store.clone(), config);
1481
1482 let reader: Arc<dyn Reader> = obj_store.open(&tmp_file).await.unwrap().into();
1484 let file_scheduler = scheduler.open_reader(reader);
1485
1486 let bytes = file_scheduler
1487 .submit_request(vec![0..DATA_SIZE], 0)
1488 .await
1489 .unwrap();
1490 assert_eq!(bytes[0], some_data);
1491 }
1492
1493 #[tokio::test]
1494 async fn test_split_coalesce() {
1495 let tmp_file = TempObjFile::default();
1496
1497 let obj_store = Arc::new(ObjectStore::local());
1498
1499 const DATA_SIZE: u64 = 75 * 1024 * 1024;
1501 let mut some_data = vec![0; DATA_SIZE as usize];
1502 rand::rng().fill_bytes(&mut some_data);
1503 obj_store.put(&tmp_file, &some_data).await.unwrap();
1504
1505 let config = SchedulerConfig::default_for_testing();
1506
1507 let scheduler = ScanScheduler::new(obj_store, config);
1508
1509 let file_scheduler = scheduler
1510 .open_file(&tmp_file, &CachedFileSize::unknown())
1511 .await
1512 .unwrap();
1513
1514 let req =
1517 file_scheduler.submit_request(vec![50_000..51_000, 52_000..53_000, 54_000..55_000], 0);
1518
1519 let bytes = req.await.unwrap();
1520
1521 assert_eq!(bytes[0], &some_data[50_000..51_000]);
1522 assert_eq!(bytes[1], &some_data[52_000..53_000]);
1523 assert_eq!(bytes[2], &some_data[54_000..55_000]);
1524
1525 assert_eq!(1, scheduler.stats().iops);
1526
1527 let req = file_scheduler.submit_request(vec![0..DATA_SIZE], 0);
1529 let bytes = req.await.unwrap();
1530 assert!(bytes[0] == some_data, "data is not the same");
1531
1532 assert_eq!(6, scheduler.stats().iops);
1533
1534 let chunk_size = *DEFAULT_MAX_IOP_SIZE;
1538 let req = file_scheduler.submit_request(
1539 vec![
1540 10..chunk_size,
1541 chunk_size + 10..(chunk_size * 2) - 20,
1542 chunk_size * 2..(chunk_size * 2) + 10,
1543 ],
1544 0,
1545 );
1546
1547 let bytes = req.await.unwrap();
1548 let chunk_size = chunk_size as usize;
1549 assert!(
1550 bytes[0] == some_data[10..chunk_size],
1551 "data is not the same"
1552 );
1553 assert!(
1554 bytes[1] == some_data[chunk_size + 10..(chunk_size * 2) - 20],
1555 "data is not the same"
1556 );
1557 assert!(
1558 bytes[2] == some_data[chunk_size * 2..(chunk_size * 2) + 10],
1559 "data is not the same"
1560 );
1561 assert_eq!(8, scheduler.stats().iops);
1562
1563 let reads = (0..44)
1564 .map(|i| i * 1_000_000..(i + 1) * 1_000_000)
1565 .collect::<Vec<_>>();
1566 let req = file_scheduler.submit_request(reads, 0);
1567 let bytes = req.await.unwrap();
1568 for (i, bytes) in bytes.iter().enumerate() {
1569 assert!(
1570 bytes == &some_data[i * 1_000_000..(i + 1) * 1_000_000],
1571 "data is not the same"
1572 );
1573 }
1574 assert_eq!(11, scheduler.stats().iops);
1575 }
1576
1577 #[tokio::test]
1578 async fn test_io_stats_sink() {
1579 let tmp_file = TempObjFile::default();
1580 let obj_store = Arc::new(ObjectStore::local());
1581
1582 const DATA_SIZE: u64 = 1024 * 1024;
1583 let mut some_data = vec![0; DATA_SIZE as usize];
1584 rand::rng().fill_bytes(&mut some_data);
1585 obj_store.put(&tmp_file, &some_data).await.unwrap();
1586
1587 let scheduler = ScanScheduler::new(obj_store, SchedulerConfig::default_for_testing());
1588
1589 let sink = IoStats::new();
1591 let file_scheduler = scheduler
1592 .open_file(&tmp_file, &CachedFileSize::unknown())
1593 .await
1594 .unwrap()
1595 .with_io_stats(sink.recorder());
1596
1597 file_scheduler
1601 .submit_request(vec![50_000..51_000, 52_000..53_000, 54_000..55_000], 0)
1602 .await
1603 .unwrap();
1604
1605 let global = scheduler.stats();
1606 let scoped = sink.snapshot();
1607 assert_eq!(1, scoped.iops);
1608 assert_eq!(1, scoped.requests);
1609 assert_eq!(5000, scoped.bytes_read);
1611 assert_eq!(global.iops, scoped.iops);
1612 assert_eq!(global.requests, scoped.requests);
1613 assert_eq!(global.bytes_read, scoped.bytes_read);
1614
1615 let other = scheduler
1618 .open_file(&tmp_file, &CachedFileSize::unknown())
1619 .await
1620 .unwrap();
1621 other.submit_request(vec![0..1000], 0).await.unwrap();
1622
1623 let global_after = scheduler.stats();
1624 let scoped_after = sink.snapshot();
1625 assert_eq!(global.bytes_read + 1000, global_after.bytes_read);
1626 assert_eq!(scoped.bytes_read, scoped_after.bytes_read);
1627 assert_eq!(scoped.iops, scoped_after.iops);
1628 }
1629
1630 #[tokio::test]
1631 async fn test_priority() {
1632 let some_path = Path::parse("foo").unwrap();
1633 let base_store = Arc::new(InMemory::new());
1634 base_store
1635 .put(&some_path, vec![0; 1000].into())
1636 .await
1637 .unwrap();
1638
1639 let semaphore = Arc::new(tokio::sync::Semaphore::new(0));
1640 let mut obj_store = MockObjectStore::default();
1641 let semaphore_copy = semaphore.clone();
1642 obj_store
1643 .expect_get_opts()
1644 .returning(move |location, options| {
1645 let semaphore = semaphore.clone();
1646 let base_store = base_store.clone();
1647 let location = location.clone();
1648 async move {
1649 semaphore.acquire().await.unwrap().forget();
1650 base_store.get_opts(&location, options).await
1651 }
1652 .boxed()
1653 });
1654 let obj_store = Arc::new(ObjectStore::new(
1655 Arc::new(obj_store),
1656 Url::parse("mem://").unwrap(),
1657 Some(500),
1658 None,
1659 false,
1660 false,
1661 1,
1662 DEFAULT_DOWNLOAD_RETRY_COUNT,
1663 None,
1664 ));
1665
1666 let config = SchedulerConfig {
1667 io_buffer_size_bytes: 1024 * 1024,
1668 use_lite_scheduler: None,
1669 };
1670
1671 let scan_scheduler = ScanScheduler::new(obj_store, config);
1672
1673 let file_scheduler = scan_scheduler
1674 .open_file(&Path::parse("foo").unwrap(), &CachedFileSize::new(1000))
1675 .await
1676 .unwrap();
1677
1678 let first_fut = timeout(
1682 Duration::from_secs(10),
1683 file_scheduler.submit_single(0..10, 0),
1684 )
1685 .boxed();
1686
1687 let mut second_fut = timeout(
1689 Duration::from_secs(10),
1690 file_scheduler.submit_single(0..20, 100),
1691 )
1692 .boxed();
1693
1694 let mut third_fut = timeout(
1697 Duration::from_secs(10),
1698 file_scheduler.submit_single(0..30, 0),
1699 )
1700 .boxed();
1701
1702 semaphore_copy.add_permits(1);
1704 assert!(first_fut.await.unwrap().unwrap().len() == 10);
1705 assert!(poll!(&mut second_fut).is_pending());
1707 assert!(poll!(&mut third_fut).is_pending());
1708
1709 semaphore_copy.add_permits(1);
1711 assert!(third_fut.await.unwrap().unwrap().len() == 30);
1712 assert!(poll!(&mut second_fut).is_pending());
1713
1714 semaphore_copy.add_permits(1);
1716 assert!(second_fut.await.unwrap().unwrap().len() == 20);
1717 }
1718
1719 #[tokio::test]
1720 async fn test_standard_scheduler_state_tracks_queue_state() {
1721 let some_path = Path::parse("foo").unwrap();
1722 let base_store = Arc::new(InMemory::new());
1723 base_store
1724 .put(&some_path, vec![0; 1000].into())
1725 .await
1726 .unwrap();
1727
1728 let semaphore = Arc::new(tokio::sync::Semaphore::new(0));
1729 let mut obj_store = MockObjectStore::default();
1730 let semaphore_copy = semaphore.clone();
1731 obj_store
1732 .expect_get_opts()
1733 .returning(move |location, options| {
1734 let semaphore = semaphore.clone();
1735 let base_store = base_store.clone();
1736 let location = location.clone();
1737 async move {
1738 semaphore.acquire().await.unwrap().forget();
1739 base_store.get_opts(&location, options).await
1740 }
1741 .boxed()
1742 });
1743 let obj_store = Arc::new(ObjectStore::new(
1744 Arc::new(obj_store),
1745 Url::parse("mem://").unwrap(),
1746 Some(500),
1747 None,
1748 false,
1749 false,
1750 1,
1751 DEFAULT_DOWNLOAD_RETRY_COUNT,
1752 None,
1753 ));
1754
1755 let scheduler = ScanScheduler::new(
1756 obj_store,
1757 SchedulerConfig {
1758 io_buffer_size_bytes: 1024 * 1024,
1759 use_lite_scheduler: Some(false),
1760 },
1761 );
1762 let file_scheduler = scheduler
1763 .open_file(&Path::parse("foo").unwrap(), &CachedFileSize::new(1000))
1764 .await
1765 .unwrap();
1766
1767 let first_fut = timeout(
1768 Duration::from_secs(10),
1769 file_scheduler.submit_single(0..10, 0),
1770 )
1771 .boxed();
1772 let second_fut = timeout(
1773 Duration::from_secs(10),
1774 file_scheduler.submit_single(0..20, 100),
1775 )
1776 .boxed();
1777 let third_fut = timeout(
1778 Duration::from_secs(10),
1779 file_scheduler.submit_single(0..30, 0),
1780 )
1781 .boxed();
1782
1783 let io_queue = match &scheduler.io_queue {
1784 IoQueueType::Standard(io_queue) => io_queue.clone(),
1785 IoQueueType::Lite(_) => unreachable!("test forces the standard scheduler"),
1786 };
1787 let (
1788 io_capacity,
1789 iops_available,
1790 pending_bytes,
1791 bytes_reserved,
1792 priorities_in_flight,
1793 head_task_bytes,
1794 head_task_blocked_by_iops,
1795 head_task_blocked_by_bytes,
1796 ) = timeout(Duration::from_secs(5), async {
1797 loop {
1798 let observed = {
1799 let state = io_queue.state.lock().unwrap();
1800 let active_iops = state.io_capacity.saturating_sub(state.iops_avail);
1801 if active_iops == 1 && state.pending_requests.len() == 2 {
1802 let pending_bytes = state
1803 .pending_requests
1804 .iter()
1805 .map(IoTask::num_bytes)
1806 .sum::<u64>();
1807 let head_task = state.pending_requests.peek().unwrap();
1808 let bypasses_bytes = state.no_backpressure
1809 || head_task.bypass_backpressure
1810 || head_task.priority <= state.priorities_in_flight.min_in_flight();
1811 Some((
1812 state.io_capacity,
1813 state.iops_avail,
1814 pending_bytes,
1815 state.io_buffer_size as i64 - state.bytes_avail,
1816 state.priorities_in_flight.len(),
1817 head_task.num_bytes(),
1818 state.iops_avail == 0,
1819 !bypasses_bytes && head_task.num_bytes() as i64 > state.bytes_avail,
1820 ))
1821 } else {
1822 None
1823 }
1824 };
1825 if let Some(observed) = observed {
1826 break observed;
1827 }
1828 tokio::task::yield_now().await;
1829 }
1830 })
1831 .await
1832 .unwrap();
1833
1834 assert_eq!(io_capacity, 1);
1835 assert_eq!(iops_available, 0);
1836 assert_eq!(pending_bytes, 50);
1837 assert_eq!(bytes_reserved, 10);
1838 assert_eq!(priorities_in_flight, 1);
1839 assert_eq!(head_task_bytes, 30);
1840 assert!(head_task_blocked_by_iops);
1841 assert!(!head_task_blocked_by_bytes);
1842
1843 semaphore_copy.add_permits(3);
1844 assert_eq!(first_fut.await.unwrap().unwrap().len(), 10);
1845 assert_eq!(third_fut.await.unwrap().unwrap().len(), 30);
1846 assert_eq!(second_fut.await.unwrap().unwrap().len(), 20);
1847 }
1848
1849 #[tokio::test(flavor = "multi_thread")]
1850 async fn test_backpressure() {
1851 let some_path = Path::parse("foo").unwrap();
1852 let base_store = Arc::new(InMemory::new());
1853 base_store
1854 .put(&some_path, vec![0; 100000].into())
1855 .await
1856 .unwrap();
1857
1858 let bytes_read = Arc::new(AtomicU64::from(0));
1859 let mut obj_store = MockObjectStore::default();
1860 let bytes_read_copy = bytes_read.clone();
1861 obj_store
1863 .expect_get_opts()
1864 .returning(move |location, options| {
1865 let range = options.range.as_ref().unwrap();
1866 let num_bytes = match range {
1867 GetRange::Bounded(bounded) => bounded.end - bounded.start,
1868 _ => panic!(),
1869 };
1870 bytes_read_copy.fetch_add(num_bytes, Ordering::Release);
1871 let location = location.clone();
1872 let base_store = base_store.clone();
1873 async move { base_store.get_opts(&location, options).await }.boxed()
1874 });
1875 let obj_store = Arc::new(ObjectStore::new(
1876 Arc::new(obj_store),
1877 Url::parse("mem://").unwrap(),
1878 Some(500),
1879 None,
1880 false,
1881 false,
1882 1,
1883 DEFAULT_DOWNLOAD_RETRY_COUNT,
1884 None,
1885 ));
1886
1887 let config = SchedulerConfig {
1888 io_buffer_size_bytes: 10,
1889 use_lite_scheduler: None,
1890 };
1891
1892 let scan_scheduler = ScanScheduler::new(obj_store.clone(), config);
1893
1894 let file_scheduler = scan_scheduler
1895 .open_file(&Path::parse("foo").unwrap(), &CachedFileSize::new(100000))
1896 .await
1897 .unwrap();
1898
1899 let wait_for_idle = || async move {
1900 let handle = Handle::current();
1901 while handle.metrics().num_alive_tasks() != 1 {
1902 tokio::time::sleep(Duration::from_millis(10)).await;
1903 }
1904 };
1905 let wait_for_bytes_read_and_idle = |target_bytes: u64| {
1906 let bytes_read = &bytes_read;
1908 async move {
1909 let bytes_read_copy = bytes_read.clone();
1910 while bytes_read_copy.load(Ordering::Acquire) < target_bytes {
1911 tokio::time::sleep(Duration::from_millis(10)).await;
1912 }
1913 wait_for_idle().await;
1914 }
1915 };
1916
1917 let first_fut = file_scheduler.submit_single(0..5, 0);
1919 let second_fut = file_scheduler.submit_single(0..5, 0);
1921 let third_fut = file_scheduler.submit_single(0..3, 0);
1923 wait_for_bytes_read_and_idle(10).await;
1925
1926 assert_eq!(first_fut.await.unwrap().len(), 5);
1927 wait_for_bytes_read_and_idle(13).await;
1929
1930 let fourth_fut = file_scheduler.submit_single(0..5, 0);
1932 wait_for_bytes_read_and_idle(13).await;
1933
1934 assert_eq!(third_fut.await.unwrap().len(), 3);
1936 wait_for_bytes_read_and_idle(18).await;
1937
1938 assert_eq!(second_fut.await.unwrap().len(), 5);
1939 let fifth_fut = file_scheduler.submit_request(vec![0..3, 90000..90007], 0);
1946 wait_for_bytes_read_and_idle(21).await;
1947
1948 let fifth_bytes = tokio::time::timeout(Duration::from_secs(10), fifth_fut)
1950 .await
1951 .unwrap();
1952 assert_eq!(
1953 fifth_bytes.unwrap().iter().map(|b| b.len()).sum::<usize>(),
1954 10
1955 );
1956
1957 assert_eq!(fourth_fut.await.unwrap().len(), 5);
1959 wait_for_bytes_read_and_idle(28).await;
1960
1961 let config = SchedulerConfig {
1963 io_buffer_size_bytes: 10,
1964 use_lite_scheduler: None,
1965 };
1966
1967 let scan_scheduler = ScanScheduler::new(obj_store, config);
1968 let file_scheduler = scan_scheduler
1969 .open_file(&Path::parse("foo").unwrap(), &CachedFileSize::new(100000))
1970 .await
1971 .unwrap();
1972
1973 let first_fut = file_scheduler.submit_single(0..10, 0);
1974 let second_fut = file_scheduler.submit_single(0..10, 0);
1975
1976 std::thread::sleep(Duration::from_millis(100));
1977 assert_eq!(first_fut.await.unwrap().len(), 10);
1978 assert_eq!(second_fut.await.unwrap().len(), 10);
1979 }
1980
1981 #[derive(Debug)]
1982 struct BlockingReader {
1983 semaphore: Arc<tokio::sync::Semaphore>,
1984 get_range_count: Arc<AtomicU64>,
1985 path: Path,
1986 }
1987
1988 impl lance_core::deepsize::DeepSizeOf for BlockingReader {
1989 fn deep_size_of_children(&self, _context: &mut lance_core::deepsize::Context) -> usize {
1990 0
1991 }
1992 }
1993
1994 impl Reader for BlockingReader {
1995 fn path(&self) -> &Path {
1996 &self.path
1997 }
1998
1999 fn block_size(&self) -> usize {
2000 4096
2001 }
2002
2003 fn io_parallelism(&self) -> usize {
2004 1
2005 }
2006
2007 fn size(&self) -> futures::future::BoxFuture<'_, object_store::Result<usize>> {
2008 Box::pin(async { Ok(1_000_000) })
2009 }
2010
2011 fn get_range(
2012 &self,
2013 range: Range<usize>,
2014 ) -> futures::future::BoxFuture<'static, object_store::Result<Bytes>> {
2015 self.get_range_count.fetch_add(1, Ordering::Release);
2016 let semaphore = self.semaphore.clone();
2017 let num_bytes = range.end - range.start;
2018 Box::pin(async move {
2019 semaphore.acquire().await.unwrap().forget();
2020 Ok(Bytes::from(vec![0u8; num_bytes]))
2021 })
2022 }
2023
2024 fn get_all(&self) -> futures::future::BoxFuture<'_, object_store::Result<Bytes>> {
2025 Box::pin(async { Ok(Bytes::from(vec![0u8; 1_000_000])) })
2026 }
2027 }
2028
2029 #[tokio::test(flavor = "multi_thread")]
2030 async fn test_same_priority_chunks_continue_after_higher_priority_request() {
2031 let obj_store = Arc::new(ObjectStore::new(
2032 Arc::new(InMemory::new()),
2033 Url::parse("mem://").unwrap(),
2034 Some(4096),
2035 None,
2036 false,
2037 false,
2038 1,
2039 DEFAULT_DOWNLOAD_RETRY_COUNT,
2040 None,
2041 ));
2042 let scheduler = ScanScheduler::new(
2043 obj_store,
2044 SchedulerConfig {
2045 io_buffer_size_bytes: 10,
2046 use_lite_scheduler: Some(false),
2047 },
2048 );
2049 let semaphore = Arc::new(tokio::sync::Semaphore::new(0));
2050 let reader: Arc<dyn Reader> = Arc::new(BlockingReader {
2051 semaphore: semaphore.clone(),
2052 get_range_count: Arc::new(AtomicU64::new(0)),
2053 path: Path::parse("test").unwrap(),
2054 });
2055
2056 let low_priority =
2057 scheduler.submit_request(reader.clone(), vec![0..6, 100..106], 10, false);
2058 let high_priority = scheduler.submit_request(reader, vec![200..204], 0, false);
2059
2060 semaphore.add_permits(3);
2061 let low_priority = timeout(Duration::from_secs(5), low_priority)
2062 .await
2063 .unwrap()
2064 .unwrap();
2065 assert_eq!(
2066 low_priority.iter().map(|bytes| bytes.len()).sum::<usize>(),
2067 12
2068 );
2069
2070 let high_priority = timeout(Duration::from_secs(5), high_priority)
2071 .await
2072 .unwrap()
2073 .unwrap();
2074 assert_eq!(high_priority[0].len(), 4);
2075 }
2076
2077 #[derive(Debug)]
2079 struct TrackingReader {
2080 get_range_count: Arc<AtomicU64>,
2081 path: Path,
2082 }
2083
2084 impl lance_core::deepsize::DeepSizeOf for TrackingReader {
2085 fn deep_size_of_children(&self, _context: &mut lance_core::deepsize::Context) -> usize {
2086 0
2087 }
2088 }
2089
2090 impl Reader for TrackingReader {
2091 fn path(&self) -> &Path {
2092 &self.path
2093 }
2094
2095 fn block_size(&self) -> usize {
2096 4096
2097 }
2098
2099 fn io_parallelism(&self) -> usize {
2100 1
2101 }
2102
2103 fn size(&self) -> futures::future::BoxFuture<'_, object_store::Result<usize>> {
2104 Box::pin(async { Ok(1_000_000) })
2105 }
2106
2107 fn get_range(
2108 &self,
2109 range: Range<usize>,
2110 ) -> futures::future::BoxFuture<'static, object_store::Result<Bytes>> {
2111 self.get_range_count.fetch_add(1, Ordering::Release);
2112 let num_bytes = range.end - range.start;
2113 Box::pin(async move { Ok(Bytes::from(vec![0u8; num_bytes])) })
2114 }
2115
2116 fn get_all(&self) -> futures::future::BoxFuture<'_, object_store::Result<Bytes>> {
2117 Box::pin(async { Ok(Bytes::from(vec![0u8; 1_000_000])) })
2118 }
2119 }
2120
2121 #[tokio::test]
2122 async fn test_lite_scheduler_submits_eagerly() {
2123 let obj_store = Arc::new(ObjectStore::memory());
2124 let config = SchedulerConfig::default_for_testing().with_lite_scheduler();
2125 let scheduler = ScanScheduler::new(obj_store, config);
2126
2127 let get_range_count = Arc::new(AtomicU64::new(0));
2128 let reader: Arc<dyn Reader> = Arc::new(TrackingReader {
2129 get_range_count: get_range_count.clone(),
2130 path: Path::parse("test").unwrap(),
2131 });
2132
2133 let fut1 = scheduler.submit_request(reader.clone(), vec![0..100], 0, false);
2136 let fut2 = scheduler.submit_request(reader.clone(), vec![100..200], 10, false);
2137 let fut3 = scheduler.submit_request(reader.clone(), vec![200..300], 20, false);
2138
2139 assert_eq!(get_range_count.load(Ordering::Acquire), 3);
2141
2142 assert_eq!(fut1.await.unwrap()[0].len(), 100);
2144 assert_eq!(fut2.await.unwrap()[0].len(), 100);
2145 assert_eq!(fut3.await.unwrap()[0].len(), 100);
2146 }
2147
2148 #[tokio::test]
2149 async fn test_object_store_selects_scheduler() {
2150 let memory_store = Arc::new(ObjectStore::memory());
2152 assert!(!memory_store.prefers_lite_scheduler());
2153 let config = SchedulerConfig {
2154 io_buffer_size_bytes: 256 * 1024 * 1024,
2155 use_lite_scheduler: None,
2156 };
2157 let scheduler = ScanScheduler::new(memory_store.clone(), config);
2158 assert!(!scheduler.uses_lite_scheduler());
2159
2160 let uring_store = Arc::new(ObjectStore::new(
2162 Arc::new(InMemory::new()),
2163 Url::parse("file+uring:///tmp").unwrap(),
2164 None,
2165 None,
2166 false,
2167 false,
2168 8,
2169 DEFAULT_DOWNLOAD_RETRY_COUNT,
2170 None,
2171 ));
2172 assert!(uring_store.prefers_lite_scheduler());
2173 let config = SchedulerConfig {
2174 io_buffer_size_bytes: 256 * 1024 * 1024,
2175 use_lite_scheduler: None,
2176 };
2177 let scheduler = ScanScheduler::new(uring_store.clone(), config);
2178 assert!(scheduler.uses_lite_scheduler());
2179
2180 let config = SchedulerConfig {
2182 io_buffer_size_bytes: 256 * 1024 * 1024,
2183 use_lite_scheduler: Some(false),
2184 };
2185 let scheduler = ScanScheduler::new(uring_store, config);
2186 assert!(!scheduler.uses_lite_scheduler());
2187
2188 let config = SchedulerConfig {
2190 io_buffer_size_bytes: 256 * 1024 * 1024,
2191 use_lite_scheduler: Some(true),
2192 };
2193 let scheduler = ScanScheduler::new(memory_store, config);
2194 assert!(scheduler.uses_lite_scheduler());
2195 }
2196
2197 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2198 async fn stress_backpressure() {
2199 let some_path = Path::parse("foo").unwrap();
2203 let obj_store = Arc::new(ObjectStore::memory());
2204 obj_store
2205 .put(&some_path, vec![0; 100000].as_slice())
2206 .await
2207 .unwrap();
2208
2209 let config = SchedulerConfig {
2211 io_buffer_size_bytes: 1,
2212 use_lite_scheduler: None,
2213 };
2214 let scan_scheduler = ScanScheduler::new(obj_store.clone(), config);
2215 let file_scheduler = scan_scheduler
2216 .open_file(&some_path, &CachedFileSize::unknown())
2217 .await
2218 .unwrap();
2219
2220 let mut futs = Vec::with_capacity(10000);
2221 for idx in 0..10000 {
2222 futs.push(file_scheduler.submit_single(idx..idx + 1, idx));
2223 }
2224
2225 for fut in futs {
2226 fut.await.unwrap();
2227 }
2228 }
2229
2230 #[tokio::test(flavor = "multi_thread")]
2231 async fn test_zero_buffer_size_no_backpressure() {
2232 let obj_store = Arc::new(ObjectStore::memory());
2235 let config = SchedulerConfig {
2236 io_buffer_size_bytes: 0,
2237 use_lite_scheduler: Some(false),
2238 };
2239 let scheduler = ScanScheduler::new(obj_store, config);
2240
2241 let get_range_count = Arc::new(AtomicU64::new(0));
2242 let reader: Arc<dyn Reader> = Arc::new(TrackingReader {
2243 get_range_count: get_range_count.clone(),
2244 path: Path::parse("test").unwrap(),
2245 });
2246
2247 let fut1 = scheduler.submit_request(reader.clone(), vec![0..1000], 0, false);
2250 let fut2 = scheduler.submit_request(reader.clone(), vec![1000..2000], 1, false);
2251 let fut3 = scheduler.submit_request(reader.clone(), vec![2000..3000], 2, false);
2252
2253 let bytes1 = timeout(Duration::from_secs(5), fut1)
2254 .await
2255 .unwrap()
2256 .unwrap();
2257 let bytes2 = timeout(Duration::from_secs(5), fut2)
2258 .await
2259 .unwrap()
2260 .unwrap();
2261 let bytes3 = timeout(Duration::from_secs(5), fut3)
2262 .await
2263 .unwrap()
2264 .unwrap();
2265 assert_eq!(bytes1[0].len(), 1000);
2266 assert_eq!(bytes2[0].len(), 1000);
2267 assert_eq!(bytes3[0].len(), 1000);
2268 assert_eq!(get_range_count.load(Ordering::Acquire), 3);
2269 }
2270
2271 #[tokio::test(flavor = "multi_thread")]
2272 async fn test_file_scheduler_bypass_backpressure() {
2273 let some_path = Path::parse("foo").unwrap();
2276 let base_store = Arc::new(InMemory::new());
2277 base_store
2278 .put(&some_path, vec![0u8; 1000].into())
2279 .await
2280 .unwrap();
2281
2282 let bytes_dispatched = Arc::new(AtomicU64::from(0));
2283 let mut obj_store = MockObjectStore::default();
2284 let bytes_dispatched_copy = bytes_dispatched.clone();
2285 obj_store
2286 .expect_get_opts()
2287 .returning(move |location, options| {
2288 let range = options.range.as_ref().unwrap();
2289 let num_bytes = match range {
2290 GetRange::Bounded(bounded) => bounded.end - bounded.start,
2291 _ => panic!(),
2292 };
2293 bytes_dispatched_copy.fetch_add(num_bytes, Ordering::Release);
2294 let location = location.clone();
2295 let base_store = base_store.clone();
2296 async move { base_store.get_opts(&location, options).await }.boxed()
2297 });
2298 let obj_store = Arc::new(ObjectStore::new(
2299 Arc::new(obj_store),
2300 Url::parse("mem://").unwrap(),
2301 Some(500),
2302 None,
2303 false,
2304 false,
2305 1,
2306 DEFAULT_DOWNLOAD_RETRY_COUNT,
2307 None,
2308 ));
2309
2310 let config = SchedulerConfig {
2312 io_buffer_size_bytes: 10,
2313 use_lite_scheduler: Some(false),
2314 };
2315 let scan_scheduler = ScanScheduler::new(obj_store, config);
2316 let file_scheduler = scan_scheduler
2317 .open_file(&Path::parse("foo").unwrap(), &CachedFileSize::new(1000))
2318 .await
2319 .unwrap();
2320 let bypass_scheduler = file_scheduler.with_bypass_backpressure();
2321
2322 let blocker_fut = file_scheduler.submit_single(0..10, 0);
2324 while bytes_dispatched.load(Ordering::Acquire) < 10 {
2325 tokio::time::sleep(Duration::from_millis(1)).await;
2326 }
2327
2328 let normal_fut = file_scheduler.submit_single(0..10, 2);
2331 let bypass_fut = bypass_scheduler.submit_single(0..10, 1);
2332
2333 while bytes_dispatched.load(Ordering::Acquire) < 20 {
2335 tokio::time::sleep(Duration::from_millis(1)).await;
2336 }
2337 tokio::time::sleep(Duration::from_millis(20)).await;
2338 assert_eq!(
2339 bytes_dispatched.load(Ordering::Acquire),
2340 20,
2341 "normal read should still be blocked while budget is exhausted"
2342 );
2343
2344 timeout(Duration::from_secs(5), blocker_fut)
2346 .await
2347 .unwrap()
2348 .unwrap();
2349 timeout(Duration::from_secs(5), bypass_fut)
2350 .await
2351 .unwrap()
2352 .unwrap();
2353 timeout(Duration::from_secs(5), normal_fut)
2354 .await
2355 .unwrap()
2356 .unwrap();
2357 assert_eq!(bytes_dispatched.load(Ordering::Acquire), 30);
2358 }
2359
2360 async fn run_caller_drop_scenario(use_lite_scheduler: bool) -> (bool, Duration) {
2366 let obj_store = Arc::new(ObjectStore::new(
2367 Arc::new(InMemory::new()),
2368 Url::parse("mem://").unwrap(),
2369 Some(4096),
2370 None,
2371 false,
2372 false,
2373 1,
2374 DEFAULT_DOWNLOAD_RETRY_COUNT,
2375 None,
2376 ));
2377 let scheduler = ScanScheduler::new(
2378 obj_store,
2379 SchedulerConfig {
2380 io_buffer_size_bytes: 100,
2381 use_lite_scheduler: Some(use_lite_scheduler),
2382 },
2383 );
2384
2385 let semaphore = Arc::new(tokio::sync::Semaphore::new(0));
2386 let get_range_count = Arc::new(AtomicU64::new(0));
2387 let reader: Arc<dyn Reader> = Arc::new(BlockingReader {
2388 semaphore: semaphore.clone(),
2389 get_range_count: get_range_count.clone(),
2390 path: Path::parse("test").unwrap(),
2391 });
2392
2393 let fut1 = scheduler.submit_request(reader.clone(), vec![0..50], 0, false);
2397 let handle = tokio::spawn(async move {
2398 let _ = fut1.await;
2399 });
2400
2401 while get_range_count.load(Ordering::Acquire) == 0 {
2405 tokio::time::sleep(Duration::from_millis(1)).await;
2406 }
2407
2408 handle.abort();
2410 let _ = handle.await;
2411
2412 semaphore.add_permits(1);
2415 tokio::time::sleep(Duration::from_millis(50)).await;
2418
2419 semaphore.add_permits(1);
2422 let fut2 = scheduler.submit_request(reader, vec![100..160], 1, false);
2423
2424 let start = std::time::Instant::now();
2425 let outcome = timeout(Duration::from_secs(2), fut2).await;
2426 let elapsed = start.elapsed();
2427 match outcome {
2428 Ok(res) => {
2429 assert_eq!(res.unwrap().iter().map(|b| b.len()).sum::<usize>(), 60);
2430 (true, elapsed)
2431 }
2432 Err(_) => (false, elapsed),
2433 }
2434 }
2435
2436 #[tokio::test(flavor = "multi_thread")]
2440 async fn standard_scheduler_refunds_reservation_on_caller_drop() {
2441 let (completed, elapsed) = run_caller_drop_scenario(false).await;
2442 assert!(
2443 completed,
2444 "standard scheduler deadlocked the follow-up request (elapsed {elapsed:?}); \
2445 the dropped request's reservation was not refunded"
2446 );
2447 }
2448
2449 #[tokio::test(flavor = "multi_thread")]
2452 async fn lite_scheduler_refunds_reservation_on_caller_drop() {
2453 let (completed, elapsed) = run_caller_drop_scenario(true).await;
2454 assert!(
2455 completed,
2456 "lite scheduler deadlocked the follow-up request (elapsed {elapsed:?}); \
2457 the dropped request's reservation was not refunded"
2458 );
2459 }
2460}