zencodec_io/work_quota.rs
1use core::num::NonZeroUsize;
2
3/// Maximum codec steps in one uninterrupted async compute turn.
4///
5/// A step is indivisible. This bounds scheduling work units, not wall time,
6/// memory, rows or pending encoded output. Yielding never asks an encoder to
7/// drain: output backpressure needs a separate retention-qualified strategy.
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub struct WorkQuota(NonZeroUsize);
10
11impl WorkQuota {
12 /// A nonzero step quota; zero cannot make progress and returns `None`.
13 pub const fn new(steps: usize) -> Option<Self> {
14 match NonZeroUsize::new(steps) {
15 Some(n) => Some(Self(n)),
16 None => None,
17 }
18 }
19
20 /// Compatibility setting: finish the available batch without a quota yield.
21 pub const fn unlimited() -> Self {
22 Self(NonZeroUsize::MAX)
23 }
24
25 /// Maximum number of complete steps per turn.
26 pub const fn max_steps(self) -> usize {
27 self.0.get()
28 }
29}
30
31impl Default for WorkQuota {
32 fn default() -> Self {
33 Self::unlimited()
34 }
35}
36
37/// Give the calling executor one scheduling opportunity without depending on
38/// a particular runtime or putting I/O backpressure on the worker.
39#[cfg(feature = "tokio")]
40pub(crate) async fn yield_once() {
41 let mut yielded = false;
42 core::future::poll_fn(|cx| {
43 if yielded {
44 core::task::Poll::Ready(())
45 } else {
46 yielded = true;
47 cx.waker().wake_by_ref();
48 core::task::Poll::Pending
49 }
50 })
51 .await;
52}