crawlkit_engine/
backpressure.rs1use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
2use std::sync::Arc;
3
4use tokio::sync::{mpsc, Semaphore};
5
6pub struct BackpressureController {
26 semaphore: Arc<Semaphore>,
27 #[allow(dead_code)]
28 bounded_tx: Option<mpsc::Sender<()>>,
29 active_tasks: Arc<AtomicUsize>,
30 shutdown: Arc<AtomicBool>,
31}
32
33impl BackpressureController {
34 #[must_use]
39 pub fn new(max_concurrent: usize) -> Self {
40 Self {
41 semaphore: Arc::new(Semaphore::new(max_concurrent)),
42 bounded_tx: None,
43 active_tasks: Arc::new(AtomicUsize::new(0)),
44 shutdown: Arc::new(AtomicBool::new(false)),
45 }
46 }
47
48 #[must_use]
50 pub fn with_channel(max_concurrent: usize, channel_size: usize) -> Self {
51 let (tx, _rx) = mpsc::channel(channel_size);
52 Self {
53 semaphore: Arc::new(Semaphore::new(max_concurrent)),
54 bounded_tx: Some(tx),
55 active_tasks: Arc::new(AtomicUsize::new(0)),
56 shutdown: Arc::new(AtomicBool::new(false)),
57 }
58 }
59
60 pub async fn acquire(&self) -> Result<BackpressurePermit<'_>, BackpressureError> {
65 if self.shutdown.load(Ordering::Acquire) {
66 return Err(BackpressureError::ShutDown);
67 }
68
69 let permit = self
70 .semaphore
71 .clone()
72 .acquire_owned()
73 .await
74 .map_err(|_| BackpressureError::ShutDown)?;
75
76 self.active_tasks.fetch_add(1, Ordering::AcqRel);
77
78 Ok(BackpressurePermit {
79 controller: self,
80 _permit: permit,
81 })
82 }
83
84 #[must_use]
86 pub fn try_acquire(&self) -> Option<BackpressurePermit<'_>> {
87 if self.shutdown.load(Ordering::Acquire) {
88 return None;
89 }
90
91 let permit = self.semaphore.clone().try_acquire_owned().ok()?;
92
93 self.active_tasks.fetch_add(1, Ordering::AcqRel);
94
95 Some(BackpressurePermit {
96 controller: self,
97 _permit: permit,
98 })
99 }
100
101 #[must_use]
103 pub fn active_count(&self) -> usize {
104 self.active_tasks.load(Ordering::Acquire)
105 }
106
107 #[must_use]
109 pub fn is_at_capacity(&self) -> bool {
110 self.active_tasks.load(Ordering::Acquire) >= self.semaphore.available_permits()
111 }
112
113 pub fn shutdown(&self) {
115 self.shutdown.store(true, Ordering::Release);
116 }
117
118 #[must_use]
120 pub fn is_shut_down(&self) -> bool {
121 self.shutdown.load(Ordering::Acquire)
122 }
123}
124
125impl Default for BackpressureController {
126 fn default() -> Self {
127 Self::new(10)
128 }
129}
130
131pub struct BackpressurePermit<'a> {
133 controller: &'a BackpressureController,
134 _permit: tokio::sync::OwnedSemaphorePermit,
135}
136
137impl Drop for BackpressurePermit<'_> {
138 fn drop(&mut self) {
139 self.controller.active_tasks.fetch_sub(1, Ordering::AcqRel);
140 }
141}
142
143#[derive(Debug, thiserror::Error)]
145pub enum BackpressureError {
146 #[error("backpressure controller is shut down")]
147 ShutDown,
148}
149
150pub struct BoundedPipeline<T> {
168 tx: mpsc::Sender<T>,
169 rx: Option<mpsc::Receiver<T>>,
170 max_size: usize,
171}
172
173impl<T> BoundedPipeline<T> {
174 #[must_use]
176 pub fn new(max_size: usize) -> Self {
177 let (tx, rx) = mpsc::channel(max_size);
178 Self {
179 tx,
180 rx: Some(rx),
181 max_size,
182 }
183 }
184
185 #[must_use]
187 pub fn sender(&self) -> mpsc::Sender<T> {
188 self.tx.clone()
189 }
190
191 pub fn receiver(&mut self) -> Option<mpsc::Receiver<T>> {
193 self.rx.take()
194 }
195
196 #[must_use]
198 pub fn capacity(&self) -> usize {
199 self.max_size
200 }
201
202 #[must_use]
204 pub fn is_full(&self) -> bool {
205 self.tx.capacity() == 0
206 }
207}
208
209#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[tokio::test]
218 async fn test_backpressure_controller_acquire() {
219 let controller = BackpressureController::new(2);
220
221 let permit1 = controller.acquire().await.unwrap();
222 assert_eq!(controller.active_count(), 1);
223
224 let permit2 = controller.acquire().await.unwrap();
225 assert_eq!(controller.active_count(), 2);
226
227 drop(permit1);
228 assert_eq!(controller.active_count(), 1);
229
230 drop(permit2);
231 assert_eq!(controller.active_count(), 0);
232 }
233
234 #[tokio::test]
235 async fn test_backpressure_controller_shutdown() {
236 let controller = BackpressureController::new(1);
237 let _permit = controller.acquire().await.unwrap();
238
239 controller.shutdown();
240 assert!(controller.is_shut_down());
241 assert!(controller.acquire().await.is_err());
242 }
243
244 #[test]
245 fn test_bounded_pipeline() {
246 let mut pipeline = BoundedPipeline::<i32>::new(10);
247 assert_eq!(pipeline.capacity(), 10);
248
249 let tx = pipeline.sender();
250 assert!(tx.try_send(42).is_ok());
251
252 let mut rx = pipeline.receiver().unwrap();
253 assert_eq!(rx.try_recv().unwrap(), 42);
254 }
255}