1use std::sync::{Arc, Condvar, Mutex, MutexGuard};
4
5use tokio::sync::{OwnedSemaphorePermit, Semaphore};
6
7use crate::DeltaFunnelError;
8
9pub const NATIVE_ASYNC_DEFAULT_PREFETCH_FILE_COUNT_PER_PARTITION: usize = 2;
11
12pub const NATIVE_ASYNC_DEFAULT_FILE_READS_PER_PARTITION: usize = 3;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum DeltaProviderReaderBackend {
18 OfficialKernel,
20 NativeAsync,
22}
23
24impl DeltaProviderReaderBackend {
25 pub(crate) fn supports_dv_row_index_predicate_reads(self) -> bool {
28 match self {
29 Self::OfficialKernel => false,
30 Self::NativeAsync => true,
31 }
32 }
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub struct DeltaProviderScanExecutionOptions {
44 pub reader_backend: DeltaProviderReaderBackend,
46
47 pub max_concurrent_file_reads_per_scan: Option<usize>,
52
53 pub max_concurrent_file_reads_per_partition: usize,
58
59 pub output_buffer_capacity_per_partition: usize,
64
65 pub native_async_prefetch_file_count_per_partition: usize,
73}
74
75pub(crate) struct DeltaProviderSyncReadLimiter {
81 options: DeltaProviderScanExecutionOptions,
82 state: Mutex<DeltaProviderSyncReadLimiterState>,
83 ready: Condvar,
84}
85
86#[derive(Debug)]
87struct DeltaProviderSyncReadLimiterState {
88 active_file_reads: usize,
89 partition_active_file_reads: Vec<usize>,
90}
91
92#[derive(Clone)]
94pub(crate) struct DeltaProviderSyncPartitionReadLimiter {
95 partition: usize,
96 limiter: Arc<DeltaProviderSyncReadLimiter>,
97}
98
99pub(crate) struct DeltaProviderSyncFileReadPermit {
105 partition: usize,
106 limiter: Arc<DeltaProviderSyncReadLimiter>,
107 released: bool,
108}
109
110#[allow(dead_code)]
117pub(crate) struct DeltaProviderAsyncReadLimiter {
118 options: DeltaProviderScanExecutionOptions,
119 scan_permits: Arc<Semaphore>,
120 partition_permits: Vec<Arc<Semaphore>>,
121}
122
123#[allow(dead_code)]
125#[derive(Clone)]
126pub(crate) struct DeltaProviderAsyncPartitionReadLimiter {
127 partition: usize,
128 limiter: Arc<DeltaProviderAsyncReadLimiter>,
129}
130
131#[allow(dead_code)]
133pub(crate) struct DeltaProviderAsyncFileReadPermit {
134 _scan_permit: OwnedSemaphorePermit,
135 _partition_permit: OwnedSemaphorePermit,
136}
137
138impl DeltaProviderSyncReadLimiter {
139 pub(crate) fn new(
140 options: DeltaProviderScanExecutionOptions,
141 partition_count: usize,
142 ) -> Arc<Self> {
143 let options = options.with_resolved_scan_wide_capacity(partition_count);
144 Arc::new(Self {
145 options,
146 state: Mutex::new(DeltaProviderSyncReadLimiterState {
147 active_file_reads: 0,
148 partition_active_file_reads: vec![0; partition_count],
149 }),
150 ready: Condvar::new(),
151 })
152 }
153
154 pub(crate) fn partition_limiter(
155 self: &Arc<Self>,
156 partition: usize,
157 ) -> Result<DeltaProviderSyncPartitionReadLimiter, DeltaFunnelError> {
158 let partition_count = self.lock_state().partition_active_file_reads.len();
159 if partition >= partition_count {
160 return Err(DeltaFunnelError::Config {
161 message: format!(
162 "sync read limiter partition {partition} is out of range for {partition_count} partitions"
163 ),
164 });
165 }
166
167 Ok(DeltaProviderSyncPartitionReadLimiter {
168 partition,
169 limiter: Arc::clone(self),
170 })
171 }
172
173 #[cfg(test)]
174 pub(crate) fn active_file_reads(&self) -> usize {
175 self.lock_state().active_file_reads
176 }
177
178 #[cfg(test)]
179 fn try_acquire_file_permit(
180 self: &Arc<Self>,
181 partition: usize,
182 ) -> Option<DeltaProviderSyncFileReadPermit> {
183 let mut state = self.lock_state();
184 if !self.can_acquire(&state, partition) {
185 return None;
186 }
187
188 state.active_file_reads += 1;
189 state.partition_active_file_reads[partition] += 1;
190
191 Some(DeltaProviderSyncFileReadPermit {
192 partition,
193 limiter: Arc::clone(self),
194 released: false,
195 })
196 }
197
198 fn acquire_file_permit(self: &Arc<Self>, partition: usize) -> DeltaProviderSyncFileReadPermit {
199 let mut state = self.lock_state();
200 while !self.can_acquire(&state, partition) {
201 state = self.wait_for_ready(state);
202 }
203
204 state.active_file_reads += 1;
205 state.partition_active_file_reads[partition] += 1;
206
207 DeltaProviderSyncFileReadPermit {
208 partition,
209 limiter: Arc::clone(self),
210 released: false,
211 }
212 }
213
214 fn release_file_permit(&self, partition: usize) {
215 let mut state = self.lock_state();
216 if state.active_file_reads > 0 {
217 state.active_file_reads -= 1;
218 }
219 if let Some(partition_active_file_reads) =
220 state.partition_active_file_reads.get_mut(partition)
221 && *partition_active_file_reads > 0
222 {
223 *partition_active_file_reads -= 1;
224 }
225 self.ready.notify_all();
226 }
227
228 fn can_acquire(&self, state: &DeltaProviderSyncReadLimiterState, partition: usize) -> bool {
229 state.active_file_reads < self.options.resolved_max_concurrent_file_reads_per_scan()
230 && state.partition_active_file_reads[partition]
231 < self.options.max_concurrent_file_reads_per_partition
232 }
233
234 fn lock_state(&self) -> MutexGuard<'_, DeltaProviderSyncReadLimiterState> {
235 match self.state.lock() {
236 Ok(state) => state,
237 Err(poisoned) => poisoned.into_inner(),
238 }
239 }
240
241 fn wait_for_ready<'a>(
242 &self,
243 state: MutexGuard<'a, DeltaProviderSyncReadLimiterState>,
244 ) -> MutexGuard<'a, DeltaProviderSyncReadLimiterState> {
245 match self.ready.wait(state) {
246 Ok(state) => state,
247 Err(poisoned) => poisoned.into_inner(),
248 }
249 }
250}
251
252#[allow(dead_code)]
253impl DeltaProviderAsyncReadLimiter {
254 pub(crate) fn new(
255 options: DeltaProviderScanExecutionOptions,
256 partition_count: usize,
257 ) -> Arc<Self> {
258 let options = options.with_resolved_scan_wide_capacity(partition_count);
259 Arc::new(Self {
260 options,
261 scan_permits: Arc::new(Semaphore::new(
262 options.resolved_max_concurrent_file_reads_per_scan(),
263 )),
264 partition_permits: (0..partition_count)
265 .map(|_| {
266 Arc::new(Semaphore::new(
267 options.max_concurrent_file_reads_per_partition,
268 ))
269 })
270 .collect(),
271 })
272 }
273
274 pub(crate) fn partition_limiter(
275 self: &Arc<Self>,
276 partition: usize,
277 ) -> Result<DeltaProviderAsyncPartitionReadLimiter, DeltaFunnelError> {
278 let partition_count = self.partition_permits.len();
279 if partition >= partition_count {
280 return Err(DeltaFunnelError::Config {
281 message: format!(
282 "async read limiter partition {partition} is out of range for {partition_count} partitions"
283 ),
284 });
285 }
286
287 Ok(DeltaProviderAsyncPartitionReadLimiter {
288 partition,
289 limiter: Arc::clone(self),
290 })
291 }
292
293 #[cfg(test)]
294 pub(crate) fn active_file_reads(&self) -> usize {
295 self.options
296 .resolved_max_concurrent_file_reads_per_scan()
297 .saturating_sub(self.scan_permits.available_permits())
298 }
299
300 #[cfg(test)]
301 pub(crate) fn partition_active_file_reads(&self, partition: usize) -> Option<usize> {
302 self.partition_permits.get(partition).map(|permits| {
303 self.options
304 .max_concurrent_file_reads_per_partition
305 .saturating_sub(permits.available_permits())
306 })
307 }
308
309 async fn acquire_file_permit(
310 self: &Arc<Self>,
311 partition: usize,
312 ) -> Result<DeltaProviderAsyncFileReadPermit, DeltaFunnelError> {
313 let scan_permit = Arc::clone(&self.scan_permits)
314 .acquire_owned()
315 .await
316 .map_err(|_| DeltaFunnelError::Config {
317 message: "async read limiter scan permits are closed".to_owned(),
318 })?;
319 let partition_permits = self.partition_permits.get(partition).ok_or_else(|| {
320 DeltaFunnelError::Config {
321 message: format!(
322 "async read limiter partition {partition} is out of range for {} partitions",
323 self.partition_permits.len()
324 ),
325 }
326 })?;
327 let partition_permit = Arc::clone(partition_permits)
328 .acquire_owned()
329 .await
330 .map_err(|_| DeltaFunnelError::Config {
331 message: format!("async read limiter partition {partition} permits are closed"),
332 })?;
333
334 Ok(DeltaProviderAsyncFileReadPermit {
335 _scan_permit: scan_permit,
336 _partition_permit: partition_permit,
337 })
338 }
339
340 #[cfg(test)]
341 fn try_acquire_file_permit(
342 self: &Arc<Self>,
343 partition: usize,
344 ) -> Option<DeltaProviderAsyncFileReadPermit> {
345 let scan_permit = Arc::clone(&self.scan_permits).try_acquire_owned().ok()?;
346 let partition_permits = self.partition_permits.get(partition)?;
347 let partition_permit = match Arc::clone(partition_permits).try_acquire_owned() {
348 Ok(permit) => permit,
349 Err(_) => return None,
350 };
351
352 Some(DeltaProviderAsyncFileReadPermit {
353 _scan_permit: scan_permit,
354 _partition_permit: partition_permit,
355 })
356 }
357}
358
359#[allow(dead_code)]
360impl DeltaProviderAsyncPartitionReadLimiter {
361 pub(crate) async fn acquire_file_permit(
362 &self,
363 ) -> Result<DeltaProviderAsyncFileReadPermit, DeltaFunnelError> {
364 self.limiter.acquire_file_permit(self.partition).await
365 }
366
367 #[cfg(test)]
368 fn try_acquire_file_permit(&self) -> Option<DeltaProviderAsyncFileReadPermit> {
369 self.limiter.try_acquire_file_permit(self.partition)
370 }
371}
372
373impl DeltaProviderSyncPartitionReadLimiter {
374 pub(crate) fn acquire_file_permit(&self) -> DeltaProviderSyncFileReadPermit {
375 self.limiter.acquire_file_permit(self.partition)
376 }
377
378 #[cfg(test)]
379 fn try_acquire_file_permit(&self) -> Option<DeltaProviderSyncFileReadPermit> {
380 self.limiter.try_acquire_file_permit(self.partition)
381 }
382}
383
384impl Drop for DeltaProviderSyncFileReadPermit {
385 fn drop(&mut self) {
386 if !self.released {
387 self.limiter.release_file_permit(self.partition);
388 self.released = true;
389 }
390 }
391}
392
393impl Default for DeltaProviderScanExecutionOptions {
394 fn default() -> Self {
395 Self {
396 reader_backend: DeltaProviderReaderBackend::NativeAsync,
397 max_concurrent_file_reads_per_scan: None,
398 max_concurrent_file_reads_per_partition: NATIVE_ASYNC_DEFAULT_FILE_READS_PER_PARTITION,
399 output_buffer_capacity_per_partition: 1,
400 native_async_prefetch_file_count_per_partition:
401 NATIVE_ASYNC_DEFAULT_PREFETCH_FILE_COUNT_PER_PARTITION,
402 }
403 }
404}
405
406impl DeltaProviderScanExecutionOptions {
407 pub fn try_new(
409 max_concurrent_file_reads_per_scan: usize,
410 max_concurrent_file_reads_per_partition: usize,
411 ) -> Result<Self, DeltaFunnelError> {
412 Self::try_new_with_reader_backend(
413 DeltaProviderReaderBackend::OfficialKernel,
414 max_concurrent_file_reads_per_scan,
415 max_concurrent_file_reads_per_partition,
416 )
417 }
418
419 pub fn try_new_with_reader_backend(
421 reader_backend: DeltaProviderReaderBackend,
422 max_concurrent_file_reads_per_scan: usize,
423 max_concurrent_file_reads_per_partition: usize,
424 ) -> Result<Self, DeltaFunnelError> {
425 let native_async_prefetch_file_count_per_partition =
426 default_native_async_prefetch_file_count_per_partition(
427 reader_backend,
428 max_concurrent_file_reads_per_partition,
429 );
430 let options = Self {
431 reader_backend,
432 max_concurrent_file_reads_per_scan: Some(max_concurrent_file_reads_per_scan),
433 max_concurrent_file_reads_per_partition,
434 output_buffer_capacity_per_partition: 1,
435 native_async_prefetch_file_count_per_partition,
436 };
437 options.validate()?;
438 Ok(options)
439 }
440
441 pub fn with_output_buffer_capacity_per_partition(
443 mut self,
444 output_buffer_capacity_per_partition: usize,
445 ) -> Result<Self, DeltaFunnelError> {
446 self.output_buffer_capacity_per_partition = output_buffer_capacity_per_partition;
447 self.validate()?;
448 Ok(self)
449 }
450
451 pub fn with_native_async_prefetch_file_count_per_partition(
453 mut self,
454 native_async_prefetch_file_count_per_partition: usize,
455 ) -> Result<Self, DeltaFunnelError> {
456 self.native_async_prefetch_file_count_per_partition =
457 native_async_prefetch_file_count_per_partition;
458 self.validate()?;
459 Ok(self)
460 }
461
462 pub(crate) fn with_default_scan_wide_capacity_for_target_partitions(
463 mut self,
464 target_partitions: usize,
465 ) -> Result<Self, DeltaFunnelError> {
466 validate_positive("target_partitions", target_partitions)?;
467 self = self.with_resolved_scan_wide_capacity(target_partitions);
468 self.validate()?;
469 Ok(self)
470 }
471
472 fn with_resolved_scan_wide_capacity(mut self, target_partitions: usize) -> Self {
473 if self.max_concurrent_file_reads_per_scan.is_none() {
474 self.max_concurrent_file_reads_per_scan = Some(
475 target_partitions
476 .saturating_mul(self.max_concurrent_file_reads_per_partition)
477 .max(1),
478 );
479 }
480 self
481 }
482
483 fn resolved_max_concurrent_file_reads_per_scan(&self) -> usize {
484 self.max_concurrent_file_reads_per_scan
485 .unwrap_or_else(|| self.max_concurrent_file_reads_per_partition.max(1))
486 }
487
488 pub fn validate(&self) -> Result<(), DeltaFunnelError> {
490 validate_reader_backend(self.reader_backend)?;
491 if let Some(max_concurrent_file_reads_per_scan) = self.max_concurrent_file_reads_per_scan {
492 validate_positive(
493 "max_concurrent_file_reads_per_scan",
494 max_concurrent_file_reads_per_scan,
495 )?;
496 }
497 validate_positive(
498 "max_concurrent_file_reads_per_partition",
499 self.max_concurrent_file_reads_per_partition,
500 )?;
501 validate_positive(
502 "output_buffer_capacity_per_partition",
503 self.output_buffer_capacity_per_partition,
504 )?;
505 Ok(())
506 }
507}
508
509fn validate_reader_backend(
510 reader_backend: DeltaProviderReaderBackend,
511) -> Result<(), DeltaFunnelError> {
512 match reader_backend {
513 DeltaProviderReaderBackend::OfficialKernel | DeltaProviderReaderBackend::NativeAsync => {
514 Ok(())
515 }
516 }
517}
518
519fn validate_positive(name: &'static str, value: usize) -> Result<(), DeltaFunnelError> {
520 if value == 0 {
521 return Err(DeltaFunnelError::Config {
522 message: format!("{name} must be greater than zero"),
523 });
524 }
525
526 Ok(())
527}
528
529fn default_native_async_prefetch_file_count_per_partition(
530 reader_backend: DeltaProviderReaderBackend,
531 max_concurrent_file_reads_per_partition: usize,
532) -> usize {
533 if reader_backend != DeltaProviderReaderBackend::NativeAsync {
534 return 0;
535 }
536
537 max_concurrent_file_reads_per_partition
538 .saturating_sub(1)
539 .min(NATIVE_ASYNC_DEFAULT_PREFETCH_FILE_COUNT_PER_PARTITION)
540}
541
542#[cfg(test)]
543mod tests {
544 use super::{
545 DeltaProviderAsyncReadLimiter, DeltaProviderReaderBackend,
546 DeltaProviderScanExecutionOptions, DeltaProviderSyncReadLimiter,
547 NATIVE_ASYNC_DEFAULT_FILE_READS_PER_PARTITION,
548 NATIVE_ASYNC_DEFAULT_PREFETCH_FILE_COUNT_PER_PARTITION,
549 };
550
551 #[test]
552 fn default_execution_options_are_valid() -> Result<(), Box<dyn std::error::Error>> {
553 DeltaProviderScanExecutionOptions::default().validate()?;
554
555 Ok(())
556 }
557
558 #[test]
559 fn default_execution_options_select_native_async_backend()
560 -> Result<(), Box<dyn std::error::Error>> {
561 let options = DeltaProviderScanExecutionOptions::default();
562
563 assert_eq!(
564 options.reader_backend,
565 DeltaProviderReaderBackend::NativeAsync
566 );
567 assert_eq!(options.max_concurrent_file_reads_per_scan, None);
568 assert_eq!(
569 options.max_concurrent_file_reads_per_partition,
570 NATIVE_ASYNC_DEFAULT_FILE_READS_PER_PARTITION
571 );
572 assert_eq!(options.output_buffer_capacity_per_partition, 1);
573 assert_eq!(
574 options.native_async_prefetch_file_count_per_partition,
575 NATIVE_ASYNC_DEFAULT_PREFETCH_FILE_COUNT_PER_PARTITION
576 );
577 options.validate()?;
578
579 Ok(())
580 }
581
582 #[test]
583 fn execution_options_can_select_official_kernel_backend()
584 -> Result<(), Box<dyn std::error::Error>> {
585 let options = DeltaProviderScanExecutionOptions::try_new_with_reader_backend(
586 DeltaProviderReaderBackend::OfficialKernel,
587 2,
588 1,
589 )?;
590
591 assert_eq!(
592 options.reader_backend,
593 DeltaProviderReaderBackend::OfficialKernel
594 );
595 assert_eq!(options.max_concurrent_file_reads_per_scan, Some(2));
596 assert_eq!(options.max_concurrent_file_reads_per_partition, 1);
597 assert_eq!(options.output_buffer_capacity_per_partition, 1);
598 assert_eq!(options.native_async_prefetch_file_count_per_partition, 0);
599
600 Ok(())
601 }
602
603 #[test]
604 fn execution_options_can_select_native_async_backend() -> Result<(), Box<dyn std::error::Error>>
605 {
606 let options = DeltaProviderScanExecutionOptions::try_new_with_reader_backend(
607 DeltaProviderReaderBackend::NativeAsync,
608 2,
609 1,
610 )?;
611
612 assert_eq!(
613 options.reader_backend,
614 DeltaProviderReaderBackend::NativeAsync
615 );
616 assert_eq!(options.max_concurrent_file_reads_per_scan, Some(2));
617 assert_eq!(options.max_concurrent_file_reads_per_partition, 1);
618 assert_eq!(options.output_buffer_capacity_per_partition, 1);
619 assert_eq!(options.native_async_prefetch_file_count_per_partition, 0);
620
621 Ok(())
622 }
623
624 #[test]
625 fn native_async_backend_defaults_to_bounded_prefetch_when_capacity_allows()
626 -> Result<(), Box<dyn std::error::Error>> {
627 let options = DeltaProviderScanExecutionOptions::try_new_with_reader_backend(
628 DeltaProviderReaderBackend::NativeAsync,
629 12,
630 3,
631 )?;
632
633 assert_eq!(
634 options.native_async_prefetch_file_count_per_partition,
635 NATIVE_ASYNC_DEFAULT_PREFETCH_FILE_COUNT_PER_PARTITION
636 );
637
638 Ok(())
639 }
640
641 #[test]
642 fn native_async_backend_default_prefetch_respects_partition_capacity()
643 -> Result<(), Box<dyn std::error::Error>> {
644 let options = DeltaProviderScanExecutionOptions::try_new_with_reader_backend(
645 DeltaProviderReaderBackend::NativeAsync,
646 2,
647 2,
648 )?;
649
650 assert_eq!(options.native_async_prefetch_file_count_per_partition, 1);
651
652 Ok(())
653 }
654
655 #[test]
656 fn native_async_backend_can_force_lazy_after_default_prefetch()
657 -> Result<(), Box<dyn std::error::Error>> {
658 let options = DeltaProviderScanExecutionOptions::try_new_with_reader_backend(
659 DeltaProviderReaderBackend::NativeAsync,
660 12,
661 3,
662 )?
663 .with_native_async_prefetch_file_count_per_partition(0)?;
664
665 assert_eq!(options.native_async_prefetch_file_count_per_partition, 0);
666
667 Ok(())
668 }
669
670 #[test]
671 fn native_async_default_scan_wide_capacity_uses_target_partitions()
672 -> Result<(), Box<dyn std::error::Error>> {
673 let options = DeltaProviderScanExecutionOptions::default()
674 .with_default_scan_wide_capacity_for_target_partitions(8)?;
675
676 assert_eq!(
677 options.max_concurrent_file_reads_per_scan,
678 Some(8 * NATIVE_ASYNC_DEFAULT_FILE_READS_PER_PARTITION)
679 );
680 assert_eq!(
681 options.max_concurrent_file_reads_per_partition,
682 NATIVE_ASYNC_DEFAULT_FILE_READS_PER_PARTITION
683 );
684
685 Ok(())
686 }
687
688 #[test]
689 fn explicit_scan_wide_capacity_resolution_is_noop() -> Result<(), Box<dyn std::error::Error>> {
690 let options = DeltaProviderScanExecutionOptions::try_new_with_reader_backend(
691 DeltaProviderReaderBackend::OfficialKernel,
692 2,
693 1,
694 )?
695 .with_default_scan_wide_capacity_for_target_partitions(8)?;
696
697 assert_eq!(options.max_concurrent_file_reads_per_scan, Some(2));
698 assert_eq!(options.max_concurrent_file_reads_per_partition, 1);
699
700 Ok(())
701 }
702
703 #[test]
704 fn execution_options_can_set_output_buffer_capacity() -> Result<(), Box<dyn std::error::Error>>
705 {
706 let options = DeltaProviderScanExecutionOptions::try_new_with_reader_backend(
707 DeltaProviderReaderBackend::NativeAsync,
708 2,
709 1,
710 )?
711 .with_output_buffer_capacity_per_partition(4)?;
712
713 assert_eq!(options.output_buffer_capacity_per_partition, 4);
714
715 Ok(())
716 }
717
718 #[test]
719 fn execution_options_can_set_native_async_prefetch_depth()
720 -> Result<(), Box<dyn std::error::Error>> {
721 let options = DeltaProviderScanExecutionOptions::try_new_with_reader_backend(
722 DeltaProviderReaderBackend::NativeAsync,
723 2,
724 2,
725 )?
726 .with_native_async_prefetch_file_count_per_partition(1)?;
727
728 assert_eq!(options.native_async_prefetch_file_count_per_partition, 1);
729
730 Ok(())
731 }
732
733 #[test]
734 fn execution_options_reject_zero_max_concurrent_file_reads_per_scan() {
735 let error = DeltaProviderScanExecutionOptions::try_new(0, 1)
736 .expect_err("zero max_concurrent_file_reads_per_scan must fail");
737
738 assert_eq!(
739 error.to_string(),
740 "configuration error: max_concurrent_file_reads_per_scan must be greater than zero"
741 );
742 }
743
744 #[test]
745 fn execution_options_reject_zero_max_concurrent_file_reads_per_partition() {
746 let error = DeltaProviderScanExecutionOptions::try_new(1, 0)
747 .expect_err("zero max_concurrent_file_reads_per_partition must fail");
748
749 assert_eq!(
750 error.to_string(),
751 "configuration error: max_concurrent_file_reads_per_partition must be greater than zero"
752 );
753 }
754
755 #[test]
756 fn execution_options_reject_zero_output_buffer_capacity() {
757 let error = DeltaProviderScanExecutionOptions::default()
758 .with_output_buffer_capacity_per_partition(0)
759 .expect_err("zero output_buffer_capacity_per_partition must fail");
760
761 assert_eq!(
762 error.to_string(),
763 "configuration error: output_buffer_capacity_per_partition must be greater than zero"
764 );
765 }
766
767 #[test]
768 fn sync_file_permit_releases_on_drop() -> Result<(), Box<dyn std::error::Error>> {
769 let limiter =
770 DeltaProviderSyncReadLimiter::new(DeltaProviderScanExecutionOptions::default(), 1);
771 let partition = limiter.partition_limiter(0)?;
772
773 let permit = partition.acquire_file_permit();
774 assert_eq!(limiter.active_file_reads(), 1);
775
776 drop(permit);
777
778 assert_eq!(limiter.active_file_reads(), 0);
779
780 Ok(())
781 }
782
783 #[test]
784 fn sync_file_permit_rejects_out_of_range_partition() -> Result<(), Box<dyn std::error::Error>> {
785 let limiter =
786 DeltaProviderSyncReadLimiter::new(DeltaProviderScanExecutionOptions::default(), 1);
787 let error = match limiter.partition_limiter(1) {
788 Ok(_) => return Err("out of range partition must fail".into()),
789 Err(error) => error,
790 };
791
792 assert_eq!(
793 error.to_string(),
794 "configuration error: sync read limiter partition 1 is out of range for 1 partitions"
795 );
796
797 Ok(())
798 }
799
800 #[test]
801 fn sync_file_permit_respects_global_cap() -> Result<(), Box<dyn std::error::Error>> {
802 let limiter =
803 DeltaProviderSyncReadLimiter::new(DeltaProviderScanExecutionOptions::try_new(1, 1)?, 2);
804 let first_partition = limiter.partition_limiter(0)?;
805 let second_partition = limiter.partition_limiter(1)?;
806
807 let first_permit = first_partition.acquire_file_permit();
808
809 assert!(second_partition.try_acquire_file_permit().is_none());
810 assert_eq!(limiter.active_file_reads(), 1);
811
812 drop(first_permit);
813
814 let second_permit = second_partition
815 .try_acquire_file_permit()
816 .ok_or("expected global permit after release")?;
817
818 assert_eq!(limiter.active_file_reads(), 1);
819
820 drop(second_permit);
821
822 assert_eq!(limiter.active_file_reads(), 0);
823
824 Ok(())
825 }
826
827 #[test]
828 fn sync_file_permit_respects_partition_cap() -> Result<(), Box<dyn std::error::Error>> {
829 let limiter =
830 DeltaProviderSyncReadLimiter::new(DeltaProviderScanExecutionOptions::try_new(2, 1)?, 2);
831 let first_partition = limiter.partition_limiter(0)?;
832 let second_partition = limiter.partition_limiter(1)?;
833
834 let first_permit = first_partition.acquire_file_permit();
835
836 assert!(first_partition.try_acquire_file_permit().is_none());
837
838 let second_permit = second_partition
839 .try_acquire_file_permit()
840 .ok_or("expected another partition to use remaining global capacity")?;
841
842 assert_eq!(limiter.active_file_reads(), 2);
843
844 drop(first_permit);
845 drop(second_permit);
846
847 assert_eq!(limiter.active_file_reads(), 0);
848
849 Ok(())
850 }
851
852 #[tokio::test]
853 async fn async_file_permit_rejects_out_of_range_partition()
854 -> Result<(), Box<dyn std::error::Error>> {
855 let limiter =
856 DeltaProviderAsyncReadLimiter::new(DeltaProviderScanExecutionOptions::default(), 1);
857 let error = match limiter.partition_limiter(1) {
858 Ok(_) => return Err("out of range partition must fail".into()),
859 Err(error) => error,
860 };
861
862 assert_eq!(
863 error.to_string(),
864 "configuration error: async read limiter partition 1 is out of range for 1 partitions"
865 );
866
867 Ok(())
868 }
869
870 #[tokio::test]
871 async fn async_file_permit_respects_global_cap() -> Result<(), Box<dyn std::error::Error>> {
872 let limiter = DeltaProviderAsyncReadLimiter::new(
873 DeltaProviderScanExecutionOptions::try_new(1, 1)?,
874 2,
875 );
876 let first_partition = limiter.partition_limiter(0)?;
877 let second_partition = limiter.partition_limiter(1)?;
878
879 let first_permit = first_partition.acquire_file_permit().await?;
880
881 assert!(second_partition.try_acquire_file_permit().is_none());
882 assert_eq!(limiter.active_file_reads(), 1);
883 assert_eq!(limiter.partition_active_file_reads(0), Some(1));
884 assert_eq!(limiter.partition_active_file_reads(1), Some(0));
885
886 drop(first_permit);
887
888 let second_permit = second_partition
889 .try_acquire_file_permit()
890 .ok_or("expected global permit after release")?;
891
892 assert_eq!(limiter.active_file_reads(), 1);
893 assert_eq!(limiter.partition_active_file_reads(0), Some(0));
894 assert_eq!(limiter.partition_active_file_reads(1), Some(1));
895
896 drop(second_permit);
897
898 assert_eq!(limiter.active_file_reads(), 0);
899 assert_eq!(limiter.partition_active_file_reads(1), Some(0));
900
901 Ok(())
902 }
903
904 #[tokio::test]
905 async fn async_file_permit_respects_partition_cap() -> Result<(), Box<dyn std::error::Error>> {
906 let limiter = DeltaProviderAsyncReadLimiter::new(
907 DeltaProviderScanExecutionOptions::try_new(2, 1)?,
908 2,
909 );
910 let first_partition = limiter.partition_limiter(0)?;
911 let second_partition = limiter.partition_limiter(1)?;
912
913 let first_permit = first_partition.acquire_file_permit().await?;
914
915 assert!(first_partition.try_acquire_file_permit().is_none());
916
917 let second_permit = second_partition
918 .try_acquire_file_permit()
919 .ok_or("expected another partition to use remaining global capacity")?;
920
921 assert_eq!(limiter.active_file_reads(), 2);
922 assert_eq!(limiter.partition_active_file_reads(0), Some(1));
923 assert_eq!(limiter.partition_active_file_reads(1), Some(1));
924
925 drop(first_permit);
926 drop(second_permit);
927
928 assert_eq!(limiter.active_file_reads(), 0);
929 assert_eq!(limiter.partition_active_file_reads(0), Some(0));
930 assert_eq!(limiter.partition_active_file_reads(1), Some(0));
931
932 Ok(())
933 }
934
935 #[tokio::test]
936 async fn async_file_permit_releases_on_success() -> Result<(), Box<dyn std::error::Error>> {
937 let limiter =
938 DeltaProviderAsyncReadLimiter::new(DeltaProviderScanExecutionOptions::default(), 1);
939 let partition = limiter.partition_limiter(0)?;
940
941 async fn successful_read(
942 partition: &super::DeltaProviderAsyncPartitionReadLimiter,
943 ) -> Result<(), crate::DeltaFunnelError> {
944 let _permit = partition.acquire_file_permit().await?;
945 Ok(())
946 }
947
948 successful_read(&partition).await?;
949
950 assert_eq!(limiter.active_file_reads(), 0);
951 assert_eq!(limiter.partition_active_file_reads(0), Some(0));
952
953 Ok(())
954 }
955
956 #[tokio::test]
957 async fn async_file_permit_releases_on_failure() -> Result<(), Box<dyn std::error::Error>> {
958 let limiter =
959 DeltaProviderAsyncReadLimiter::new(DeltaProviderScanExecutionOptions::default(), 1);
960 let partition = limiter.partition_limiter(0)?;
961
962 async fn failing_read(
963 partition: &super::DeltaProviderAsyncPartitionReadLimiter,
964 ) -> Result<(), crate::DeltaFunnelError> {
965 let _permit = partition.acquire_file_permit().await?;
966 Err(crate::DeltaFunnelError::Config {
967 message: "fake async read failure".to_owned(),
968 })
969 }
970
971 let error = match failing_read(&partition).await {
972 Ok(_) => return Err("fake read failure must fail".into()),
973 Err(error) => error,
974 };
975
976 assert_eq!(
977 error.to_string(),
978 "configuration error: fake async read failure"
979 );
980 assert_eq!(limiter.active_file_reads(), 0);
981 assert_eq!(limiter.partition_active_file_reads(0), Some(0));
982
983 Ok(())
984 }
985
986 #[tokio::test]
987 async fn async_file_permit_releases_on_drop() -> Result<(), Box<dyn std::error::Error>> {
988 let limiter =
989 DeltaProviderAsyncReadLimiter::new(DeltaProviderScanExecutionOptions::default(), 1);
990 let partition = limiter.partition_limiter(0)?;
991
992 let permit = partition.acquire_file_permit().await?;
993
994 assert_eq!(limiter.active_file_reads(), 1);
995 assert_eq!(limiter.partition_active_file_reads(0), Some(1));
996
997 drop(permit);
998
999 assert_eq!(limiter.active_file_reads(), 0);
1000 assert_eq!(limiter.partition_active_file_reads(0), Some(0));
1001
1002 Ok(())
1003 }
1004
1005 #[test]
1006 fn async_limiter_source_avoids_blocking_runtime_patterns()
1007 -> Result<(), Box<dyn std::error::Error>> {
1008 let source = include_str!("scheduling.rs");
1009 let async_limiter_source = source_section(
1010 source,
1011 "impl DeltaProviderAsyncReadLimiter",
1012 "impl DeltaProviderSyncPartitionReadLimiter",
1013 )?;
1014 let forbidden_patterns = [
1015 concat!("Cond", "var"),
1016 concat!("Mut", "ex"),
1017 concat!("block", "_", "on"),
1018 concat!("Handle", "::", "block", "_", "on"),
1019 concat!("Runtime", "::", "new"),
1020 concat!("spawn", "_", "blocking"),
1021 ];
1022
1023 for pattern in forbidden_patterns {
1024 assert!(
1025 !async_limiter_source.contains(pattern),
1026 "async limiter source must not contain {pattern}"
1027 );
1028 }
1029
1030 Ok(())
1031 }
1032
1033 fn source_section<'a>(
1034 source: &'a str,
1035 start_pattern: &str,
1036 end_pattern: &str,
1037 ) -> Result<&'a str, Box<dyn std::error::Error>> {
1038 let start = source
1039 .find(start_pattern)
1040 .ok_or("expected source section start")?;
1041 let end = source[start..]
1042 .find(end_pattern)
1043 .map(|offset| start + offset)
1044 .ok_or("expected source section end")?;
1045
1046 Ok(&source[start..end])
1047 }
1048}