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