1use std::{
19 convert::Infallible,
20 num::NonZeroUsize,
21 pin::Pin,
22 sync::{Arc, Mutex, PoisonError},
23 task::{Context, Poll, ready},
24 time::Duration,
25};
26
27use bytes::Bytes;
28use futures::future::BoxFuture;
29use http_body::Frame;
30use pin_project_lite::pin_project;
31use tokio::{sync::Semaphore, task::AbortHandle};
32use tonic::{
33 Status,
34 body::Body,
35 codegen::http::{Request, Response},
36 server::NamedService,
37};
38use tower::Service;
39
40type BoxError = Box<dyn std::error::Error + Send + Sync>;
41
42#[derive(Clone)]
49pub struct ServiceConcurrencyLimit<S> {
50 inner: S,
51 semaphore: Arc<Semaphore>,
52 load_shed: bool,
53}
54
55impl<S> ServiceConcurrencyLimit<S> {
56 pub fn new(inner: S, limit: NonZeroUsize, load_shed: bool) -> Self {
57 Self {
58 inner,
59 semaphore: Arc::new(Semaphore::new(limit.get().min(Semaphore::MAX_PERMITS))),
63 load_shed,
64 }
65 }
66}
67
68impl<S> Service<Request<Body>> for ServiceConcurrencyLimit<S>
69where
70 S: Service<Request<Body>, Response = Response<Body>, Error = Infallible>
71 + Clone
72 + Send
73 + 'static,
74 S::Future: Send + 'static,
75{
76 type Response = Response<Body>;
77 type Error = Infallible;
78 type Future = BoxFuture<'static, Result<Response<Body>, Infallible>>;
79
80 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
81 self.inner.poll_ready(cx)
82 }
83
84 fn call(&mut self, request: Request<Body>) -> Self::Future {
85 let clone = self.inner.clone();
98 let mut inner = std::mem::replace(&mut self.inner, clone);
99 let semaphore = self.semaphore.clone();
100 let load_shed = self.load_shed;
101
102 Box::pin(async move {
103 let _permit = if load_shed {
106 match semaphore.try_acquire_owned() {
107 Ok(permit) => permit,
108 Err(_) => {
109 return Ok(tonic::Status::resource_exhausted(
110 "service concurrency limit reached",
111 )
112 .into_http());
113 }
114 }
115 } else {
116 semaphore
117 .acquire_owned()
118 .await
119 .expect("the semaphore is never closed")
120 };
121 inner.call(request).await
122 })
123 }
124}
125
126impl<S: NamedService> NamedService for ServiceConcurrencyLimit<S> {
127 const NAME: &'static str = S::NAME;
128}
129
130pin_project! {
131 pub struct PermitGuardedBody<B, G> {
135 #[pin]
136 inner: B,
137 _guard: Option<G>,
138 }
139}
140
141impl<B, G> PermitGuardedBody<B, G> {
142 pub fn new(inner: B, guard: Option<G>) -> Self {
144 Self {
145 inner,
146 _guard: guard,
147 }
148 }
149}
150
151impl<B, G> http_body::Body for PermitGuardedBody<B, G>
152where
153 B: http_body::Body,
154{
155 type Data = B::Data;
156 type Error = B::Error;
157
158 fn poll_frame(
159 self: Pin<&mut Self>,
160 cx: &mut Context<'_>,
161 ) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
162 self.project().inner.poll_frame(cx)
163 }
164
165 fn is_end_stream(&self) -> bool {
166 self.inner.is_end_stream()
167 }
168
169 fn size_hint(&self) -> http_body::SizeHint {
170 self.inner.size_hint()
171 }
172}
173
174const MAX_FRAME_BYTES: usize = 64 * 1024;
178
179struct Held<B, D> {
181 inner: B,
182 pending: Bytes,
184 _until_deadline: Option<D>,
185}
186
187pin_project! {
188 pub struct ReclaimableBody<B, D, E> {
196 held: Arc<Mutex<Option<Held<B, D>>>>,
197 _until_end: Option<E>,
198 timer: Option<AbortHandle>,
199 }
200
201 impl<B, D, E> PinnedDrop for ReclaimableBody<B, D, E> {
202 fn drop(this: Pin<&mut Self>) {
203 if let Some(timer) = &this.timer {
204 timer.abort();
205 }
206 }
207 }
208}
209
210impl<B, D, E> ReclaimableBody<B, D, E>
211where
212 B: Send + 'static,
213 D: Send + 'static,
214{
215 pub fn new(inner: B, until_deadline: Option<D>, until_end: Option<E>) -> Self {
219 Self {
220 held: Arc::new(Mutex::new(Some(Held {
221 inner,
222 pending: Bytes::new(),
223 _until_deadline: until_deadline,
224 }))),
225 _until_end: until_end,
226 timer: None,
227 }
228 }
229
230 pub fn with_deadline(
233 inner: B,
234 until_deadline: Option<D>,
235 until_end: Option<E>,
236 deadline: Duration,
237 on_reclaim: impl FnOnce() + Send + 'static,
238 ) -> Self {
239 let mut body = Self::new(inner, until_deadline, until_end);
240 let held = body.held.clone();
241 let timer = tokio::spawn(async move {
242 tokio::time::sleep(deadline).await;
243 let reclaimed = held.lock().unwrap_or_else(PoisonError::into_inner).take();
244 if reclaimed.is_some() {
245 on_reclaim();
246 }
247 });
248 body.timer = Some(timer.abort_handle());
249 body
250 }
251}
252
253impl<B, D, E> Default for ReclaimableBody<B, D, E>
254where
255 B: Default + Send + 'static,
256 D: Send + 'static,
257{
258 fn default() -> Self {
261 Self::new(B::default(), None, None)
262 }
263}
264
265impl<B, D, E> http_body::Body for ReclaimableBody<B, D, E>
266where
267 B: http_body::Body<Data = Bytes> + Unpin,
268 B::Error: Into<BoxError>,
269{
270 type Data = Bytes;
271 type Error = BoxError;
272
273 fn poll_frame(
274 self: Pin<&mut Self>,
275 cx: &mut Context<'_>,
276 ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
277 let this = self.project();
278 let mut held = this.held.lock().unwrap_or_else(PoisonError::into_inner);
279 let Some(state) = held.as_mut() else {
280 return Poll::Ready(Some(Err(Box::new(Status::deadline_exceeded(
281 "response was not read in time",
282 )))));
283 };
284 if state.pending.is_empty() {
285 match ready!(Pin::new(&mut state.inner).poll_frame(cx)) {
286 None => {
287 if let Some(timer) = this.timer.take() {
289 timer.abort();
290 }
291 return Poll::Ready(None);
292 }
293 Some(Err(error)) => return Poll::Ready(Some(Err(error.into()))),
294 Some(Ok(frame)) => match frame.into_data() {
295 Ok(data) if this.timer.is_none() => {
297 return Poll::Ready(Some(Ok(Frame::data(data))));
298 }
299 Ok(data) => state.pending = data,
300 Err(frame) => {
301 if let Some(timer) = this.timer.take() {
302 timer.abort();
303 }
304 return Poll::Ready(Some(Ok(frame)));
305 }
306 },
307 }
308 }
309 let piece = state
310 .pending
311 .split_to(state.pending.len().min(MAX_FRAME_BYTES));
312 Poll::Ready(Some(Ok(Frame::data(Bytes::copy_from_slice(&piece)))))
315 }
316
317 fn is_end_stream(&self) -> bool {
318 let held = self.held.lock().unwrap_or_else(PoisonError::into_inner);
319 held.as_ref()
320 .is_some_and(|state| state.pending.is_empty() && state.inner.is_end_stream())
321 }
322
323 fn size_hint(&self) -> http_body::SizeHint {
324 let held = self.held.lock().unwrap_or_else(PoisonError::into_inner);
325 match held.as_ref() {
326 Some(state) => {
327 let pending = state.pending.len() as u64;
328 let mut hint = state.inner.size_hint();
329 if let Some(upper) = hint.upper() {
331 hint.set_upper(upper.saturating_add(pending));
332 }
333 hint.set_lower(hint.lower().saturating_add(pending));
334 hint
335 }
336 None => http_body::SizeHint::default(),
337 }
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use std::sync::atomic::{AtomicBool, Ordering};
344
345 use http_body::Body as _;
346 use tower::ServiceExt;
347
348 use super::*;
349
350 #[derive(Clone)]
353 struct BlockingService {
354 release: Arc<tokio::sync::Notify>,
355 }
356
357 impl Service<Request<Body>> for BlockingService {
358 type Response = Response<Body>;
359 type Error = Infallible;
360 type Future = BoxFuture<'static, Result<Response<Body>, Infallible>>;
361
362 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
363 Poll::Ready(Ok(()))
364 }
365
366 fn call(&mut self, _request: Request<Body>) -> Self::Future {
367 let release = self.release.clone();
368 Box::pin(async move {
369 release.notified().await;
370 Ok(Response::new(Body::default()))
371 })
372 }
373 }
374
375 fn request() -> Request<Body> {
376 Request::new(Body::default())
377 }
378
379 #[tokio::test]
380 async fn load_shedding_rejects_requests_over_the_limit() {
381 let release = Arc::new(tokio::sync::Notify::new());
382 let service = ServiceConcurrencyLimit::new(
383 BlockingService {
384 release: release.clone(),
385 },
386 NonZeroUsize::MIN,
387 true,
388 );
389
390 let in_flight = tokio::spawn(service.clone().oneshot(request()));
391 tokio::task::yield_now().await;
392
393 let shed = service.clone().oneshot(request()).await.unwrap();
394 assert_eq!(
395 shed.headers().get("grpc-status").unwrap(),
396 &(tonic::Code::ResourceExhausted as i32).to_string()
397 );
398
399 release.notify_one();
400 let response = in_flight.await.unwrap().unwrap();
401 assert!(response.headers().get("grpc-status").is_none());
402 }
403
404 #[tokio::test]
405 async fn without_load_shedding_requests_over_the_limit_wait() {
406 let release = Arc::new(tokio::sync::Notify::new());
407 let service = ServiceConcurrencyLimit::new(
408 BlockingService {
409 release: release.clone(),
410 },
411 NonZeroUsize::MIN,
412 false,
413 );
414
415 let first = tokio::spawn(service.clone().oneshot(request()));
416 tokio::task::yield_now().await;
417
418 let mut second = tokio::spawn(service.clone().oneshot(request()));
419 let waiting = tokio::time::timeout(Duration::from_millis(50), &mut second).await;
420 assert!(waiting.is_err(), "second request should wait for a slot");
421
422 release.notify_one();
423 first.await.unwrap().unwrap();
424 release.notify_one();
425 second.await.unwrap().unwrap();
426 }
427
428 #[tokio::test]
429 async fn limits_are_independent_per_service() {
430 let release = Arc::new(tokio::sync::Notify::new());
431 let blocking = BlockingService {
432 release: release.clone(),
433 };
434 let saturated = ServiceConcurrencyLimit::new(blocking.clone(), NonZeroUsize::MIN, true);
435 let other = ServiceConcurrencyLimit::new(blocking, NonZeroUsize::MIN, true);
436
437 let in_flight = tokio::spawn(saturated.clone().oneshot(request()));
438 tokio::task::yield_now().await;
439
440 let admitted = tokio::spawn(other.oneshot(request()));
442 tokio::task::yield_now().await;
443
444 release.notify_waiters();
445 assert!(
446 in_flight
447 .await
448 .unwrap()
449 .unwrap()
450 .headers()
451 .get("grpc-status")
452 .is_none()
453 );
454 assert!(
455 admitted
456 .await
457 .unwrap()
458 .unwrap()
459 .headers()
460 .get("grpc-status")
461 .is_none()
462 );
463 }
464
465 struct DropFlag(Arc<AtomicBool>);
467
468 impl Drop for DropFlag {
469 fn drop(&mut self) {
470 self.0.store(true, Ordering::SeqCst);
471 }
472 }
473
474 struct PendingBody;
477
478 impl http_body::Body for PendingBody {
479 type Data = bytes::Bytes;
480 type Error = Infallible;
481
482 fn poll_frame(
483 self: Pin<&mut Self>,
484 _cx: &mut Context<'_>,
485 ) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
486 Poll::Pending
487 }
488 }
489
490 #[tokio::test]
491 async fn the_guard_survives_the_end_of_the_stream() {
492 let dropped = Arc::new(AtomicBool::new(false));
493 let mut body = PermitGuardedBody::new(Body::default(), Some(DropFlag(dropped.clone())));
494
495 let end = std::future::poll_fn(|cx| Pin::new(&mut body).poll_frame(cx)).await;
496 assert!(end.is_none());
497 assert!(!dropped.load(Ordering::SeqCst));
498
499 drop(body);
500 assert!(dropped.load(Ordering::SeqCst));
501 }
502
503 #[tokio::test]
504 async fn dropping_an_unfinished_body_releases_the_guard() {
505 let dropped = Arc::new(AtomicBool::new(false));
506 let mut body = PermitGuardedBody::new(PendingBody, Some(DropFlag(dropped.clone())));
507
508 let polled = tokio::time::timeout(
509 Duration::from_millis(10),
510 std::future::poll_fn(|cx| Pin::new(&mut body).poll_frame(cx)),
511 )
512 .await;
513 assert!(polled.is_err());
514
515 drop(body);
516 assert!(dropped.load(Ordering::SeqCst));
517 }
518
519 const DEADLINE: Duration = Duration::from_millis(30);
520
521 fn flag() -> (Arc<AtomicBool>, DropFlag) {
522 let raised = Arc::new(AtomicBool::new(false));
523 (raised.clone(), DropFlag(raised))
524 }
525
526 fn report() -> (Arc<AtomicBool>, impl FnOnce() + Send + 'static) {
528 let reported = Arc::new(AtomicBool::new(false));
529 let raise = reported.clone();
530 (reported, move || raise.store(true, Ordering::SeqCst))
531 }
532
533 async fn next_frame<B: http_body::Body + Unpin>(
535 body: &mut B,
536 ) -> Option<Result<Frame<B::Data>, B::Error>> {
537 std::future::poll_fn(|cx| Pin::new(&mut *body).poll_frame(cx)).await
538 }
539
540 #[tokio::test]
541 async fn reclaimable_body_hands_over_frames_of_bounded_size() {
542 let inner = http_body_util::Full::new(Bytes::from(vec![7u8; MAX_FRAME_BYTES * 2 + 5]));
543 let mut body = ReclaimableBody::<_, DropFlag, DropFlag>::with_deadline(
544 inner,
545 None,
546 None,
547 Duration::from_secs(3600),
548 || {},
549 );
550
551 let mut sizes = Vec::new();
552 let mut remaining = vec![body.size_hint().exact()];
553 while let Some(frame) = next_frame(&mut body).await {
554 sizes.push(frame.unwrap().into_data().unwrap().len());
555 remaining.push(body.size_hint().exact());
556 }
557 assert_eq!(sizes, [MAX_FRAME_BYTES, MAX_FRAME_BYTES, 5]);
558 let frame = MAX_FRAME_BYTES as u64;
559 assert_eq!(
560 remaining,
561 [Some(2 * frame + 5), Some(frame + 5), Some(5), Some(0)]
562 );
563 }
564
565 #[tokio::test]
566 async fn the_deadline_reclaims_the_body_and_keeps_the_end_guard() {
567 let (reclaimed_deadline, until_deadline) = flag();
568 let (reclaimed_end, until_end) = flag();
569 let (reported, on_reclaim) = report();
570 let mut body = ReclaimableBody::with_deadline(
571 PendingBody,
572 Some(until_deadline),
573 Some(until_end),
574 DEADLINE,
575 on_reclaim,
576 );
577
578 tokio::time::sleep(DEADLINE * 3).await;
579 assert!(reclaimed_deadline.load(Ordering::SeqCst));
580 assert!(reported.load(Ordering::SeqCst));
581 assert!(!reclaimed_end.load(Ordering::SeqCst));
582 let error = next_frame(&mut body).await.unwrap().unwrap_err();
584 assert_eq!(
585 Status::from_error(error).code(),
586 tonic::Code::DeadlineExceeded
587 );
588
589 drop(body);
590 assert!(reclaimed_end.load(Ordering::SeqCst));
591 }
592
593 #[tokio::test]
594 async fn a_body_read_to_the_end_is_never_reclaimed() {
595 let (reclaimed_deadline, until_deadline) = flag();
596 let (reported, on_reclaim) = report();
597 let mut body = ReclaimableBody::<_, _, DropFlag>::with_deadline(
598 Body::default(),
599 Some(until_deadline),
600 None,
601 DEADLINE,
602 on_reclaim,
603 );
604
605 assert!(next_frame(&mut body).await.is_none());
606 tokio::time::sleep(DEADLINE * 3).await;
607 assert!(!reclaimed_deadline.load(Ordering::SeqCst));
608 assert!(!reported.load(Ordering::SeqCst));
609
610 drop(body);
611 assert!(reclaimed_deadline.load(Ordering::SeqCst));
612 }
613
614 #[tokio::test]
615 async fn dropping_a_reclaimable_body_stops_its_deadline() {
616 let (reported, on_reclaim) = report();
617 let body = ReclaimableBody::<_, DropFlag, DropFlag>::with_deadline(
618 PendingBody,
619 None,
620 None,
621 DEADLINE,
622 on_reclaim,
623 );
624
625 drop(body);
626 tokio::time::sleep(DEADLINE * 3).await;
627 assert!(!reported.load(Ordering::SeqCst));
628 }
629
630 #[tokio::test]
631 async fn the_deadline_frees_the_buffer_behind_a_piece_already_handed_over() {
632 let response = Bytes::from(vec![7u8; MAX_FRAME_BYTES * 2]);
633 let mut body = ReclaimableBody::<_, DropFlag, DropFlag>::with_deadline(
634 http_body_util::Full::new(response.clone()),
635 None,
636 None,
637 DEADLINE,
638 || {},
639 );
640 let piece = next_frame(&mut body)
642 .await
643 .unwrap()
644 .unwrap()
645 .into_data()
646 .unwrap();
647
648 tokio::time::sleep(DEADLINE * 3).await;
649 assert!(
650 response.is_unique(),
651 "the handed-over piece must not keep the response buffer alive"
652 );
653 drop((piece, body));
654 }
655
656 #[tokio::test]
657 async fn a_body_without_a_deadline_hands_over_frames_whole() {
658 let chunk = Bytes::from(vec![7u8; MAX_FRAME_BYTES * 2 + 5]);
659 let mut body = ReclaimableBody::<_, DropFlag, DropFlag>::new(
660 http_body_util::Full::new(chunk.clone()),
661 None,
662 None,
663 );
664
665 let frame = next_frame(&mut body)
666 .await
667 .unwrap()
668 .unwrap()
669 .into_data()
670 .unwrap();
671 assert_eq!(frame.len(), chunk.len());
672 assert_eq!(
673 frame.as_ptr(),
674 chunk.as_ptr(),
675 "the chunk must not be copied"
676 );
677 assert!(next_frame(&mut body).await.is_none());
678 }
679}