Skip to content

Commit 42a7435

Browse files
authored
concurrency-limit: Drop permit on readiness (#751)
The concurrency-limit module holds permits until the response future is dropped. This is fine in theory, but we have reports of clients hitting the concurrency limit unexpectedly. This change ensures that the permit is released immediately as soon as the inner future becomes ready, regardless of how long the response future is held. This also adds trace logging when the permit is released.
1 parent f0da619 commit 42a7435

File tree

1 file changed

+22
-26
lines changed
  • linkerd/concurrency-limit/src

1 file changed

+22
-26
lines changed

linkerd/concurrency-limit/src/lib.rs

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@
77
#![deny(warnings, rust_2018_idioms)]
88

99
use pin_project::pin_project;
10-
use std::future::Future;
11-
use std::pin::Pin;
12-
use std::sync::Arc;
13-
use std::task::{Context, Poll};
14-
use std::{fmt, mem};
10+
use std::{
11+
fmt,
12+
future::Future,
13+
mem,
14+
pin::Pin,
15+
sync::Arc,
16+
task::{Context, Poll},
17+
};
1518
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
1619
use tower::Service;
1720
use tracing::trace;
@@ -41,8 +44,8 @@ enum State {
4144
pub struct ResponseFuture<T> {
4245
#[pin]
4346
inner: T,
44-
// We only keep this around so that it is dropped when the future completes.
45-
_permit: OwnedSemaphorePermit,
47+
// The permit is held until the future becomes ready.
48+
permit: Option<OwnedSemaphorePermit>,
4649
}
4750

4851
impl From<Arc<Semaphore>> for Layer {
@@ -74,21 +77,6 @@ impl<T> ConcurrencyLimit<T> {
7477
state: State::Empty,
7578
}
7679
}
77-
78-
/// Get a reference to the inner service
79-
pub fn get_ref(&self) -> &T {
80-
&self.inner
81-
}
82-
83-
/// Get a mutable reference to the inner service
84-
pub fn get_mut(&mut self) -> &mut T {
85-
&mut self.inner
86-
}
87-
88-
/// Consume `self`, returning the inner service
89-
pub fn into_inner(self) -> T {
90-
self.inner
91-
}
9280
}
9381

9482
impl<S, Request> Service<Request> for ConcurrencyLimit<S>
@@ -119,17 +107,17 @@ where
119107

120108
fn call(&mut self, request: Request) -> Self::Future {
121109
// Make sure a permit has been acquired
122-
let _permit = match mem::replace(&mut self.state, State::Empty) {
110+
let permit = match mem::replace(&mut self.state, State::Empty) {
123111
// Take the permit.
124-
State::Ready(permit) => permit,
112+
State::Ready(permit) => Some(permit),
125113
// whoopsie!
126114
_ => panic!("max requests in-flight; poll_ready must be called first"),
127115
};
128116

129117
// Call the inner service
130118
let inner = self.inner.call(request);
131119

132-
ResponseFuture { inner, _permit }
120+
ResponseFuture { inner, permit }
133121
}
134122
}
135123

@@ -153,7 +141,15 @@ where
153141
type Output = T::Output;
154142

155143
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
156-
self.project().inner.poll(cx)
144+
let this = self.project();
145+
let res = futures::ready!(this.inner.poll(cx));
146+
let released = this.permit.take().is_some();
147+
debug_assert!(
148+
released,
149+
"Permit must be released when the future completes"
150+
);
151+
trace!("permit released");
152+
Poll::Ready(res)
157153
}
158154
}
159155

0 commit comments

Comments
 (0)