Skip to main content

primitives/correlated_randomness/stream/buffered/
config.rs

1// ================================
2// ===== Buffer Configuration =====
3// ================================
4
5use std::{sync::Arc, time::Duration};
6
7use blanket::blanket;
8use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
9
10use crate::correlated_randomness::stream::CorrelatedStreamError;
11
12/// Configuration for the `PreprocessingStream`.
13#[derive(Debug, Clone)]
14pub struct BufferConfig {
15    /// Maximum number of elements the buffer can hold.
16    capacity: usize,
17    /// Maximum number of elements that can be requested at once.
18    max_request_size: usize,
19    /// Threshold to trigger generation of elements.
20    refill_threshold: usize,
21}
22
23impl BufferConfig {
24    /// Capacity sentinel for an effectively unbounded buffer: admission never trips and the
25    /// dispatcher does not pre-allocate it. Used by on-demand streams (see `new_on_demand`).
26    pub const UNBOUNDED: usize = usize::MAX;
27
28    /// Creates a new `BufferConfig` with the specified capacity, and sets:
29    /// - `max_request_size = capacity` (allowing requests up to the full buffer size).
30    /// - `refill_threshold = capacity` (always refill when any elements are consumed).
31    pub fn eager(capacity: usize) -> Self {
32        assert!(capacity > 0, "capacity must be greater than 0");
33        Self {
34            capacity,
35            max_request_size: capacity,
36            refill_threshold: capacity,
37        }
38    }
39
40    /// Creates a new `BufferConfig` with the specified capacity and max_request_size, and sets:
41    /// - `refill_threshold = capacity` (always refill when any elements are consumed).
42    pub fn eager_with(capacity: usize, max_request_size: usize) -> Self {
43        assert!(capacity > 0, "capacity must be greater than 0");
44        Self {
45            capacity,
46            max_request_size: max_request_size.min(capacity),
47            refill_threshold: capacity,
48        }
49    }
50
51    /// Creates a new `BufferConfig` with the specified capacity and refill threshold, and sets:
52    /// - `max_request_size = capacity` (allowing requests up to the full buffer size).
53    ///
54    /// If `refill_threshold` > `capacity`, it is adjusted down to match `capacity`.
55    pub fn lazy(capacity: usize, refill_threshold: usize) -> Self {
56        assert!(capacity > 0, "capacity must be greater than 0");
57        Self {
58            capacity,
59            max_request_size: capacity,
60            refill_threshold: refill_threshold.min(capacity),
61        }
62    }
63
64    /// Creates a new `BufferConfig` with the specified capacity, max_request_size, and
65    /// refill_threshold.
66    ///
67    /// If `max_request_size` > `capacity` or `refill_threshold` > `capacity`, they are adjusted
68    /// down to match `capacity`.
69    pub fn lazy_with(capacity: usize, refill_threshold: usize, max_request_size: usize) -> Self {
70        assert!(capacity > 0, "capacity must be greater than 0");
71        Self {
72            capacity,
73            max_request_size: max_request_size.min(capacity),
74            refill_threshold: refill_threshold.min(capacity),
75        }
76    }
77
78    /// The maximum number of elements the buffer can hold (validated to be >= max_request_size).
79    #[inline]
80    pub fn capacity(&self) -> usize {
81        self.capacity
82    }
83
84    /// The maximum number of elements that can be requested at once (validated to be <= capacity).
85    #[inline]
86    pub fn max_request_size(&self) -> usize {
87        self.max_request_size
88    }
89
90    /// The threshold to trigger generation of elements (validated to be <= capacity).
91    #[inline]
92    pub fn refill_threshold(&self) -> usize {
93        self.refill_threshold
94    }
95
96    // `capacity` is the ceiling: `max_request_size` and `refill_threshold` are always clamped to
97    // it, matching the constructors. Setting the capacity down pulls the other two down with it;
98    // setting either of the others clamps to the current capacity (never grows it).
99
100    /// Sets the capacity, clamping `max_request_size` and `refill_threshold` down to fit.
101    pub fn set_capacity(&mut self, capacity: usize) {
102        assert!(capacity > 0, "capacity must be greater than 0");
103        self.capacity = capacity;
104        self.max_request_size = self.max_request_size.min(capacity);
105        self.refill_threshold = self.refill_threshold.min(capacity);
106    }
107
108    /// Sets the max_request_size, clamped to the current capacity.
109    pub fn set_max_request_size(&mut self, max_request_size: usize) {
110        self.max_request_size = max_request_size.min(self.capacity);
111    }
112
113    /// Sets the refill_threshold, clamped to the current capacity.
114    pub fn set_refill_threshold(&mut self, refill_threshold: usize) {
115        self.refill_threshold = refill_threshold.min(self.capacity);
116    }
117}
118
119// ================================
120// ===== Buffer trait =============
121// ================================
122
123/// Default timeout when acquiring the config lock. Critical sections are tiny CPU work
124/// (a few `usize` reads/writes) and never held across `.await`, so any contention longer
125/// than this indicates a logic bug or stuck thread.
126pub const LOCK_TIMEOUT: Duration = Duration::from_millis(500);
127
128/// Acquires a shared **read** lock (concurrent reads don't block) for at most `LOCK_TIMEOUT`,
129/// returning `LockTimeout` on failure.
130#[inline]
131pub fn try_read_config(
132    m: &RwLock<BufferConfig>,
133) -> Result<RwLockReadGuard<'_, BufferConfig>, CorrelatedStreamError> {
134    m.try_read_for(LOCK_TIMEOUT)
135        .ok_or(CorrelatedStreamError::LockTimeout {
136            timeout_ms: LOCK_TIMEOUT.as_millis() as u64,
137        })
138}
139
140/// Acquires an exclusive **write** lock for at most `LOCK_TIMEOUT`, returning `LockTimeout` on
141/// failure.
142#[inline]
143pub fn try_write_config(
144    m: &RwLock<BufferConfig>,
145) -> Result<RwLockWriteGuard<'_, BufferConfig>, CorrelatedStreamError> {
146    m.try_write_for(LOCK_TIMEOUT)
147        .ok_or(CorrelatedStreamError::LockTimeout {
148            timeout_ms: LOCK_TIMEOUT.as_millis() as u64,
149        })
150}
151
152/// A shared, thread-safe handle to a [`BufferConfig`].
153pub type SharedBufferConfig = Arc<RwLock<BufferConfig>>;
154
155/// A buffer-backed entity exposing a shared `BufferConfig`.
156///
157/// Default-impl methods provide thread-safe getters and setters through an
158/// `Arc<parking_lot::RwLock<_>>`, with a bounded timeout (`LOCK_TIMEOUT`) on acquisition. Getters
159/// take a shared read lock (concurrent reads don't block); setters take an exclusive write lock.
160/// Critical sections are tiny and never held across `.await`.
161#[blanket(derive(Arc, Ref, Mut))]
162pub trait Buffer {
163    /// The shared configuration handle.
164    fn config(&self) -> &SharedBufferConfig;
165
166    #[inline]
167    fn capacity(&self) -> Result<usize, CorrelatedStreamError> {
168        Ok(try_read_config(self.config())?.capacity())
169    }
170    #[inline]
171    fn max_request_size(&self) -> Result<usize, CorrelatedStreamError> {
172        Ok(try_read_config(self.config())?.max_request_size())
173    }
174    #[inline]
175    fn refill_threshold(&self) -> Result<usize, CorrelatedStreamError> {
176        Ok(try_read_config(self.config())?.refill_threshold())
177    }
178
179    #[inline]
180    fn set_capacity(&self, capacity: usize) -> Result<(), CorrelatedStreamError> {
181        try_write_config(self.config())?.set_capacity(capacity);
182        Ok(())
183    }
184    #[inline]
185    fn set_max_request_size(&self, n: usize) -> Result<(), CorrelatedStreamError> {
186        try_write_config(self.config())?.set_max_request_size(n);
187        Ok(())
188    }
189    #[inline]
190    fn set_refill_threshold(&self, n: usize) -> Result<(), CorrelatedStreamError> {
191        try_write_config(self.config())?.set_refill_threshold(n);
192        Ok(())
193    }
194}
195
196/// A slice of config handles is itself a [`Buffer`], so a whole bundle of streams is tuned in one
197/// call. Writes (`set_*`) apply to every entry; reads (`capacity`, ...) return the first as a
198/// representative, assuming the bundle is configured uniformly.
199impl Buffer for [SharedBufferConfig] {
200    fn config(&self) -> &SharedBufferConfig {
201        self.first().expect("Buffer config slice must be non-empty")
202    }
203    fn set_capacity(&self, capacity: usize) -> Result<(), CorrelatedStreamError> {
204        self.iter().try_for_each(|c| {
205            try_write_config(c)?.set_capacity(capacity);
206            Ok(())
207        })
208    }
209    fn set_max_request_size(&self, n: usize) -> Result<(), CorrelatedStreamError> {
210        self.iter().try_for_each(|c| {
211            try_write_config(c)?.set_max_request_size(n);
212            Ok(())
213        })
214    }
215    fn set_refill_threshold(&self, n: usize) -> Result<(), CorrelatedStreamError> {
216        self.iter().try_for_each(|c| {
217            try_write_config(c)?.set_refill_threshold(n);
218            Ok(())
219        })
220    }
221}