1use futures_core::stream::{Stream, TryStream};
2use loadpace::{
3 DispatchReservation, DispatchState, EndpointConfig, EndpointController, InFlightRequest,
4 Outcome,
5};
6use pin_project_lite::pin_project;
7use std::future::Future;
8use std::pin::Pin;
9use std::sync::{Arc, Mutex, MutexGuard};
10use std::task::{Context, Poll, Waker};
11use std::time::Instant;
12use tower::discover::Change;
13use tower::load::Load;
14use tower::{Layer, Service};
15
16#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
21pub struct LoadMetric(pub f64);
22
23impl LoadMetric {
24 pub fn as_secs(self) -> f64 {
26 self.0
27 }
28}
29
30struct Shared<S> {
31 service: tokio::sync::Mutex<S>,
32 controller: Mutex<ControllerState>,
33 dispatch: tokio::sync::Notify,
34}
35
36struct ControllerState {
37 controller: EndpointController,
38 admission_waker: Option<Waker>,
39}
40
41impl ControllerState {
42 fn poll_admission(&mut self, cx: &mut Context<'_>) -> Poll<()> {
43 if self.controller.may_schedule() {
44 self.admission_waker = None;
45 return Poll::Ready(());
46 }
47
48 if self
49 .admission_waker
50 .as_ref()
51 .is_none_or(|waker| !waker.will_wake(cx.waker()))
52 {
53 self.admission_waker = Some(cx.waker().clone());
54 }
55 Poll::Pending
56 }
57
58 fn take_admission_waker(&mut self) -> Option<Waker> {
59 self.admission_waker.take()
60 }
61}
62
63fn now() -> Instant {
67 tokio::time::Instant::now().into_std()
68}
69
70fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
71 mutex
72 .lock()
73 .expect("loadpace-tower internal mutex was poisoned")
74}
75
76pub struct AdaptiveEndpoint<S> {
89 shared: Arc<Shared<S>>,
90}
91
92impl<S> AdaptiveEndpoint<S> {
93 pub fn new(inner: S, config: EndpointConfig) -> Self {
101 Self::new_at(inner, config, now())
102 }
103
104 pub fn new_at(inner: S, config: EndpointConfig, now: Instant) -> Self {
114 Self {
115 shared: Arc::new(Shared {
116 service: tokio::sync::Mutex::new(inner),
117 controller: Mutex::new(ControllerState {
118 controller: EndpointController::new(config, now),
119 admission_waker: None,
120 }),
121 dispatch: tokio::sync::Notify::new(),
122 }),
123 }
124 }
125
126 fn with_controller<T>(&self, operation: impl FnOnce(&mut EndpointController) -> T) -> T {
127 let (result, changed) = {
128 let mut state = lock(&self.shared.controller);
129 let before = state.controller.active_probe();
130 let result = operation(&mut state.controller);
131 (result, before != state.controller.active_probe())
132 };
133 if changed {
134 self.shared.dispatch.notify_waiters();
135 }
136 result
137 }
138
139 pub fn snapshot(&self) -> loadpace::ControllerSnapshot {
144 self.with_controller(|controller| controller.snapshot(now()))
145 }
146
147 pub fn load_metric(&self) -> LoadMetric {
152 self.with_controller(|controller| {
153 let current = now();
154 LoadMetric(controller.load(current))
155 })
156 }
157}
158
159#[derive(Clone, Debug)]
161pub struct AdaptiveLayer {
162 config: EndpointConfig,
163}
164
165impl AdaptiveLayer {
166 pub fn new(config: EndpointConfig) -> Self {
168 Self { config }
169 }
170
171 pub fn config(&self) -> &EndpointConfig {
173 &self.config
174 }
175}
176
177impl Default for AdaptiveLayer {
178 fn default() -> Self {
179 Self::new(EndpointConfig::default())
180 }
181}
182
183impl<S> Layer<S> for AdaptiveLayer {
184 type Service = AdaptiveEndpoint<S>;
185
186 fn layer(&self, inner: S) -> Self::Service {
187 AdaptiveEndpoint::new(inner, self.config.clone())
188 }
189}
190
191pin_project! {
192 #[doc = "Maps a Tower discovery stream into freshly initialized adaptive endpoints."]
193 #[doc = ""]
194 #[doc = "The wrapper intentionally creates new controller state for every insert."]
195 #[doc = "This is the safe behavior when discovery removes and later reuses an"]
196 #[doc = "endpoint key; state retention can be added without changing the discovery"]
197 #[doc = "contract once churn behavior is better understood."]
198 pub struct AdaptiveDiscovery<D> {
199 #[pin]
200 inner: D,
201 config: EndpointConfig,
202 }
203}
204
205impl<D> AdaptiveDiscovery<D> {
206 pub fn new(inner: D, config: EndpointConfig) -> Self {
208 Self { inner, config }
209 }
210
211 pub fn into_inner(self) -> D {
213 self.inner
214 }
215}
216
217impl<D, K, S> Stream for AdaptiveDiscovery<D>
218where
219 D: TryStream<Ok = Change<K, S>>,
220{
221 type Item = Result<Change<K, AdaptiveEndpoint<S>>, D::Error>;
222
223 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
224 let this = self.project();
225 this.inner.try_poll_next(cx).map(|change| {
226 change.map(|result| {
227 result.map(|change| match change {
228 Change::Insert(key, service) => {
229 Change::Insert(key, AdaptiveEndpoint::new(service, this.config.clone()))
230 }
231 Change::Remove(key) => Change::Remove(key),
232 })
233 })
234 })
235 }
236}
237
238impl<S> Load for AdaptiveEndpoint<S> {
239 type Metric = LoadMetric;
240
241 fn load(&self) -> Self::Metric {
242 self.load_metric()
243 }
244}
245
246impl<S, Request> Service<Request> for AdaptiveEndpoint<S>
247where
248 S: Service<Request> + Send + 'static,
249 S::Future: Send + 'static,
250 Request: Send + 'static,
251{
252 type Response = S::Response;
253 type Error = S::Error;
254 type Future = ResponseFuture<S::Response, S::Error>;
255
256 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
257 let mut state = lock(&self.shared.controller);
258 match state.poll_admission(cx) {
259 Poll::Ready(()) => Poll::Ready(Ok(())),
260 Poll::Pending => Poll::Pending,
261 }
262 }
263
264 fn call(&mut self, request: Request) -> Self::Future {
265 let reservation = lock(&self.shared.controller).controller.reserve(now());
269 let reservation =
270 reservation.expect("AdaptiveEndpoint::call invoked without available readiness");
271
272 let pending = PendingRequest::new(Arc::clone(&self.shared), reservation);
273 ResponseFuture {
274 inner: Box::pin(pending.execute(request)),
275 }
276 }
277}
278
279pub struct ResponseFuture<T, E> {
281 inner: Pin<Box<dyn Future<Output = Result<T, E>> + Send + 'static>>,
282}
283
284impl<T, E> Future for ResponseFuture<T, E> {
285 type Output = Result<T, E>;
286
287 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
288 self.get_mut().inner.as_mut().poll(cx)
289 }
290}
291
292enum RequestState {
293 Queued(DispatchReservation),
294 InFlight(InFlightRequest),
295 Finished,
296}
297
298struct PendingRequest<S> {
299 shared: Arc<Shared<S>>,
300 state: RequestState,
301}
302
303impl<S> PendingRequest<S> {
304 fn new(shared: Arc<Shared<S>>, reservation: DispatchReservation) -> Self {
305 Self {
306 shared,
307 state: RequestState::Queued(reservation),
308 }
309 }
310
311 fn reservation(&self) -> DispatchReservation {
312 match &self.state {
313 RequestState::Queued(reservation) => *reservation,
314 RequestState::InFlight(_) | RequestState::Finished => {
315 panic!("only a queued request has a dispatch reservation")
316 }
317 }
318 }
319
320 fn mark_dispatched(&mut self, active: InFlightRequest) {
321 self.state = RequestState::InFlight(active);
324 }
325
326 fn finish(&mut self, outcome: Outcome, now: Instant) {
327 match std::mem::replace(&mut self.state, RequestState::Finished) {
328 RequestState::InFlight(active) => {
329 let latency = now.saturating_duration_since(active.dispatched_at());
330 lock(&self.shared.controller)
331 .controller
332 .on_complete(active, outcome, latency, now);
333 }
334 RequestState::Queued(reservation) => {
335 let admission_waker = {
336 let mut state = lock(&self.shared.controller);
337 state.controller.cancel(reservation, now);
338 state.take_admission_waker()
339 };
340 if let Some(waker) = admission_waker {
341 waker.wake();
342 }
343 }
344 RequestState::Finished => return,
345 }
346 self.shared.dispatch.notify_waiters();
347 }
348
349 async fn wait_until_dispatchable(&self) {
350 let reservation = self.reservation();
351 loop {
352 let notified = self.shared.dispatch.notified();
353 let mut notified = std::pin::pin!(notified);
354 notified.as_mut().enable();
357
358 let (state, probe_changed) = {
359 let mut shared_state = lock(&self.shared.controller);
360 let previous_probe = shared_state.controller.active_probe();
361 let state = shared_state.controller.dispatch_state(reservation, now());
362 (
363 state,
364 previous_probe != shared_state.controller.active_probe(),
365 )
366 };
367 if probe_changed {
368 self.shared.dispatch.notify_waiters();
369 }
370
371 match state {
372 DispatchState::Ready => return,
373 DispatchState::WaitUntil(deadline) => {
374 let delay = deadline.saturating_duration_since(now());
375 let _ = tokio::time::timeout(delay, notified.as_mut()).await;
376 }
377 DispatchState::WaitForPrevious | DispatchState::InflightLimit => {
378 notified.as_mut().await;
379 }
380 DispatchState::Cancelled => {
381 panic!("a live request reservation was cancelled externally")
382 }
383 }
384 }
385 }
386
387 async fn start<Request>(&mut self, request: Request) -> Result<S::Future, S::Error>
388 where
389 S: Service<Request>,
390 {
391 let shared = Arc::clone(&self.shared);
392 let mut service = shared.service.lock().await;
393 std::future::poll_fn(|cx| service.poll_ready(cx)).await?;
394
395 loop {
399 let (dispatch, admission_waker) = {
400 let mut state = lock(&self.shared.controller);
401 let dispatch = state.controller.on_dispatched(self.reservation(), now());
402 let admission_waker = if dispatch.is_ok() {
403 state.take_admission_waker()
404 } else {
405 None
406 };
407 (dispatch, admission_waker)
408 };
409 if let Some(waker) = admission_waker {
410 waker.wake();
411 }
412 match dispatch {
413 Ok(active) => {
414 self.mark_dispatched(active);
415 break;
416 }
417 Err(_) => self.wait_until_dispatchable().await,
418 }
419 }
420 self.shared.dispatch.notify_waiters();
422
423 Ok(service.call(request))
424 }
425
426 async fn execute<Request>(mut self, request: Request) -> Result<S::Response, S::Error>
427 where
428 S: Service<Request>,
429 {
430 self.wait_until_dispatchable().await;
431
432 let response = match self.start(request).await {
433 Ok(response) => response,
434 Err(error) => {
435 lock(&self.shared.controller)
436 .controller
437 .on_admission_failure(now());
438 self.finish(Outcome::Failure, now());
439 return Err(error);
440 }
441 };
442 let response = response.await;
443 let outcome = if response.is_ok() {
444 Outcome::Success
445 } else {
446 Outcome::Failure
447 };
448 self.finish(outcome, now());
449 response
450 }
451}
452
453impl<S> Drop for PendingRequest<S> {
454 fn drop(&mut self) {
455 self.finish(Outcome::Failure, now());
456 }
457}