netget/llm/rate_limiter.rs
1//! LLM rate limiter with concurrency control and token-based throttling
2//!
3//! This module provides a rate limiter that controls:
4//! 1. **Concurrency**: Maximum number of concurrent LLM requests
5//! 2. **Token usage**: Maximum tokens per time window (for API usage control)
6//!
7//! # Concurrency is a queue, not a filter
8//!
9//! `max_concurrent` bounds how many LLM calls run *at once*; it does not decide
10//! which requests are worth serving. A request that arrives while the limit is
11//! saturated **waits for a permit** — including network-sourced ones. This is
12//! what `--llm-max-concurrent 1` ("sequential processing") has always claimed to
13//! mean, and it is now what it does.
14//!
15//! Network requests used to `try_acquire` and fail instantly instead, which in
16//! the shipped default configuration (`max_concurrent: 1`) meant that a second
17//! simultaneous connection had its LLM call refused, its handler returned `Err`,
18//! and most protocols write nothing on that path — so the peer hung until its own
19//! timeout with nothing on the wire. Two concurrent `curl`s were enough.
20//!
21//! The wait is bounded in **two** dimensions, because an unbounded queue of
22//! network requests is its own denial of service (a slow backend plus a burst of
23//! connections becomes unbounded memory, and every peer waits forever):
24//!
25//! - [`RateLimiterConfig::max_queued`] caps how many network requests may be
26//! waiting at once. Past that the limiter fails fast, so the overload is
27//! reported at the edge instead of being absorbed into RAM.
28//! - [`RateLimiterConfig::queue_timeout_secs`] caps how long any one of them
29//! waits. It defaults to one backend request timeout: with a healthy backend a
30//! permit frees within at most one in-flight call, so exceeding it means the
31//! queue ahead is genuinely deeper than the system can drain.
32//!
33//! Both failures surface as a typed [`RateLimitError`], so a protocol can tell
34//! "the system is overloaded" apart from "the model said something unusable" and
35//! answer the peer accordingly (HTTP does: 503 rather than 500). Use
36//! [`is_overload_error`] to test an [`anyhow::Error`] for it.
37//!
38//! Token-limit exhaustion is different in kind and still discards network
39//! requests: it is a *budget* that will not free up on the timescale of a
40//! request/response exchange, so waiting for it would only convert a fast refusal
41//! into a slow one.
42
43use crate::utils::clock::Instant;
44use anyhow::{Context, Result};
45use std::collections::VecDeque;
46use std::future::Future;
47use std::pin::Pin;
48use std::sync::atomic::{AtomicUsize, Ordering};
49use std::sync::Arc;
50use std::task::{Context as TaskContext, Poll, Waker};
51use std::time::Duration;
52use tokio::sync::{Mutex, RwLock};
53use tracing::{debug, info, warn};
54
55/// Default bound on how long a network-sourced request waits for a concurrency
56/// permit.
57///
58/// Deliberately equal to [`crate::llm::ollama_client::DEFAULT_REQUEST_TIMEOUT`]:
59/// the request holding the permit cannot itself run longer than that, so a
60/// shorter wait would reject requests that were about to be served, and a longer
61/// one would keep queueing behind a backlog the backend is not draining.
62///
63/// *Derived* from it rather than restated, because the equality is load-bearing and a
64/// hand-copied `120` silently stopped being equal the moment the request timeout moved.
65pub const DEFAULT_QUEUE_TIMEOUT_SECS: u64 =
66 crate::llm::ollama_client::DEFAULT_REQUEST_TIMEOUT.as_secs();
67
68/// Default bound on how many network-sourced requests may wait for a permit at
69/// once. Beyond this the limiter fails fast rather than growing the queue.
70pub const DEFAULT_MAX_QUEUED: usize = 128;
71
72/// Source of the LLM request
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum RequestSource {
75 /// User input (waits for capacity, unbounded)
76 User,
77 /// Network event (waits for a concurrency permit within the configured
78 /// bounds; discarded outright only when the token budget is exhausted)
79 Network,
80}
81
82/// Why the rate limiter refused a request.
83///
84/// Carried as the source of the [`anyhow::Error`] returned by
85/// [`RateLimiter::acquire_permit`] so callers can distinguish an overloaded
86/// system from a failed LLM call. See [`is_overload_error`].
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum RateLimitError {
89 /// Waited the full [`RateLimiterConfig::queue_timeout_secs`] without a
90 /// concurrency permit becoming available.
91 QueueTimeout {
92 /// How long the request waited, in seconds.
93 waited_secs: u64,
94 },
95 /// Too many network requests were already waiting for a permit.
96 QueueFull {
97 /// The configured [`RateLimiterConfig::max_queued`].
98 max_queued: usize,
99 },
100 /// The token budget for the current window is exhausted.
101 TokenLimit {
102 /// The configured token limit.
103 limit: u64,
104 /// The window the limit applies to, in seconds.
105 window_secs: u64,
106 },
107}
108
109impl std::fmt::Display for RateLimitError {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 match self {
112 Self::QueueTimeout { waited_secs } => write!(
113 f,
114 "LLM backend overloaded: waited {}s for a concurrency permit without one becoming available",
115 waited_secs
116 ),
117 Self::QueueFull { max_queued } => write!(
118 f,
119 "LLM backend overloaded: {} requests already queued for a concurrency permit",
120 max_queued
121 ),
122 Self::TokenLimit { limit, window_secs } => write!(
123 f,
124 "LLM token budget exhausted: {} tokens per {}s window",
125 limit, window_secs
126 ),
127 }
128 }
129}
130
131impl std::error::Error for RateLimitError {}
132
133/// Whether `err` (or anything in its context chain) is a rate-limiter refusal.
134///
135/// Protocols use this to answer a peer with an overload status instead of a
136/// generic failure — the distinction matters because an overload is transient
137/// and retryable while a malformed model answer is not.
138pub fn is_overload_error(err: &anyhow::Error) -> bool {
139 err.downcast_ref::<RateLimitError>().is_some()
140}
141
142/// Token usage record with timestamp
143#[derive(Debug, Clone)]
144struct TokenUsage {
145 timestamp: Instant,
146 input_tokens: u64,
147 output_tokens: u64,
148}
149
150/// Rate limiter configuration
151#[derive(Debug, Clone)]
152pub struct RateLimiterConfig {
153 /// Maximum concurrent LLM requests (default: 1)
154 pub max_concurrent: usize,
155
156 /// Maximum tokens per time window (None = unlimited)
157 /// Default: None for local Ollama, recommended: 10000 for cloud APIs
158 pub token_limit: Option<u64>,
159
160 /// Time window for token limiting in seconds (default: 60)
161 pub token_window_secs: u64,
162
163 /// How long a network-sourced request waits for a concurrency permit before
164 /// giving up (default: [`DEFAULT_QUEUE_TIMEOUT_SECS`]). `0` means wait
165 /// forever — only sane when the peer has its own timeout and you would
166 /// rather it drove the deadline.
167 pub queue_timeout_secs: u64,
168
169 /// How many network-sourced requests may wait for a concurrency permit at
170 /// once (default: [`DEFAULT_MAX_QUEUED`]). `0` means unbounded, which trades
171 /// the memory bound away; prefer a large number to zero.
172 pub max_queued: usize,
173}
174
175impl Default for RateLimiterConfig {
176 fn default() -> Self {
177 Self {
178 max_concurrent: 1,
179 token_limit: None,
180 token_window_secs: 60,
181 queue_timeout_secs: DEFAULT_QUEUE_TIMEOUT_SECS,
182 max_queued: DEFAULT_MAX_QUEUED,
183 }
184 }
185}
186
187/// LLM rate limiter with concurrency and token-based throttling
188#[derive(Clone)]
189pub struct RateLimiter {
190 /// One resizable gate shared by old permits and new waiters.
191 semaphore: Arc<ConcurrencyGate>,
192
193 /// Configuration (can be updated at runtime)
194 config: Arc<RwLock<RateLimiterConfig>>,
195
196 /// Token usage history (protected by mutex for interior mutability)
197 token_usage: Arc<Mutex<Vec<TokenUsage>>>,
198
199 /// Network-sourced requests currently waiting for a concurrency permit.
200 /// Kept outside `stats` so the queue-depth check on the hot path never
201 /// contends with the stats mutex.
202 queued_network: Arc<AtomicUsize>,
203 waiting_users: Arc<AtomicUsize>,
204
205 /// Statistics
206 stats: Arc<Mutex<RateLimiterStats>>,
207}
208
209/// Decrements the queue-depth counter however the wait ends — permit acquired,
210/// timed out, or task cancelled mid-`await`. A plain `fetch_sub` after the await
211/// would leak the slot on cancellation, and a leaked slot is permanent: the
212/// limiter would refuse ever more requests as `QueueFull` with nothing actually
213/// queued.
214struct QueueSlot(Arc<AtomicUsize>);
215
216impl Drop for QueueSlot {
217 fn drop(&mut self) {
218 self.0.fetch_sub(1, Ordering::AcqRel);
219 }
220}
221
222/// Rate limiter statistics
223#[derive(Debug, Default, Clone)]
224pub struct RateLimiterStats {
225 /// Total requests attempted
226 pub total_requests: u64,
227
228 /// Requests completed
229 pub requests_completed: u64,
230
231 /// Requests refused outright (token budget exhausted, or the wait queue was
232 /// already full)
233 pub requests_discarded: u64,
234
235 /// Requests waiting (user inputs that are queued)
236 pub requests_waiting: u64,
237
238 /// Network requests that had to wait for a concurrency permit rather than
239 /// getting one immediately
240 pub requests_queued: u64,
241
242 /// Network requests that gave up after waiting the full queue timeout
243 pub requests_queue_timed_out: u64,
244
245 /// Network requests waiting for a concurrency permit right now
246 pub currently_queued: u64,
247
248 /// Total input tokens processed
249 pub total_input_tokens: u64,
250
251 /// Total output tokens processed
252 pub total_output_tokens: u64,
253
254 /// Current tokens in window
255 pub current_window_tokens: u64,
256}
257
258impl RateLimiter {
259 /// Create a new rate limiter with the given configuration
260 pub fn new(config: RateLimiterConfig) -> Self {
261 let max_concurrent = config.max_concurrent;
262
263 Self {
264 semaphore: Arc::new(ConcurrencyGate::new(max_concurrent)),
265 config: Arc::new(RwLock::new(config)),
266 token_usage: Arc::new(Mutex::new(Vec::new())),
267 queued_network: Arc::new(AtomicUsize::new(0)),
268 waiting_users: Arc::new(AtomicUsize::new(0)),
269 stats: Arc::new(Mutex::new(RateLimiterStats::default())),
270 }
271 }
272
273 /// Update the rate limiter configuration at runtime
274 pub async fn update_config(&self, config: RateLimiterConfig) -> Result<()> {
275 let mut current_config = self.config.write().await;
276
277 // Existing requests count against the new limit. Shrinking never revokes a
278 // running request; new admission waits for the excess requests to finish.
279 self.semaphore.resize(config.max_concurrent);
280
281 *current_config = config;
282 info!(
283 "Rate limiter config updated: max_concurrent={}, token_limit={:?}, window={}s, queue_timeout={}s, max_queued={}",
284 current_config.max_concurrent,
285 current_config.token_limit,
286 current_config.token_window_secs,
287 current_config.queue_timeout_secs,
288 current_config.max_queued
289 );
290
291 Ok(())
292 }
293
294 /// Get current configuration
295 pub async fn get_config(&self) -> RateLimiterConfig {
296 self.config.read().await.clone()
297 }
298
299 /// Get current statistics
300 pub async fn get_stats(&self) -> RateLimiterStats {
301 // Update current window tokens before returning stats
302 let window = Duration::from_secs(self.config.read().await.token_window_secs);
303
304 // Snapshot usage before locking statistics. Never nest the config and stats
305 // guards: a pending config writer can otherwise deadlock concurrent readers.
306 // Compare ages rather than subtracting an arbitrary user-supplied window from
307 // Instant, which panics when the duration cannot be represented by the clock.
308 let token_usage = self.token_usage.lock().await;
309 let window_tokens: u64 = token_usage
310 .iter()
311 .filter(|usage| usage.timestamp.elapsed() <= window)
312 .fold(0u64, |total, usage| {
313 total.saturating_add(usage.input_tokens.saturating_add(usage.output_tokens))
314 });
315 drop(token_usage);
316
317 let mut stats = self.stats.lock().await;
318 stats.current_window_tokens = window_tokens;
319 stats.currently_queued = self.queued_network.load(Ordering::Acquire) as u64;
320 stats.requests_waiting = self.waiting_users.load(Ordering::Acquire) as u64;
321 stats.clone()
322 }
323
324 /// Drop token-usage records that have fallen out of the current window.
325 ///
326 /// Called unconditionally rather than only on the token-limited path: the
327 /// history is appended to on *every* LLM call, so skipping the prune when
328 /// `token_limit` is `None` — the default, and therefore what every long-lived
329 /// `netget --mcp` runs — grew the vector for the life of the process and made
330 /// `get_stats()` scan all of it.
331 async fn prune_token_usage(&self, window_secs: u64) {
332 let window = Duration::from_secs(window_secs);
333 self.token_usage
334 .lock()
335 .await
336 .retain(|usage| usage.timestamp.elapsed() <= window);
337 }
338
339 /// Check if we have token capacity available
340 async fn check_token_capacity(&self) -> Result<bool> {
341 let config = self.config.read().await.clone();
342 let window_secs = config.token_window_secs;
343
344 // Prune before the early return, not after it.
345 self.prune_token_usage(window_secs).await;
346
347 // If no token limit, always have capacity
348 let Some(token_limit) = config.token_limit else {
349 return Ok(true);
350 };
351
352 let token_usage = self.token_usage.lock().await;
353
354 // Calculate total tokens in current window
355 let window_tokens: u64 = token_usage.iter().fold(0u64, |total, usage| {
356 total.saturating_add(usage.input_tokens.saturating_add(usage.output_tokens))
357 });
358
359 debug!(
360 "Token usage check: {}/{} tokens in {}s window",
361 window_tokens, token_limit, config.token_window_secs
362 );
363
364 Ok(window_tokens < token_limit)
365 }
366
367 /// Record token usage after a successful LLM call
368 pub async fn record_token_usage(&self, input_tokens: u64, output_tokens: u64) {
369 let usage = TokenUsage {
370 timestamp: Instant::now(),
371 input_tokens,
372 output_tokens,
373 };
374
375 // Add to history, then drop anything that has aged out of the window.
376 // Pruning here (and not only in `check_token_capacity`) is what actually
377 // bounds the vector: with the default `token_limit: None` the capacity
378 // check returns before it ever needs the history.
379 let window_secs = self.config.read().await.token_window_secs;
380 self.token_usage.lock().await.push(usage);
381 self.prune_token_usage(window_secs).await;
382
383 // Update stats
384 let mut stats = self.stats.lock().await;
385 stats.total_input_tokens = stats.total_input_tokens.saturating_add(input_tokens);
386 stats.total_output_tokens = stats.total_output_tokens.saturating_add(output_tokens);
387 stats.requests_completed += 1;
388
389 debug!(
390 "Recorded token usage: input={}, output={}, total_requests={}",
391 input_tokens, output_tokens, stats.requests_completed
392 );
393 }
394
395 /// Number of token-usage records currently retained.
396 ///
397 /// Exposed so tests can pin the pruning behaviour; the history is otherwise
398 /// an implementation detail.
399 pub async fn token_history_len(&self) -> usize {
400 self.token_usage.lock().await.len()
401 }
402
403 /// Enforce the token budget both before queueing and after a permit frees.
404 async fn wait_for_token_capacity(&self, source: RequestSource) -> Result<()> {
405 // Check token capacity
406 let has_token_capacity = self.check_token_capacity().await?;
407
408 if !has_token_capacity {
409 match source {
410 RequestSource::User => {
411 // For user requests, wait for token capacity
412 info!("Token limit reached, waiting for capacity (user request)");
413
414 // Poll until we have capacity (with exponential backoff)
415 let mut delay = Duration::from_millis(100);
416 while !self.check_token_capacity().await? {
417 tokio::time::sleep(delay).await;
418 delay = std::cmp::min(delay * 2, Duration::from_secs(5));
419 }
420
421 info!("Token capacity available, proceeding with user request");
422 }
423 RequestSource::Network => {
424 // Token exhaustion is a budget, not congestion: it will not
425 // clear on the timescale of one request, so waiting would
426 // only turn a fast refusal into a slow one.
427 self.stats.lock().await.requests_discarded += 1;
428 let config = self.config.read().await.clone();
429 warn!(
430 "Discarding network event: Token limit ({}/{} tokens in {}s window)",
431 self.token_usage
432 .lock()
433 .await
434 .iter()
435 .fold(0u64, |total, usage| {
436 total.saturating_add(
437 usage.input_tokens.saturating_add(usage.output_tokens),
438 )
439 }),
440 config.token_limit.unwrap_or(0),
441 config.token_window_secs
442 );
443
444 return Err(anyhow::Error::new(RateLimitError::TokenLimit {
445 limit: config.token_limit.unwrap_or(0),
446 window_secs: config.token_window_secs,
447 }));
448 }
449 }
450 }
451
452 Ok(())
453 }
454
455 /// Acquire a permit for an LLM request
456 ///
457 /// - For `RequestSource::User`: waits until capacity is available, without a
458 /// deadline. A human sitting at the TUI would rather wait than be refused.
459 /// - For `RequestSource::Network`: waits for a concurrency permit within the
460 /// configured queue bounds, so `max_concurrent: 1` serializes requests
461 /// instead of dropping them. Refused with a [`RateLimitError`] when the
462 /// queue is full, the wait times out, or the token budget is exhausted.
463 ///
464 /// Returns a permit guard that must be held for the duration of the LLM call.
465 /// The permit is automatically released when the guard is dropped.
466 pub async fn acquire_permit(&self, source: RequestSource) -> Result<RateLimiterPermit> {
467 // Update stats
468 {
469 let mut stats = self.stats.lock().await;
470 stats.total_requests += 1;
471 }
472
473 let _user_wait = (source == RequestSource::User).then(|| {
474 self.waiting_users.fetch_add(1, Ordering::AcqRel);
475 QueueSlot(Arc::clone(&self.waiting_users))
476 });
477 debug!("Acquiring rate limiter permit for {:?} request", source);
478
479 self.wait_for_token_capacity(source).await?;
480
481 // Queue policy is snapshotted for this wait; concurrency admission always
482 // uses the live gate so reconfiguration cannot create overlapping pools.
483 let semaphore = Arc::clone(&self.semaphore);
484 let (queue_timeout_secs, max_queued, max_concurrent) = {
485 let config = self.config.read().await;
486 (
487 config.queue_timeout_secs,
488 config.max_queued,
489 config.max_concurrent,
490 )
491 };
492
493 // Acquire concurrency permit
494 let permit = match source {
495 RequestSource::User => {
496 // For user requests, wait for permit
497 debug!("Waiting for concurrency permit (user request)");
498 semaphore
499 .acquire_owned()
500 .await
501 .context("Failed to acquire semaphore permit")?
502 }
503 RequestSource::Network => {
504 // Fast path: capacity available right now, nothing to queue.
505 match Arc::clone(&semaphore).try_acquire_owned() {
506 Ok(permit) => permit,
507 Err(_) => {
508 // Saturated. Queue, within bounds.
509 let depth = self.queued_network.fetch_add(1, Ordering::AcqRel) + 1;
510 let _slot = QueueSlot(Arc::clone(&self.queued_network));
511
512 if max_queued > 0 && depth > max_queued {
513 let mut stats = self.stats.lock().await;
514 stats.requests_discarded += 1;
515 warn!(
516 "Refusing network event: {} requests already waiting for one of {} concurrency permits",
517 max_queued, max_concurrent
518 );
519 return Err(anyhow::Error::new(RateLimitError::QueueFull {
520 max_queued,
521 }));
522 }
523
524 {
525 let mut stats = self.stats.lock().await;
526 stats.requests_queued += 1;
527 }
528 debug!(
529 "Network event queued for a concurrency permit ({} waiting, max_concurrent={})",
530 depth, max_concurrent
531 );
532
533 let acquire = semaphore.acquire_owned();
534 let acquired = if queue_timeout_secs == 0 {
535 acquire.await.map(Some).map_err(anyhow::Error::from)
536 } else {
537 match tokio::time::timeout(
538 Duration::from_secs(queue_timeout_secs),
539 acquire,
540 )
541 .await
542 {
543 Ok(result) => result.map(Some).map_err(anyhow::Error::from),
544 Err(_elapsed) => Ok(None),
545 }
546 };
547
548 match acquired.context("Failed to acquire semaphore permit")? {
549 Some(permit) => permit,
550 None => {
551 let mut stats = self.stats.lock().await;
552 stats.requests_queue_timed_out += 1;
553 warn!(
554 "Refusing network event: waited {}s for one of {} concurrency permits",
555 queue_timeout_secs, max_concurrent
556 );
557 return Err(anyhow::Error::new(RateLimitError::QueueTimeout {
558 waited_secs: queue_timeout_secs,
559 }));
560 }
561 }
562 }
563 }
564 }
565 };
566
567 // An earlier request may have spent the remaining budget while this one
568 // waited for its concurrency permit. Its pre-queue check is no longer enough.
569 self.wait_for_token_capacity(source).await?;
570
571 debug!("Rate limiter permit acquired for {:?} request", source);
572
573 Ok(RateLimiterPermit {
574 _permit: permit,
575 rate_limiter: self.clone(),
576 })
577 }
578}
579
580/// RAII guard for a rate limiter permit
581///
582/// Automatically releases the concurrency permit when dropped.
583pub struct RateLimiterPermit {
584 _permit: ConcurrencyPermit,
585 rate_limiter: RateLimiter,
586}
587
588impl RateLimiterPermit {
589 /// Record token usage for this request
590 pub async fn record_usage(&self, input_tokens: u64, output_tokens: u64) {
591 self.rate_limiter
592 .record_token_usage(input_tokens, output_tokens)
593 .await;
594 }
595}
596
597// A FIFO gate keeps resize, release and cancellation synchronous. Awaiting tasks
598// hold no lock; dropping a queued future removes its slot and wakes its successor.
599struct ConcurrencyGate {
600 state: std::sync::Mutex<ConcurrencyState>,
601}
602struct ConcurrencyState {
603 limit: usize,
604 active: usize,
605 next_id: u64,
606 queue: VecDeque<(u64, Waker)>,
607}
608impl ConcurrencyGate {
609 fn new(limit: usize) -> Self {
610 Self {
611 state: std::sync::Mutex::new(ConcurrencyState {
612 limit,
613 active: 0,
614 next_id: 0,
615 queue: VecDeque::new(),
616 }),
617 }
618 }
619 fn lock(&self) -> std::sync::MutexGuard<'_, ConcurrencyState> {
620 self.state.lock().unwrap_or_else(|e| e.into_inner())
621 }
622 fn resize(&self, limit: usize) {
623 let wake = {
624 let mut state = self.lock();
625 state.limit = limit;
626 state.queue.front().map(|(_, w)| w.clone())
627 };
628 if let Some(w) = wake {
629 w.wake();
630 }
631 }
632 fn try_acquire_owned(self: Arc<Self>) -> Result<ConcurrencyPermit, ()> {
633 {
634 let mut state = self.lock();
635 if state.active >= state.limit || !state.queue.is_empty() {
636 return Err(());
637 }
638 state.active += 1;
639 }
640 Ok(ConcurrencyPermit(self))
641 }
642 fn acquire_owned(self: Arc<Self>) -> ConcurrencyWait {
643 ConcurrencyWait {
644 gate: self,
645 id: None,
646 }
647 }
648}
649struct ConcurrencyWait {
650 gate: Arc<ConcurrencyGate>,
651 id: Option<u64>,
652}
653impl Future for ConcurrencyWait {
654 type Output = Result<ConcurrencyPermit>;
655 fn poll(self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<Self::Output> {
656 let this = self.get_mut();
657 let gate = Arc::clone(&this.gate);
658 let mut state = gate.lock();
659 let first = this.id.map_or(state.queue.is_empty(), |id| {
660 state.queue.front().is_some_and(|(front, _)| *front == id)
661 });
662 if first && state.active < state.limit {
663 if this.id.take().is_some() {
664 state.queue.pop_front();
665 }
666 state.active += 1;
667 let wake = if state.active < state.limit {
668 state.queue.front().map(|(_, w)| w.clone())
669 } else {
670 None
671 };
672 drop(state);
673 if let Some(w) = wake {
674 w.wake();
675 }
676 return Poll::Ready(Ok(ConcurrencyPermit(gate)));
677 }
678 if let Some(id) = this.id {
679 if let Some((_, w)) = state.queue.iter_mut().find(|(entry, _)| *entry == id) {
680 w.clone_from(cx.waker());
681 }
682 } else {
683 let id = state.next_id;
684 state.next_id = state.next_id.wrapping_add(1);
685 state.queue.push_back((id, cx.waker().clone()));
686 this.id = Some(id);
687 }
688 Poll::Pending
689 }
690}
691impl Drop for ConcurrencyWait {
692 fn drop(&mut self) {
693 if let Some(id) = self.id {
694 let wake = {
695 let mut state = self.gate.lock();
696 state.queue.retain(|(entry, _)| *entry != id);
697 state.queue.front().map(|(_, w)| w.clone())
698 };
699 if let Some(w) = wake {
700 w.wake();
701 }
702 }
703 }
704}
705struct ConcurrencyPermit(Arc<ConcurrencyGate>);
706impl Drop for ConcurrencyPermit {
707 fn drop(&mut self) {
708 let wake = {
709 let mut state = self.0.lock();
710 state.active -= 1;
711 state.queue.front().map(|(_, w)| w.clone())
712 };
713 if let Some(w) = wake {
714 w.wake();
715 }
716 }
717}