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> http_body::Body for ReclaimableBody<B, D, E>
254where
255 B: http_body::Body<Data = Bytes> + Unpin,
256 B::Error: Into<BoxError>,
257{
258 type Data = Bytes;
259 type Error = BoxError;
260
261 fn poll_frame(
262 self: Pin<&mut Self>,
263 cx: &mut Context<'_>,
264 ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
265 let this = self.project();
266 let mut held = this.held.lock().unwrap_or_else(PoisonError::into_inner);
267 let Some(state) = held.as_mut() else {
268 return Poll::Ready(Some(Err(Box::new(Status::deadline_exceeded(
269 "response was not read in time",
270 )))));
271 };
272 if state.pending.is_empty() {
273 match ready!(Pin::new(&mut state.inner).poll_frame(cx)) {
274 None => {
275 if let Some(timer) = this.timer.take() {
277 timer.abort();
278 }
279 return Poll::Ready(None);
280 }
281 Some(Err(error)) => return Poll::Ready(Some(Err(error.into()))),
282 Some(Ok(frame)) => match frame.into_data() {
283 Ok(data) if this.timer.is_none() => {
285 return Poll::Ready(Some(Ok(Frame::data(data))));
286 }
287 Ok(data) => state.pending = data,
288 Err(frame) => {
289 if let Some(timer) = this.timer.take() {
290 timer.abort();
291 }
292 return Poll::Ready(Some(Ok(frame)));
293 }
294 },
295 }
296 }
297 let piece = state
298 .pending
299 .split_to(state.pending.len().min(MAX_FRAME_BYTES));
300 Poll::Ready(Some(Ok(Frame::data(Bytes::copy_from_slice(&piece)))))
303 }
304
305 fn is_end_stream(&self) -> bool {
306 let held = self.held.lock().unwrap_or_else(PoisonError::into_inner);
307 held.as_ref()
308 .is_some_and(|state| state.pending.is_empty() && state.inner.is_end_stream())
309 }
310
311 fn size_hint(&self) -> http_body::SizeHint {
312 let held = self.held.lock().unwrap_or_else(PoisonError::into_inner);
313 match held.as_ref() {
314 Some(state) => {
315 let pending = state.pending.len() as u64;
316 let mut hint = state.inner.size_hint();
317 if let Some(upper) = hint.upper() {
319 hint.set_upper(upper.saturating_add(pending));
320 }
321 hint.set_lower(hint.lower().saturating_add(pending));
322 hint
323 }
324 None => http_body::SizeHint::default(),
325 }
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use std::sync::atomic::{AtomicBool, Ordering};
332
333 use http_body::Body as _;
334 use tower::ServiceExt;
335
336 use super::*;
337
338 #[derive(Clone)]
341 struct BlockingService {
342 release: Arc<tokio::sync::Notify>,
343 }
344
345 impl Service<Request<Body>> for BlockingService {
346 type Response = Response<Body>;
347 type Error = Infallible;
348 type Future = BoxFuture<'static, Result<Response<Body>, Infallible>>;
349
350 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
351 Poll::Ready(Ok(()))
352 }
353
354 fn call(&mut self, _request: Request<Body>) -> Self::Future {
355 let release = self.release.clone();
356 Box::pin(async move {
357 release.notified().await;
358 Ok(Response::new(Body::default()))
359 })
360 }
361 }
362
363 fn request() -> Request<Body> {
364 Request::new(Body::default())
365 }
366
367 #[tokio::test]
368 async fn load_shedding_rejects_requests_over_the_limit() {
369 let release = Arc::new(tokio::sync::Notify::new());
370 let service = ServiceConcurrencyLimit::new(
371 BlockingService {
372 release: release.clone(),
373 },
374 NonZeroUsize::MIN,
375 true,
376 );
377
378 let in_flight = tokio::spawn(service.clone().oneshot(request()));
379 tokio::task::yield_now().await;
380
381 let shed = service.clone().oneshot(request()).await.unwrap();
382 assert_eq!(
383 shed.headers().get("grpc-status").unwrap(),
384 &(tonic::Code::ResourceExhausted as i32).to_string()
385 );
386
387 release.notify_one();
388 let response = in_flight.await.unwrap().unwrap();
389 assert!(response.headers().get("grpc-status").is_none());
390 }
391
392 #[tokio::test]
393 async fn without_load_shedding_requests_over_the_limit_wait() {
394 let release = Arc::new(tokio::sync::Notify::new());
395 let service = ServiceConcurrencyLimit::new(
396 BlockingService {
397 release: release.clone(),
398 },
399 NonZeroUsize::MIN,
400 false,
401 );
402
403 let first = tokio::spawn(service.clone().oneshot(request()));
404 tokio::task::yield_now().await;
405
406 let mut second = tokio::spawn(service.clone().oneshot(request()));
407 let waiting = tokio::time::timeout(Duration::from_millis(50), &mut second).await;
408 assert!(waiting.is_err(), "second request should wait for a slot");
409
410 release.notify_one();
411 first.await.unwrap().unwrap();
412 release.notify_one();
413 second.await.unwrap().unwrap();
414 }
415
416 #[tokio::test]
417 async fn limits_are_independent_per_service() {
418 let release = Arc::new(tokio::sync::Notify::new());
419 let blocking = BlockingService {
420 release: release.clone(),
421 };
422 let saturated = ServiceConcurrencyLimit::new(blocking.clone(), NonZeroUsize::MIN, true);
423 let other = ServiceConcurrencyLimit::new(blocking, NonZeroUsize::MIN, true);
424
425 let in_flight = tokio::spawn(saturated.clone().oneshot(request()));
426 tokio::task::yield_now().await;
427
428 let admitted = tokio::spawn(other.oneshot(request()));
430 tokio::task::yield_now().await;
431
432 release.notify_waiters();
433 assert!(
434 in_flight
435 .await
436 .unwrap()
437 .unwrap()
438 .headers()
439 .get("grpc-status")
440 .is_none()
441 );
442 assert!(
443 admitted
444 .await
445 .unwrap()
446 .unwrap()
447 .headers()
448 .get("grpc-status")
449 .is_none()
450 );
451 }
452
453 struct DropFlag(Arc<AtomicBool>);
455
456 impl Drop for DropFlag {
457 fn drop(&mut self) {
458 self.0.store(true, Ordering::SeqCst);
459 }
460 }
461
462 struct PendingBody;
465
466 impl http_body::Body for PendingBody {
467 type Data = bytes::Bytes;
468 type Error = Infallible;
469
470 fn poll_frame(
471 self: Pin<&mut Self>,
472 _cx: &mut Context<'_>,
473 ) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
474 Poll::Pending
475 }
476 }
477
478 #[tokio::test]
479 async fn the_guard_survives_the_end_of_the_stream() {
480 let dropped = Arc::new(AtomicBool::new(false));
481 let mut body = PermitGuardedBody::new(Body::default(), Some(DropFlag(dropped.clone())));
482
483 let end = std::future::poll_fn(|cx| Pin::new(&mut body).poll_frame(cx)).await;
484 assert!(end.is_none());
485 assert!(!dropped.load(Ordering::SeqCst));
486
487 drop(body);
488 assert!(dropped.load(Ordering::SeqCst));
489 }
490
491 #[tokio::test]
492 async fn dropping_an_unfinished_body_releases_the_guard() {
493 let dropped = Arc::new(AtomicBool::new(false));
494 let mut body = PermitGuardedBody::new(PendingBody, Some(DropFlag(dropped.clone())));
495
496 let polled = tokio::time::timeout(
497 Duration::from_millis(10),
498 std::future::poll_fn(|cx| Pin::new(&mut body).poll_frame(cx)),
499 )
500 .await;
501 assert!(polled.is_err());
502
503 drop(body);
504 assert!(dropped.load(Ordering::SeqCst));
505 }
506
507 const DEADLINE: Duration = Duration::from_millis(30);
508
509 fn flag() -> (Arc<AtomicBool>, DropFlag) {
510 let raised = Arc::new(AtomicBool::new(false));
511 (raised.clone(), DropFlag(raised))
512 }
513
514 fn report() -> (Arc<AtomicBool>, impl FnOnce() + Send + 'static) {
516 let reported = Arc::new(AtomicBool::new(false));
517 let raise = reported.clone();
518 (reported, move || raise.store(true, Ordering::SeqCst))
519 }
520
521 async fn next_frame<B: http_body::Body + Unpin>(
523 body: &mut B,
524 ) -> Option<Result<Frame<B::Data>, B::Error>> {
525 std::future::poll_fn(|cx| Pin::new(&mut *body).poll_frame(cx)).await
526 }
527
528 #[tokio::test]
529 async fn reclaimable_body_hands_over_frames_of_bounded_size() {
530 let inner = http_body_util::Full::new(Bytes::from(vec![7u8; MAX_FRAME_BYTES * 2 + 5]));
531 let mut body = ReclaimableBody::<_, DropFlag, DropFlag>::with_deadline(
532 inner,
533 None,
534 None,
535 Duration::from_secs(3600),
536 || {},
537 );
538
539 let mut sizes = Vec::new();
540 let mut remaining = vec![body.size_hint().exact()];
541 while let Some(frame) = next_frame(&mut body).await {
542 sizes.push(frame.unwrap().into_data().unwrap().len());
543 remaining.push(body.size_hint().exact());
544 }
545 assert_eq!(sizes, [MAX_FRAME_BYTES, MAX_FRAME_BYTES, 5]);
546 let frame = MAX_FRAME_BYTES as u64;
547 assert_eq!(
548 remaining,
549 [Some(2 * frame + 5), Some(frame + 5), Some(5), Some(0)]
550 );
551 }
552
553 #[tokio::test]
554 async fn the_deadline_reclaims_the_body_and_keeps_the_end_guard() {
555 let (reclaimed_deadline, until_deadline) = flag();
556 let (reclaimed_end, until_end) = flag();
557 let (reported, on_reclaim) = report();
558 let mut body = ReclaimableBody::with_deadline(
559 PendingBody,
560 Some(until_deadline),
561 Some(until_end),
562 DEADLINE,
563 on_reclaim,
564 );
565
566 tokio::time::sleep(DEADLINE * 3).await;
567 assert!(reclaimed_deadline.load(Ordering::SeqCst));
568 assert!(reported.load(Ordering::SeqCst));
569 assert!(!reclaimed_end.load(Ordering::SeqCst));
570 let error = next_frame(&mut body).await.unwrap().unwrap_err();
572 assert_eq!(
573 Status::from_error(error).code(),
574 tonic::Code::DeadlineExceeded
575 );
576
577 drop(body);
578 assert!(reclaimed_end.load(Ordering::SeqCst));
579 }
580
581 #[tokio::test]
582 async fn a_body_read_to_the_end_is_never_reclaimed() {
583 let (reclaimed_deadline, until_deadline) = flag();
584 let (reported, on_reclaim) = report();
585 let mut body = ReclaimableBody::<_, _, DropFlag>::with_deadline(
586 Body::default(),
587 Some(until_deadline),
588 None,
589 DEADLINE,
590 on_reclaim,
591 );
592
593 assert!(next_frame(&mut body).await.is_none());
594 tokio::time::sleep(DEADLINE * 3).await;
595 assert!(!reclaimed_deadline.load(Ordering::SeqCst));
596 assert!(!reported.load(Ordering::SeqCst));
597
598 drop(body);
599 assert!(reclaimed_deadline.load(Ordering::SeqCst));
600 }
601
602 #[tokio::test]
603 async fn dropping_a_reclaimable_body_stops_its_deadline() {
604 let (reported, on_reclaim) = report();
605 let body = ReclaimableBody::<_, DropFlag, DropFlag>::with_deadline(
606 PendingBody,
607 None,
608 None,
609 DEADLINE,
610 on_reclaim,
611 );
612
613 drop(body);
614 tokio::time::sleep(DEADLINE * 3).await;
615 assert!(!reported.load(Ordering::SeqCst));
616 }
617
618 #[tokio::test]
619 async fn the_deadline_frees_the_buffer_behind_a_piece_already_handed_over() {
620 let response = Bytes::from(vec![7u8; MAX_FRAME_BYTES * 2]);
621 let mut body = ReclaimableBody::<_, DropFlag, DropFlag>::with_deadline(
622 http_body_util::Full::new(response.clone()),
623 None,
624 None,
625 DEADLINE,
626 || {},
627 );
628 let piece = next_frame(&mut body)
630 .await
631 .unwrap()
632 .unwrap()
633 .into_data()
634 .unwrap();
635
636 tokio::time::sleep(DEADLINE * 3).await;
637 assert!(
638 response.is_unique(),
639 "the handed-over piece must not keep the response buffer alive"
640 );
641 drop((piece, body));
642 }
643
644 #[tokio::test]
645 async fn a_body_without_a_deadline_hands_over_frames_whole() {
646 let chunk = Bytes::from(vec![7u8; MAX_FRAME_BYTES * 2 + 5]);
647 let mut body = ReclaimableBody::<_, DropFlag, DropFlag>::new(
648 http_body_util::Full::new(chunk.clone()),
649 None,
650 None,
651 );
652
653 let frame = next_frame(&mut body)
654 .await
655 .unwrap()
656 .unwrap()
657 .into_data()
658 .unwrap();
659 assert_eq!(frame.len(), chunk.len());
660 assert_eq!(
661 frame.as_ptr(),
662 chunk.as_ptr(),
663 "the chunk must not be copied"
664 );
665 assert!(next_frame(&mut body).await.is_none());
666 }
667}