1use futures_core::stream::{Stream, TryStream};
2use loadpace::{
3 DispatchReservation, DispatchState, EndpointConfig, EndpointController, InFlightRequest,
4 Outcome,
5};
6use rand::Rng;
7use std::future::Future;
8use std::marker::PhantomData;
9use std::pin::Pin;
10use std::sync::{Arc, Mutex};
11use std::task::{Context, Poll};
12use std::time::Instant;
13use tower::Service;
14use tower::discover::Change;
15use tower::load::Load;
16
17#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
22pub struct LoadMetric(pub f64);
23
24impl LoadMetric {
25 pub fn as_secs(self) -> f64 {
26 self.0
27 }
28}
29
30struct Shared<S> {
31 inner: tokio::sync::Mutex<S>,
32 controller: Mutex<EndpointController>,
33 ready: Arc<tokio::sync::Semaphore>,
34 dispatch: tokio::sync::Notify,
35}
36
37type ReadinessFuture = Pin<
38 Box<
39 dyn Future<Output = Result<tokio::sync::OwnedSemaphorePermit, tokio::sync::AcquireError>>
40 + Send
41 + 'static,
42 >,
43>;
44
45fn now() -> Instant {
49 tokio::time::Instant::now().into_std()
50}
51
52pub struct AdaptiveEndpoint<S> {
60 shared: Arc<Shared<S>>,
61 readiness_permit: Option<tokio::sync::OwnedSemaphorePermit>,
62 readiness: Mutex<Option<ReadinessFuture>>,
63}
64
65impl<S> AdaptiveEndpoint<S> {
66 pub fn new(inner: S, config: EndpointConfig) -> Self {
67 Self::new_at(inner, config, now())
68 }
69
70 pub fn new_at(inner: S, config: EndpointConfig, now: Instant) -> Self {
71 let queue_capacity = config.queue_capacity;
72 Self {
73 shared: Arc::new(Shared {
74 inner: tokio::sync::Mutex::new(inner),
75 controller: Mutex::new(EndpointController::new(config, now)),
76 ready: Arc::new(tokio::sync::Semaphore::new(queue_capacity)),
77 dispatch: tokio::sync::Notify::new(),
78 }),
79 readiness_permit: None,
80 readiness: Mutex::new(None),
81 }
82 }
83
84 fn with_controller<T>(&self, operation: impl FnOnce(&mut EndpointController) -> T) -> T {
85 let (result, changed) = {
86 let mut controller = self
87 .shared
88 .controller
89 .lock()
90 .expect("controller mutex poisoned");
91 let before = controller.probe().current();
92 let result = operation(&mut controller);
93 (result, before != controller.probe().current())
94 };
95 if changed {
96 self.shared.dispatch.notify_waiters();
97 }
98 result
99 }
100
101 pub fn snapshot(&self) -> loadpace::ControllerSnapshot {
102 self.with_controller(|controller| controller.snapshot(now()))
103 }
104
105 pub fn start_positive_probe(&self, delta: f64, until: Instant) {
106 self.with_controller(|controller| {
107 controller.start_positive_probe(delta, until, now());
108 });
109 }
110
111 pub fn start_negative_probe(&self, factor: f64, until: Instant) {
112 self.with_controller(|controller| {
113 controller.start_negative_probe(factor, until, now());
114 });
115 }
116
117 pub fn maybe_start_probe<R: Rng + ?Sized>(
122 &self,
123 schedule: &loadpace::ProbeSchedule,
124 rng: &mut R,
125 ) -> Option<loadpace::Probe> {
126 self.with_controller(|controller| controller.maybe_start_probe(schedule, rng, now()))
127 }
128
129 pub fn load_metric(&self) -> LoadMetric {
130 self.with_controller(|controller| {
131 let current = now();
132 controller.refresh(current);
133 LoadMetric(controller.load(current))
134 })
135 }
136}
137
138pub struct AdaptiveDiscovery<D, Request> {
145 inner: D,
146 config: EndpointConfig,
147 _request: PhantomData<fn() -> Request>,
148}
149
150impl<D, Request> AdaptiveDiscovery<D, Request> {
151 pub fn new(inner: D, config: EndpointConfig) -> Self {
152 Self {
153 inner,
154 config,
155 _request: PhantomData,
156 }
157 }
158
159 pub fn into_inner(self) -> D {
160 self.inner
161 }
162}
163
164impl<D, Request, K, S> Stream for AdaptiveDiscovery<D, Request>
165where
166 D: TryStream<Ok = Change<K, S>> + Unpin,
167 K: Eq,
168 S: Service<Request> + Send + 'static,
169 S::Future: Send + 'static,
170 S::Response: Send + 'static,
171 S::Error: Send + 'static,
172 Request: 'static,
173{
174 type Item = Result<Change<K, AdaptiveEndpoint<S>>, D::Error>;
175
176 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
177 let this = self.get_mut();
178 Pin::new(&mut this.inner).try_poll_next(cx).map(|change| {
179 change.map(|result| {
180 result.map(|change| match change {
181 Change::Insert(key, service) => {
182 Change::Insert(key, AdaptiveEndpoint::new(service, this.config.clone()))
183 }
184 Change::Remove(key) => Change::Remove(key),
185 })
186 })
187 })
188 }
189}
190
191impl<S> Clone for AdaptiveEndpoint<S> {
192 fn clone(&self) -> Self {
193 Self {
194 shared: Arc::clone(&self.shared),
195 readiness_permit: None,
196 readiness: Mutex::new(None),
197 }
198 }
199}
200
201impl<S> Load for AdaptiveEndpoint<S> {
202 type Metric = LoadMetric;
203
204 fn load(&self) -> Self::Metric {
205 self.load_metric()
206 }
207}
208
209impl<S, Request> Service<Request> for AdaptiveEndpoint<S>
210where
211 S: Service<Request> + Send + 'static,
212 S::Future: Send + 'static,
213 S::Response: Send + 'static,
214 S::Error: Send + 'static,
215 Request: Send + 'static,
216{
217 type Response = S::Response;
218 type Error = S::Error;
219 type Future = ResponseFuture<S::Response, S::Error>;
220
221 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
222 if self.readiness_permit.is_some() {
223 return Poll::Ready(Ok(()));
224 }
225
226 let mut readiness = self
227 .readiness
228 .lock()
229 .expect("readiness future mutex poisoned");
230 if readiness.is_none() {
231 match Arc::clone(&self.shared.ready).try_acquire_owned() {
232 Ok(permit) => {
233 drop(readiness);
234 self.readiness_permit = Some(permit);
235 return Poll::Ready(Ok(()));
236 }
237 Err(tokio::sync::TryAcquireError::NoPermits) => {
238 *readiness = Some(Box::pin(Arc::clone(&self.shared.ready).acquire_owned()));
239 }
240 Err(tokio::sync::TryAcquireError::Closed) => {
241 panic!("AdaptiveEndpoint readiness semaphore was closed")
242 }
243 }
244 }
245
246 match readiness
247 .as_mut()
248 .expect("readiness future must exist")
249 .as_mut()
250 .poll(cx)
251 {
252 Poll::Ready(Ok(permit)) => {
253 *readiness = None;
254 self.readiness_permit = Some(permit);
255 Poll::Ready(Ok(()))
256 }
257 Poll::Ready(Err(_)) => {
258 panic!("AdaptiveEndpoint readiness semaphore was closed")
259 }
260 Poll::Pending => Poll::Pending,
261 }
262 }
263
264 fn call(&mut self, request: Request) -> Self::Future {
265 assert!(
266 self.readiness_permit.is_some(),
267 "AdaptiveEndpoint::call invoked without available readiness"
268 );
269 let readiness_permit = self
270 .readiness_permit
271 .take()
272 .expect("readiness permit must exist after poll_ready");
273 let reservation = self
274 .shared
275 .controller
276 .lock()
277 .expect("controller mutex poisoned")
278 .reserve(now())
279 .expect("readiness reservation was not reflected in controller capacity");
280
281 let guard = RequestGuard::new(Arc::clone(&self.shared), reservation, readiness_permit);
282 let future = dispatch_request(Arc::clone(&self.shared), request, guard);
283 ResponseFuture {
284 inner: Box::pin(future),
285 }
286 }
287}
288
289pub struct ResponseFuture<T, E> {
291 inner: Pin<Box<dyn Future<Output = Result<T, E>> + Send + 'static>>,
292}
293
294impl<T, E> Future for ResponseFuture<T, E> {
295 type Output = Result<T, E>;
296
297 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
298 self.get_mut().inner.as_mut().poll(cx)
299 }
300}
301
302struct RequestGuard<S> {
303 shared: Arc<Shared<S>>,
304 reservation: Option<DispatchReservation>,
305 readiness_permit: Option<tokio::sync::OwnedSemaphorePermit>,
306 active: Option<InFlightRequest>,
307 finished: bool,
308}
309
310impl<S> RequestGuard<S> {
311 fn new(
312 shared: Arc<Shared<S>>,
313 reservation: DispatchReservation,
314 readiness_permit: tokio::sync::OwnedSemaphorePermit,
315 ) -> Self {
316 Self {
317 shared,
318 reservation: Some(reservation),
319 readiness_permit: Some(readiness_permit),
320 active: None,
321 finished: false,
322 }
323 }
324
325 fn mark_dispatched(&mut self, active: InFlightRequest) {
326 self.reservation = None;
327 self.active = Some(active);
328 self.readiness_permit = None;
331 }
332
333 fn finish(&mut self, outcome: Outcome, now: Instant) {
334 if self.finished {
335 return;
336 }
337 self.finished = true;
338 if let Some(active) = self.active.take() {
339 let latency = now.saturating_duration_since(active.dispatched_at());
340 self.shared
341 .controller
342 .lock()
343 .expect("controller mutex poisoned")
344 .on_complete(active, outcome, latency, now);
345 } else if let Some(reservation) = self.reservation.take() {
346 self.shared
347 .controller
348 .lock()
349 .expect("controller mutex poisoned")
350 .cancel(reservation, now);
351 }
352 self.readiness_permit = None;
355 self.shared.dispatch.notify_waiters();
356 }
357}
358
359impl<S> Drop for RequestGuard<S> {
360 fn drop(&mut self) {
361 if !self.finished {
362 self.finish(Outcome::Failure, now());
363 }
364 }
365}
366
367async fn dispatch_request<S, Request>(
368 shared: Arc<Shared<S>>,
369 request: Request,
370 mut guard: RequestGuard<S>,
371) -> Result<S::Response, S::Error>
372where
373 S: Service<Request> + Send + 'static,
374 S::Future: Send + 'static,
375 S::Response: Send + 'static,
376 S::Error: Send + 'static,
377 Request: Send + 'static,
378{
379 let reservation = guard
380 .reservation
381 .expect("request guard must begin with a reservation");
382
383 loop {
384 let notified = shared.dispatch.notified();
385 let (decision, probe_until) = {
386 let mut controller = shared.controller.lock().expect("controller mutex poisoned");
387 let current = now();
388 controller.refresh(current);
389 (
390 controller.dispatch_state(reservation, current),
391 controller.probe().current().map(|probe| probe.until),
392 )
393 };
394
395 match decision {
396 DispatchState::Ready => {
397 break;
398 }
399 DispatchState::WaitUntil(deadline) => {
400 let wake_at = probe_until.map_or(deadline, |until| deadline.min(until));
401 let delay = wake_at.saturating_duration_since(now());
402 let _ = tokio::time::timeout(delay, notified).await;
403 }
404 DispatchState::WaitForPrevious | DispatchState::InflightLimit => {
405 notified.await;
406 }
407 DispatchState::Cancelled => {
408 panic!("an AdaptiveEndpoint request was cancelled while being polled");
409 }
410 }
411 }
412
413 let result = {
414 let mut inner = shared.inner.lock().await;
415 match std::future::poll_fn(|cx| inner.poll_ready(cx)).await {
416 Ok(()) => {
417 let now = now();
418 let active = shared
419 .controller
420 .lock()
421 .expect("controller mutex poisoned")
422 .on_dispatched(reservation, now)
423 .expect("dispatch state changed unexpectedly");
424 guard.mark_dispatched(active);
425 let future = inner.call(request);
426 drop(inner);
427 future.await
428 }
429 Err(error) => {
430 shared
431 .controller
432 .lock()
433 .expect("controller mutex poisoned")
434 .on_admission_failure(now());
435 Err(error)
436 }
437 }
438 };
439
440 let outcome = if result.is_ok() {
441 Outcome::Success
442 } else {
443 Outcome::Failure
444 };
445 let now = now();
446 guard.finish(outcome, now);
447 result
448}