Skip to main content

delta_funnel/query_engine/datafusion/execution/
scheduling.rs

1//! Bounded scheduling options for Delta scan execution.
2
3use std::sync::{Arc, Condvar, Mutex, MutexGuard};
4
5use tokio::sync::{OwnedSemaphorePermit, Semaphore};
6
7use crate::DeltaFunnelError;
8
9/// Native async bounded file prefetch depth selected by benchmark evidence.
10pub const NATIVE_ASYNC_DEFAULT_PREFETCH_FILE_COUNT_PER_PARTITION: usize = 2;
11
12/// Native async per-partition file-read capacity selected by benchmark evidence.
13pub const NATIVE_ASYNC_DEFAULT_FILE_READS_PER_PARTITION: usize = 3;
14
15/// Provider file-reader backend selected for Delta scan execution.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum DeltaProviderReaderBackend {
18    /// Current official delta_kernel reader baseline.
19    OfficialKernel,
20    /// Native async Parquet reader for row-index-preserving file tasks.
21    NativeAsync,
22}
23
24impl DeltaProviderReaderBackend {
25    /// Whether this backend can apply file-read predicates before DV masking
26    /// without losing each row's original physical Parquet row index.
27    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/// Bounded scheduling options for one Delta DataFusion provider scan.
36///
37/// These limits apply to provider-scheduled physical Delta file reads. The
38/// official-kernel sync fallback limits a conservative file-level
39/// handoff: reading one file and sending its batches into the bounded
40/// DataFusion output channel. The native async reader preserves the same
41/// active-read semantics with async semaphore-style permits.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub struct DeltaProviderScanExecutionOptions {
44    /// File-reader backend used by normal provider scan execution.
45    pub reader_backend: DeltaProviderReaderBackend,
46
47    /// Maximum file-read handoffs that may run at once across the whole scan.
48    ///
49    /// This is the scan-wide concurrency cap shared by all DataFusion execution
50    /// partitions for one provider scan.
51    pub max_concurrent_file_reads_per_scan: Option<usize>,
52
53    /// Maximum file-read handoffs that one execution partition may own at once.
54    ///
55    /// This prevents a single DataFusion execution partition from consuming all
56    /// scan-wide read capacity when multiple scan partitions are active.
57    pub max_concurrent_file_reads_per_partition: usize,
58
59    /// Bounded output channel capacity for each DataFusion execution partition.
60    ///
61    /// This is the producer-to-DataFusion handoff queue used after file reads
62    /// produce Arrow batches. A value of `1` preserves the historical behavior.
63    pub output_buffer_capacity_per_partition: usize,
64
65    /// Native async file reads to prefetch ahead of downstream demand per partition.
66    ///
67    /// A value of `0` keeps the native async backend fully lazy: each file is
68    /// opened only after the previous file is drained. Positive values allow
69    /// the native async backend to begin opening additional files while the
70    /// current file is still producing batches. This setting is internal
71    /// hardening and benchmark surface; the official-kernel backend ignores it.
72    pub native_async_prefetch_file_count_per_partition: usize,
73}
74
75/// Sync fallback limiter for provider-scheduled file work in one Delta scan.
76///
77/// This limiter exists for the official-kernel synchronous iterator bridge.
78/// Native async execution uses `DeltaProviderAsyncReadLimiter` at the same
79/// scheduling boundary.
80pub(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/// Per-execution-partition view of a sync fallback read limiter.
93#[derive(Clone)]
94pub(crate) struct DeltaProviderSyncPartitionReadLimiter {
95    partition: usize,
96    limiter: Arc<DeltaProviderSyncReadLimiter>,
97}
98
99/// Sync fallback guard for one provider file handoff.
100///
101/// The current official-kernel reader exposes a synchronous file iterator, so
102/// the provider limits a conservative unit: reading one Delta file and handing
103/// all batches from that file to the bounded DataFusion output channel.
104pub(crate) struct DeltaProviderSyncFileReadPermit {
105    partition: usize,
106    limiter: Arc<DeltaProviderSyncReadLimiter>,
107    released: bool,
108}
109
110/// Async limiter for native provider file work in one Delta scan.
111///
112/// This limiter is intentionally separate from the official-kernel sync
113/// fallback limiter. Native async readers must acquire both scan-wide and
114/// partition-local capacity before starting file, object-store, or range-read
115/// work.
116#[allow(dead_code)]
117pub(crate) struct DeltaProviderAsyncReadLimiter {
118    options: DeltaProviderScanExecutionOptions,
119    scan_permits: Arc<Semaphore>,
120    partition_permits: Vec<Arc<Semaphore>>,
121}
122
123/// Per-execution-partition view of the native async read limiter.
124#[allow(dead_code)]
125#[derive(Clone)]
126pub(crate) struct DeltaProviderAsyncPartitionReadLimiter {
127    partition: usize,
128    limiter: Arc<DeltaProviderAsyncReadLimiter>,
129}
130
131/// Owned async permit for one native provider file handoff.
132#[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    /// Builds validated Delta provider scan execution options.
408    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    /// Builds validated Delta provider scan execution options with a reader backend.
420    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    /// Sets the per-partition bounded output buffer capacity.
442    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    /// Sets native async file-read prefetch depth per execution partition.
452    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    /// Validates provider scan execution bounds before registration or scan execution.
489    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}