1use std::collections::HashMap;
4use std::net::{IpAddr, SocketAddr};
5use std::sync::atomic::{AtomicUsize, Ordering};
6use std::sync::{Arc, Mutex, OnceLock, PoisonError};
7use std::time::Duration;
8
9use pingora_core::apps::{HttpServerApp, ServerApp};
10use pingora_core::protocols::GetSocketDigest;
11use pingora_core::protocols::l4::listener::Listener;
12use pingora_core::protocols::l4::stream::Stream;
13use pingora_proxy::HttpProxy;
14use tokio::sync::{OwnedSemaphorePermit, Semaphore, watch};
15use tokio::task::JoinSet;
16use tokio_util::sync::CancellationToken;
17
18use crate::error::PingoraError;
19use crate::lifecycle::{Lifecycle, LifecycleCell, LifecycleEvent};
20use crate::metrics::Metrics;
21use crate::proxy::Gateway;
22use crate::upstream::Pool;
23
24const ACCEPT_BACKOFF: Duration = Duration::from_millis(100);
27
28#[derive(Debug, Clone, PartialEq, Eq)]
30#[non_exhaustive]
31pub struct UpstreamHealth {
32 pub route: String,
34 pub healthy: usize,
36 pub total: usize,
38}
39
40#[derive(Debug)]
42pub struct Shared {
43 pub lifecycle: LifecycleCell,
44 pub local_addr: OnceLock<SocketAddr>,
45 pub metrics: OnceLock<Arc<Metrics>>,
46 pools: OnceLock<(Vec<String>, Arc<Vec<Pool>>)>,
47 active: AtomicUsize,
48 stop: CancellationToken,
50 done: watch::Sender<bool>,
52}
53
54impl Default for Shared {
55 fn default() -> Self {
56 Self {
57 lifecycle: LifecycleCell::new(),
58 local_addr: OnceLock::new(),
59 metrics: OnceLock::new(),
60 pools: OnceLock::new(),
61 active: AtomicUsize::new(0),
62 stop: CancellationToken::new(),
63 done: watch::channel(false).0,
64 }
65 }
66}
67
68impl Shared {
69 pub fn active_connections(&self) -> usize {
71 self.active.load(Ordering::Acquire)
72 }
73
74 pub fn upstream_health(&self) -> Vec<UpstreamHealth> {
76 self.pools.get().map_or_else(Vec::new, |(names, pools)| {
77 names
78 .iter()
79 .zip(pools.iter())
80 .map(|(name, pool)| UpstreamHealth {
81 route: name.clone(),
82 healthy: pool.healthy(),
83 total: pool.total(),
84 })
85 .collect()
86 })
87 }
88
89 pub fn fail(&self) {
92 if self.lifecycle.apply(LifecycleEvent::StartFailed).is_ok() {
93 self.done.send_replace(true);
94 }
95 }
96
97 fn finish(&self, event: LifecycleEvent) {
99 let _ = self.lifecycle.apply(event);
100 if self.lifecycle.get().is_terminal() {
101 self.done.send_replace(true);
102 }
103 }
104}
105
106struct Active(Arc<Shared>);
108
109impl Active {
110 fn new(shared: &Arc<Shared>) -> Self {
111 shared.active.fetch_add(1, Ordering::AcqRel);
112 Self(shared.clone())
113 }
114}
115
116impl Drop for Active {
117 fn drop(&mut self) {
118 self.0.active.fetch_sub(1, Ordering::AcqRel);
119 }
120}
121
122pub struct Launch {
124 pub listener: tokio::net::TcpListener,
125 pub proxy: HttpProxy<Gateway>,
126 pub pools: Arc<Vec<Pool>>,
127 pub route_names: Vec<String>,
128 pub max_connections: usize,
129 pub max_connections_per_ip: usize,
130 pub grace: Duration,
131 pub health_interval: Option<Duration>,
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub enum Started {
137 Serving(SocketAddr),
139 Stopped,
141}
142
143pub fn start(shared: &Arc<Shared>, launch: Launch) -> Result<Started, PingoraError> {
150 let addr = launch
151 .listener
152 .local_addr()
153 .map_err(|source| PingoraError::Bind {
154 addr: SocketAddr::from(([0, 0, 0, 0], 0)),
155 source,
156 })?;
157 match shared.lifecycle.apply(LifecycleEvent::Bound) {
158 Ok(_) => {}
159 Err(Lifecycle::Stopped) => return Ok(Started::Stopped),
160 Err(_) => return Err(PingoraError::AlreadyStarted),
161 }
162 let _ = shared.local_addr.set(addr);
163 let _ = shared.pools.set((launch.route_names, launch.pools.clone()));
164 if let Some(interval) = launch.health_interval {
165 tokio::spawn(check_health(launch.pools, interval, shared.stop.clone()));
166 }
167 let limit =
168 (launch.max_connections > 0).then(|| Arc::new(Semaphore::new(launch.max_connections)));
169 let proxy = Arc::new(launch.proxy);
170 let per_ip = PerIp::new(launch.max_connections_per_ip);
171 let exit = ExitGuard(Some(shared.clone()));
174 tokio::spawn(serve(
175 exit,
176 Listener::from(launch.listener),
177 proxy,
178 Limits {
179 total: limit,
180 per_ip,
181 },
182 launch.grace,
183 ));
184 Ok(Started::Serving(addr))
185}
186
187async fn check_health(pools: Arc<Vec<Pool>>, interval: Duration, stop: CancellationToken) {
189 let mut ticker = tokio::time::interval(interval);
190 ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
191 loop {
192 tokio::select! {
193 () = stop.cancelled() => return,
194 _ = ticker.tick() => {
195 futures_util::future::join_all(pools.iter().map(Pool::check)).await;
196 }
197 }
198 }
199}
200
201async fn slot(
203 limit: Option<&Arc<Semaphore>>,
204 stop: &CancellationToken,
205) -> Option<Option<OwnedSemaphorePermit>> {
206 let Some(limit) = limit else {
207 return Some(None);
208 };
209 tokio::select! {
210 () = stop.cancelled() => None,
211 permit = limit.clone().acquire_owned() => permit.ok().map(Some),
212 }
213}
214
215#[derive(Debug)]
217struct PerIp {
218 limit: usize,
219 open: Mutex<HashMap<IpAddr, usize>>,
220}
221
222impl PerIp {
223 fn new(limit: usize) -> Option<Arc<Self>> {
224 (limit > 0).then(|| {
225 Arc::new(Self {
226 limit,
227 open: Mutex::new(HashMap::new()),
228 })
229 })
230 }
231
232 fn take(self: &Arc<Self>, ip: IpAddr) -> Option<IpSlot> {
234 let mut open = self.open.lock().unwrap_or_else(PoisonError::into_inner);
235 let count = open.entry(ip).or_insert(0);
236 let admitted = *count < self.limit;
237 if admitted {
238 *count += 1;
239 }
240 drop(open);
241 admitted.then(|| IpSlot(self.clone(), ip))
242 }
243}
244
245struct IpSlot(Arc<PerIp>, IpAddr);
247
248impl Drop for IpSlot {
249 fn drop(&mut self) {
250 let mut open = self.0.open.lock().unwrap_or_else(PoisonError::into_inner);
251 if let Some(count) = open.get_mut(&self.1) {
252 *count -= 1;
253 if *count == 0 {
254 open.remove(&self.1);
255 }
256 }
257 }
258}
259
260struct Limits {
262 total: Option<Arc<Semaphore>>,
263 per_ip: Option<Arc<PerIp>>,
264}
265
266fn ip_slot(per_ip: Option<&Arc<PerIp>>, stream: &Stream) -> Result<Option<IpSlot>, ()> {
268 let Some(per_ip) = per_ip else {
269 return Ok(None);
270 };
271 let ip = stream.get_socket_digest().and_then(|digest| {
272 digest
273 .peer_addr()
274 .and_then(|a| a.as_inet())
275 .map(SocketAddr::ip)
276 });
277 ip.map_or(Ok(None), |ip| per_ip.take(ip).map(Some).ok_or(()))
278}
279
280async fn serve(
282 mut exit: ExitGuard,
283 listener: Listener,
284 proxy: Arc<HttpProxy<Gateway>>,
285 limits: Limits,
286 grace: Duration,
287) {
288 let Some(shared) = exit.0.clone() else {
289 return;
290 };
291 let (shutdown_tx, shutdown_rx) = watch::channel(false);
292 let mut connections = JoinSet::new();
293 loop {
294 let Some(permit) = slot(limits.total.as_ref(), &shared.stop).await else {
295 break;
296 };
297 tokio::select! {
298 () = shared.stop.cancelled() => break,
299 accepted = listener.accept() => match accepted {
300 Ok(stream) => {
301 let Ok(ip) = ip_slot(limits.per_ip.as_ref(), &stream) else {
302 tracing::debug!("pingora proxy: client at max_connections_per_ip");
303 continue;
304 };
305 let active = Active::new(&shared);
306 let proxy = proxy.clone();
307 let shutdown = shutdown_rx.clone();
308 connections.spawn(async move {
309 serve_connection(proxy, stream, shutdown).await;
310 drop((active, permit, ip));
311 });
312 }
313 Err(error) => {
314 tracing::warn!(%error, "pingora proxy: accept failed");
315 tokio::select! {
316 () = shared.stop.cancelled() => {}
317 () = tokio::time::sleep(ACCEPT_BACKOFF) => {}
318 }
319 }
320 },
321 Some(_) = connections.join_next(), if !connections.is_empty() => {}
322 }
323 }
324 drop(listener);
326 drain(&proxy, &shutdown_tx, &mut connections, grace).await;
327 exit.0 = None;
328 shared.finish(LifecycleEvent::Drained);
329}
330
331struct ExitGuard(Option<Arc<Shared>>);
334
335impl Drop for ExitGuard {
336 fn drop(&mut self) {
337 if let Some(shared) = self.0.take() {
338 shared.stop.cancel();
339 shared.finish(LifecycleEvent::ServerExited);
340 }
341 }
342}
343
344async fn drain(
346 proxy: &HttpProxy<Gateway>,
347 shutdown: &watch::Sender<bool>,
348 connections: &mut JoinSet<()>,
349 grace: Duration,
350) {
351 shutdown.send_replace(true);
352 proxy.http_cleanup().await;
353 let finished = tokio::time::timeout(grace, async {
354 while connections.join_next().await.is_some() {}
355 })
356 .await;
357 if finished.is_err() {
358 tracing::warn!(
359 open = connections.len(),
360 "pingora proxy: the grace period ended; the proxy closes the open connections"
361 );
362 connections.abort_all();
363 while connections.join_next().await.is_some() {}
364 }
365}
366
367async fn serve_connection(
369 proxy: Arc<HttpProxy<Gateway>>,
370 stream: Stream,
371 shutdown: watch::Receiver<bool>,
372) {
373 let mut next: Option<pingora_core::protocols::Stream> = Some(Box::new(stream));
374 while let Some(stream) = next {
375 next = proxy.process_new(stream, &shutdown).await;
376 }
377}
378
379#[derive(Debug, Clone)]
382pub struct PingoraHandle {
383 shared: Arc<Shared>,
384}
385
386impl PingoraHandle {
387 pub(crate) const fn new(shared: Arc<Shared>) -> Self {
388 Self { shared }
389 }
390
391 #[must_use]
393 pub fn state(&self) -> Lifecycle {
394 self.shared.lifecycle.get()
395 }
396
397 #[must_use]
399 pub fn local_addr(&self) -> Option<SocketAddr> {
400 self.shared.local_addr.get().copied()
401 }
402
403 #[must_use]
405 pub fn active_connections(&self) -> usize {
406 self.shared.active_connections()
407 }
408
409 #[must_use]
411 pub fn upstream_health(&self) -> Vec<UpstreamHealth> {
412 self.shared.upstream_health()
413 }
414
415 #[must_use]
417 pub fn metric_families(&self) -> Vec<autumn_web::actuator::MetricFamily> {
418 crate::metrics::families(&self.shared)
419 }
420
421 pub async fn shutdown(&self) {
425 match self
426 .shared
427 .lifecycle
428 .apply(LifecycleEvent::ShutdownRequested)
429 {
430 Ok(Lifecycle::Draining) => self.shared.stop.cancel(),
431 Ok(_) => {
432 self.shared.stop.cancel();
433 self.shared.done.send_replace(true);
434 }
435 Err(_) => {}
436 }
437 let mut done = self.shared.done.subscribe();
438 let _ = done.wait_for(|finished| *finished).await;
439 }
440}