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