execution_policy/
concurrency.rs1use std::collections::VecDeque;
6use std::future::Future;
7use std::pin::Pin;
8use std::sync::{Arc, Mutex};
9use std::task::{Context, Poll, Waker};
10use std::time::Duration;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum SaturationPolicy {
15 Wait {
17 max_queued: usize,
18 queue_timeout: Option<Duration>,
19 },
20 Reject,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub(crate) enum Scope {
27 Operations,
28 Attempts,
29}
30
31#[derive(Debug, Clone)]
33pub struct ConcurrencyLimit {
34 permits: usize,
35 scope: Scope,
36 saturation: SaturationPolicy,
37}
38
39impl ConcurrencyLimit {
40 pub fn operations(n: usize) -> Self {
42 Self::new(n, Scope::Operations)
43 }
44 pub fn attempts(n: usize) -> Self {
46 Self::new(n, Scope::Attempts)
47 }
48
49 fn new(permits: usize, scope: Scope) -> Self {
50 Self {
51 permits: permits.max(1),
52 scope,
53 saturation: SaturationPolicy::Wait {
54 max_queued: usize::MAX,
55 queue_timeout: None,
56 },
57 }
58 }
59
60 pub fn max_queued(mut self, n: usize) -> Self {
62 self.saturation = match self.saturation {
63 SaturationPolicy::Wait { queue_timeout, .. } => SaturationPolicy::Wait {
64 max_queued: n,
65 queue_timeout,
66 },
67 SaturationPolicy::Reject => SaturationPolicy::Reject,
68 };
69 self
70 }
71 pub fn queue_timeout(mut self, d: Duration) -> Self {
73 self.saturation = match self.saturation {
74 SaturationPolicy::Wait { max_queued, .. } => SaturationPolicy::Wait {
75 max_queued,
76 queue_timeout: Some(d),
77 },
78 SaturationPolicy::Reject => SaturationPolicy::Reject,
79 };
80 self
81 }
82 pub fn reject(mut self) -> Self {
84 self.saturation = SaturationPolicy::Reject;
85 self
86 }
87
88 pub(crate) fn build(&self) -> Arc<Semaphore> {
89 Semaphore::new(self.permits)
90 }
91
92 pub(crate) fn compile(&self) -> CompiledConcurrency {
94 CompiledConcurrency {
95 sem: self.build(),
96 saturation: self.saturation,
97 scope: self.scope,
98 }
99 }
100}
101
102#[derive(Debug)]
104pub(crate) struct CompiledConcurrency {
105 pub(crate) sem: Arc<Semaphore>,
106 pub(crate) saturation: SaturationPolicy,
107 pub(crate) scope: Scope,
108}
109
110impl From<usize> for ConcurrencyLimit {
111 fn from(n: usize) -> Self {
113 ConcurrencyLimit::operations(n)
114 }
115}
116
117impl From<u32> for ConcurrencyLimit {
118 fn from(n: u32) -> Self {
119 ConcurrencyLimit::operations(n as usize)
120 }
121}
122
123#[derive(Debug)]
124struct SemState {
125 permits: usize,
126 waiters: VecDeque<Waker>,
127}
128
129#[derive(Debug)]
131pub(crate) struct Semaphore {
132 state: Mutex<SemState>,
133}
134
135impl Semaphore {
136 fn new(permits: usize) -> Arc<Self> {
137 Arc::new(Self {
138 state: Mutex::new(SemState {
139 permits,
140 waiters: VecDeque::new(),
141 }),
142 })
143 }
144
145 pub(crate) fn queued(self: &Arc<Self>) -> usize {
147 self.state.lock().unwrap().waiters.len()
148 }
149
150 pub(crate) fn try_acquire(self: &Arc<Self>) -> Option<Permit> {
152 let mut st = self.state.lock().unwrap();
153 if st.permits > 0 {
154 st.permits -= 1;
155 Some(Permit {
156 sem: Arc::clone(self),
157 })
158 } else {
159 None
160 }
161 }
162
163 pub(crate) fn acquire(self: &Arc<Self>) -> Acquire {
165 Acquire {
166 sem: Arc::clone(self),
167 }
168 }
169
170 fn release(&self) {
171 let mut st = self.state.lock().unwrap();
172 st.permits += 1;
173 if let Some(w) = st.waiters.pop_front() {
174 w.wake();
175 }
176 }
177}
178
179#[derive(Debug)]
181pub(crate) struct Permit {
182 sem: Arc<Semaphore>,
183}
184
185impl Drop for Permit {
186 fn drop(&mut self) {
187 self.sem.release();
188 }
189}
190
191pub(crate) struct Acquire {
193 sem: Arc<Semaphore>,
194}
195
196impl Future for Acquire {
197 type Output = Permit;
198
199 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Permit> {
200 let mut st = self.sem.state.lock().unwrap();
201 if st.permits > 0 {
202 st.permits -= 1;
203 return Poll::Ready(Permit {
204 sem: Arc::clone(&self.sem),
205 });
206 }
207 st.waiters.push_back(cx.waker().clone());
209 Poll::Pending
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[tokio::test]
218 async fn permit_release_lets_next_in() {
219 let cfg = ConcurrencyLimit::operations(1);
220 let sem = cfg.build();
221 let p1 = sem.try_acquire().expect("first permit");
222 assert!(sem.try_acquire().is_none(), "saturated");
223
224 let acq = sem.acquire();
226 tokio::pin!(acq);
227 let waiter = async { (&mut acq).await };
228 tokio::pin!(waiter);
229
230 let releaser = async {
231 tokio::task::yield_now().await;
232 drop(p1); };
234 let (_p2, ()) = tokio::join!(waiter, releaser);
235 assert!(sem.try_acquire().is_none());
237 }
238
239 #[test]
240 fn queued_counts_waiters() {
241 let sem = ConcurrencyLimit::operations(1).build();
242 let _p = sem.try_acquire().unwrap();
243 assert_eq!(sem.queued(), 0);
244 }
245
246 #[test]
247 fn saturation_builders() {
248 let c = ConcurrencyLimit::attempts(8)
249 .max_queued(4)
250 .queue_timeout(Duration::from_millis(10))
251 .compile();
252 assert_eq!(c.scope, Scope::Attempts);
253 match c.saturation {
254 SaturationPolicy::Wait {
255 max_queued,
256 queue_timeout,
257 } => {
258 assert_eq!(max_queued, 4);
259 assert_eq!(queue_timeout, Some(Duration::from_millis(10)));
260 }
261 _ => panic!("expected Wait"),
262 }
263 assert_eq!(
264 ConcurrencyLimit::operations(2)
265 .reject()
266 .compile()
267 .saturation,
268 SaturationPolicy::Reject
269 );
270 }
271}