1use crate::{
30 Lifecycle,
31 admission::{Admission, Permit, Pool},
32 local::peer::PeerCredentials,
33 peer::{Authenticated, Policy, authenticate},
34};
35use std::{future::Future, io, sync::Arc, time::Duration};
36use tokio::task::JoinSet;
37
38const DESCRIPTOR_BACKOFF: Duration = Duration::from_millis(50);
41
42pub trait Listener {
44 type Connection: PeerCredentials + Send + 'static;
46 fn accept(&self) -> impl Future<Output = io::Result<Self::Connection>> + Send;
50}
51
52#[cfg(unix)]
53impl Listener for tokio::net::UnixListener {
54 type Connection = tokio::net::UnixStream;
55 async fn accept(&self) -> io::Result<tokio::net::UnixStream> {
56 tokio::net::UnixListener::accept(self)
57 .await
58 .map(|(stream, _)| stream)
59 }
60}
61
62#[cfg(windows)]
63mod windows;
64
65#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
67#[non_exhaustive]
68pub struct Served {
69 pub admitted: u64,
71 pub unauthenticated: u64,
74 pub over_capacity: u64,
76 pub aborted: u64,
79}
80
81fn is_descriptor_exhaustion(error: &io::Error) -> bool {
84 cfg!(unix) && matches!(error.raw_os_error(), Some(23 | 24))
86}
87
88pub fn serve<L, P>(
91 listener: L,
92 lifecycle: Arc<Lifecycle>,
93 admission: Arc<Admission>,
94 policy: P,
95) -> Serve<L, P> {
96 Serve {
97 listener,
98 lifecycle,
99 admission,
100 policy,
101 drain_budget: Duration::from_secs(5),
102 }
103}
104
105pub struct Serve<L, P> {
107 listener: L,
108 lifecycle: Arc<Lifecycle>,
109 admission: Arc<Admission>,
110 policy: P,
111 drain_budget: Duration,
112}
113
114#[derive(Debug)]
116#[non_exhaustive]
117pub struct ServeError {
118 pub error: io::Error,
120 pub served: Served,
122}
123
124impl std::fmt::Display for ServeError {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 write!(f, "accept loop failed: {}", self.error)
127 }
128}
129
130impl std::error::Error for ServeError {
131 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
132 Some(&self.error)
133 }
134}
135
136impl<L, P> Serve<L, P> {
137 pub fn drain_budget(mut self, budget: Duration) -> Self {
140 self.drain_budget = budget;
141 self
142 }
143
144 pub async fn run<H, F>(
150 self,
151 pool: impl Fn(&P::Grant) -> Pool,
152 handler: H,
153 ) -> Result<Served, ServeError>
154 where
155 L: Listener,
156 P: Policy,
157 P::Grant: Send + 'static,
158 H: Fn(Authenticated<L::Connection, P::Grant>, Permit) -> F,
159 F: Future<Output = ()> + Send + 'static,
160 {
161 let Self {
162 listener,
163 lifecycle,
164 admission,
165 policy,
166 drain_budget,
167 } = self;
168 let mut served = Served::default();
169 let mut handlers = JoinSet::new();
170 let stopped = 'accept: loop {
171 tokio::select! {
172 biased;
173 () = lifecycle.draining() => break 'accept Ok(()),
174 Some(_) = handlers.join_next(), if !handlers.is_empty() => {}
175 accepted = listener.accept() => match accepted {
176 Ok(connection) => {
177 let Ok(evidence) = connection.credentials() else {
178 served.unauthenticated += 1;
179 lifecycle.record(crate::observation::Event::Rejected);
180 continue;
181 };
182 let Ok(authenticated) = authenticate(connection, evidence, &policy) else {
183 served.unauthenticated += 1;
184 lifecycle.record(crate::observation::Event::Rejected);
185 continue;
186 };
187 let Some(permit) = admission.try_acquire(pool(authenticated.grant())) else {
188 served.over_capacity += 1;
189 continue;
190 };
191 if !lifecycle.accepting_calls() {
193 break 'accept Ok(());
194 }
195 served.admitted += 1;
196 handlers.spawn(handler(authenticated, permit));
197 }
198 Err(error) if is_descriptor_exhaustion(&error) => {
199 tokio::select! {
200 () = lifecycle.draining() => break 'accept Ok(()),
201 () = descriptor_backoff() => {}
202 }
203 }
204 Err(error) => break 'accept Err(error),
205 },
206 }
207 };
208 served.aborted = finish(&mut handlers, drain_budget).await;
209 stopped
210 .map(|()| served)
211 .map_err(|error| ServeError { error, served })
212 }
213}
214
215#[expect(
216 clippy::disallowed_methods,
217 reason = "No event says a descriptor was freed; pause briefly, then accept again."
218)]
219async fn descriptor_backoff() {
220 tokio::time::sleep(DESCRIPTOR_BACKOFF).await;
221}
222
223async fn finish(handlers: &mut JoinSet<()>, budget: Duration) -> u64 {
226 let deadline = tokio::time::Instant::now() + budget;
227 while !handlers.is_empty() {
228 if tokio::time::timeout_at(deadline, handlers.join_next())
229 .await
230 .is_err()
231 {
232 break;
233 }
234 }
235 handlers.abort_all();
236 let mut aborted = 0;
237 while let Some(joined) = handlers.join_next().await {
238 if joined.is_err_and(|error| error.is_cancelled()) {
239 aborted += 1;
240 }
241 }
242 aborted
243}
244
245#[cfg(all(test, unix))]
246mod tests {
247 use super::*;
248 use crate::{
249 ProcessId,
250 admission::Limits,
251 peer::{Credentials, ExpectedProcess, First, SameUser},
252 };
253 use tokio::io::{AsyncReadExt, AsyncWriteExt};
254
255 #[test]
256 fn only_emfile_and_enfile_count_as_running_out_of_descriptors() {
257 assert!(is_descriptor_exhaustion(&io::Error::from_raw_os_error(23)));
258 assert!(is_descriptor_exhaustion(&io::Error::from_raw_os_error(24)));
259 assert!(!is_descriptor_exhaustion(&io::Error::from_raw_os_error(22)));
260 assert!(!is_descriptor_exhaustion(&io::ErrorKind::Other.into()));
261 }
262
263 struct Fixture {
264 _dir: tempfile::TempDir,
265 path: std::path::PathBuf,
266 listener: tokio::net::UnixListener,
267 }
268
269 fn fixture() -> Fixture {
270 let dir = tempfile::tempdir().unwrap();
271 let path = dir.path().join("serve.sock");
272 let listener = tokio::net::UnixListener::bind(&path).unwrap();
273 Fixture {
274 _dir: dir,
275 path,
276 listener,
277 }
278 }
279
280 async fn exchange(path: &std::path::Path) -> Vec<u8> {
282 let mut stream = tokio::net::UnixStream::connect(path).await.unwrap();
283 let mut reply = Vec::new();
284 stream.read_to_end(&mut reply).await.unwrap();
285 reply
286 }
287
288 #[tokio::test]
289 async fn an_accepted_peer_is_served_with_its_grant_and_a_permit_from_its_pool() {
290 let Fixture {
291 _dir,
292 path,
293 listener,
294 } = fixture();
295 let lifecycle = Arc::new(Lifecycle::default());
296 let admission = Arc::new(Admission::new(Limits {
298 application: 0,
299 ..Limits::default()
300 }));
301 let own = ProcessId::new(std::process::id());
302 let policy = First::new()
303 .then(ExpectedProcess::new(move || own), "full")
304 .then(SameUser, "redacted");
305 let server = tokio::spawn(
306 serve(
307 listener,
308 Arc::clone(&lifecycle),
309 Arc::clone(&admission),
310 policy,
311 )
312 .drain_budget(Duration::from_secs(5))
313 .run(
314 |grant: &&str| {
315 if *grant == "full" {
316 Pool::Control
317 } else {
318 Pool::Application
319 }
320 },
321 |mut connection, permit| async move {
322 let _permit = permit;
323 let grant = *connection.grant();
324 connection.write_all(grant.as_bytes()).await.unwrap();
325 },
326 ),
327 );
328 assert_eq!(exchange(&path).await, b"full");
329 assert_eq!(admission.snapshot(Pool::Application).rejected, 0);
330 lifecycle.start_drain();
331 let served = server.await.unwrap().unwrap();
332 assert_eq!(
333 served,
334 Served {
335 admitted: 1,
336 ..Served::default()
337 }
338 );
339 }
340
341 #[tokio::test]
342 async fn a_rejected_peer_is_dropped_before_it_takes_capacity() {
343 let Fixture {
344 _dir,
345 path,
346 listener,
347 } = fixture();
348 let observations = Arc::new(crate::observation::Observations::new(4));
349 let lifecycle = Arc::new(Lifecycle::observed(Arc::clone(&observations)));
350 let admission = Arc::new(Admission::new(Limits::default()));
351 let server = tokio::spawn(
352 serve(
353 listener,
354 Arc::clone(&lifecycle),
355 Arc::clone(&admission),
356 ExpectedProcess::new(|| ProcessId::new(1)),
357 )
358 .drain_budget(Duration::from_secs(5))
359 .run(
360 |_| Pool::Control,
361 |_connection, _permit| async move {
362 panic!("a rejected peer reached the handler");
363 },
364 ),
365 );
366 assert_eq!(exchange(&path).await, b"");
367 let control = admission.snapshot(Pool::Control);
368 assert_eq!((control.active, control.rejected), (0, 0));
369 assert!(
370 observations
371 .take_events()
372 .iter()
373 .any(|event| event.event == crate::observation::Event::Rejected)
374 );
375 lifecycle.start_drain();
376 let served = server.await.unwrap().unwrap();
377 assert_eq!(served.unauthenticated, 1);
378 assert_eq!(served.admitted, 0);
379 }
380
381 #[tokio::test]
382 async fn a_full_pool_refuses_an_authenticated_peer() {
383 let Fixture {
384 _dir,
385 path,
386 listener,
387 } = fixture();
388 let lifecycle = Arc::new(Lifecycle::default());
389 let admission = Arc::new(Admission::new(Limits {
390 control: 0,
391 ..Limits::default()
392 }));
393 let server = tokio::spawn(
394 serve(
395 listener,
396 Arc::clone(&lifecycle),
397 Arc::clone(&admission),
398 SameUser,
399 )
400 .drain_budget(Duration::from_secs(5))
401 .run(
402 |()| Pool::Control,
403 |_connection, _permit| async move {
404 panic!("a refused peer reached the handler");
405 },
406 ),
407 );
408 assert_eq!(exchange(&path).await, b"");
409 assert_eq!(admission.snapshot(Pool::Control).rejected, 1);
410 lifecycle.start_drain();
411 let served = server.await.unwrap().unwrap();
412 assert_eq!(served.over_capacity, 1);
413 assert_eq!(served.unauthenticated, 0);
414 }
415
416 #[tokio::test(start_paused = true)]
417 async fn draining_stops_accepting_and_aborts_handlers_past_the_budget() {
418 let Fixture {
419 _dir,
420 path,
421 listener,
422 } = fixture();
423 let lifecycle = Arc::new(Lifecycle::default());
424 let admission = Arc::new(Admission::new(Limits::default()));
425 let (started, mut running) = tokio::sync::mpsc::unbounded_channel();
426 let server = tokio::spawn(
427 serve(listener, Arc::clone(&lifecycle), admission, SameUser)
428 .drain_budget(Duration::from_secs(5))
429 .run(
430 |()| Pool::Application,
431 move |_connection, permit| {
432 let started = started.clone();
433 async move {
434 let _permit = permit;
435 started.send(()).unwrap();
436 std::future::pending::<()>().await;
437 }
438 },
439 ),
440 );
441 let _client = tokio::net::UnixStream::connect(&path).await.unwrap();
442 running.recv().await.unwrap();
443 lifecycle.start_drain();
444 let served = server.await.unwrap().unwrap();
445 assert_eq!(served.admitted, 1);
446 assert_eq!(served.aborted, 1);
447 }
448
449 #[tokio::test(start_paused = true)]
450 async fn a_handler_that_finishes_within_the_budget_is_not_aborted() {
451 let Fixture {
452 _dir,
453 path,
454 listener,
455 } = fixture();
456 let lifecycle = Arc::new(Lifecycle::default());
457 let admission = Arc::new(Admission::new(Limits::default()));
458 let (started, mut running) = tokio::sync::mpsc::unbounded_channel();
459 let finish = Arc::new(tokio::sync::Notify::new());
460 let release = Arc::clone(&finish);
461 let server = tokio::spawn(
462 serve(listener, Arc::clone(&lifecycle), admission, SameUser)
463 .drain_budget(Duration::from_secs(5))
464 .run(
465 |()| Pool::Application,
466 move |_connection, permit| {
467 let started = started.clone();
468 let finish = Arc::clone(&finish);
469 async move {
470 let _permit = permit;
471 started.send(()).unwrap();
472 finish.notified().await;
473 }
474 },
475 ),
476 );
477 let _client = tokio::net::UnixStream::connect(&path).await.unwrap();
478 running.recv().await.unwrap();
479 lifecycle.start_drain();
480 tokio::task::yield_now().await;
482 release.notify_one();
483 let served = server.await.unwrap().unwrap();
484 assert_eq!(served.admitted, 1);
485 assert_eq!(served.aborted, 0);
486 }
487
488 #[tokio::test]
489 async fn an_accept_failure_keeps_counts_and_its_error_source() {
490 let error = serve(
491 Scripted::new([
492 Ok(Fake(None)),
493 Ok(same_user()),
494 Err(io::ErrorKind::PermissionDenied.into()),
495 ]),
496 Arc::new(Lifecycle::default()),
497 Arc::new(Admission::new(Limits::default())),
498 SameUser,
499 )
500 .run(|()| Pool::Application, |_connection, _permit| async {})
501 .await
502 .unwrap_err();
503 assert_eq!(error.served.unauthenticated, 1);
504 assert_eq!(error.served.admitted, 1);
505 assert_eq!(error.served.over_capacity, 0);
506 assert_eq!(error.served.aborted, 0);
507 assert_eq!(error.error.kind(), io::ErrorKind::PermissionDenied);
508 assert_eq!(
509 error.to_string(),
510 format!("accept loop failed: {}", error.error)
511 );
512 assert!(
513 std::error::Error::source(&error)
514 .unwrap()
515 .downcast_ref::<io::Error>()
516 .is_some()
517 );
518 }
519
520 #[tokio::test(start_paused = true)]
521 async fn the_handler_drain_budget_is_configurable_and_defaults_to_five_seconds() {
522 for budget in [None, Some(Duration::from_secs(13))] {
523 let lifecycle = Arc::new(Lifecycle::default());
524 let (started, mut ready) = tokio::sync::mpsc::unbounded_channel();
525 let configured = serve(
526 Scripted::new([Ok(same_user())]),
527 Arc::clone(&lifecycle),
528 Arc::new(Admission::new(Limits::default())),
529 SameUser,
530 );
531 let configured = match budget {
532 Some(budget) => configured.drain_budget(budget),
533 None => configured,
534 };
535 let server = tokio::spawn(configured.run(
536 |()| Pool::Application,
537 move |_connection, permit| {
538 let started = started.clone();
539 async move {
540 let _permit = permit;
541 started.send(()).unwrap();
542 std::future::pending::<()>().await;
543 }
544 },
545 ));
546 ready.recv().await.unwrap();
547 let began = tokio::time::Instant::now();
548 lifecycle.start_drain();
549 let served = server.await.unwrap().unwrap();
550 assert_eq!(served.aborted, 1);
551 assert_eq!(began.elapsed(), budget.unwrap_or(Duration::from_secs(5)));
552 }
553 }
554
555 struct Fake(Option<Credentials>);
557
558 impl PeerCredentials for Fake {
559 fn credentials(&self) -> io::Result<Credentials> {
560 self.0
561 .clone()
562 .ok_or_else(|| io::Error::other("no peer credentials"))
563 }
564 }
565
566 struct Scripted(std::sync::Mutex<std::collections::VecDeque<io::Result<Fake>>>);
568
569 impl Scripted {
570 fn new(results: impl IntoIterator<Item = io::Result<Fake>>) -> Self {
571 Self(std::sync::Mutex::new(results.into_iter().collect()))
572 }
573 }
574
575 impl Listener for Scripted {
576 type Connection = Fake;
577 async fn accept(&self) -> io::Result<Fake> {
578 let next = self.0.lock().unwrap().pop_front();
579 match next {
580 Some(result) => result,
581 None => std::future::pending().await,
582 }
583 }
584 }
585
586 fn same_user() -> Fake {
587 Fake(Some(Credentials::new(
588 ProcessId::new(std::process::id()),
589 true,
590 )))
591 }
592
593 async fn serve_scripted(
596 listener: Scripted,
597 policy: impl Policy<Grant = ()> + Send + 'static,
598 lifecycle: Arc<Lifecycle>,
599 admissions: usize,
600 ) -> Result<Served, ServeError> {
601 let (admitted, mut seen) = tokio::sync::mpsc::unbounded_channel();
602 let server = tokio::spawn(
603 serve(
604 listener,
605 Arc::clone(&lifecycle),
606 Arc::new(Admission::new(Limits::default())),
607 policy,
608 )
609 .drain_budget(Duration::from_secs(5))
610 .run(
611 |()| Pool::Application,
612 move |_connection, _permit| {
613 let admitted = admitted.clone();
614 async move {
615 let _ = admitted.send(());
616 }
617 },
618 ),
619 );
620 for _ in 0..admissions {
621 seen.recv().await.unwrap();
622 }
623 tokio::task::yield_now().await;
624 lifecycle.start_drain();
625 server.await.unwrap()
626 }
627
628 #[tokio::test]
629 async fn unreadable_evidence_rejects_the_connection() {
630 let served = serve_scripted(
631 Scripted::new([Ok(Fake(None)), Ok(same_user())]),
632 SameUser,
633 Arc::new(Lifecycle::default()),
634 1,
635 )
636 .await
637 .unwrap();
638 assert_eq!(
639 (
640 served.unauthenticated,
641 served.admitted,
642 served.over_capacity
643 ),
644 (1, 1, 0)
645 );
646 }
647
648 #[tokio::test(start_paused = true)]
649 async fn running_out_of_descriptors_pauses_accepting_then_continues() {
650 let started = tokio::time::Instant::now();
651 let served = serve_scripted(
652 Scripted::new([Err(io::Error::from_raw_os_error(24)), Ok(same_user())]),
653 SameUser,
654 Arc::new(Lifecycle::default()),
655 1,
656 )
657 .await
658 .unwrap();
659 assert_eq!(served.admitted, 1);
660 assert!(started.elapsed() >= DESCRIPTOR_BACKOFF);
661 }
662
663 #[tokio::test]
664 async fn any_other_accept_error_stops_the_loop_and_is_returned() {
665 let error = serve_scripted(
666 Scripted::new([Err(io::Error::from(io::ErrorKind::PermissionDenied))]),
667 SameUser,
668 Arc::new(Lifecycle::default()),
669 0,
670 )
671 .await
672 .unwrap_err();
673 assert_eq!(error.error.kind(), io::ErrorKind::PermissionDenied);
674 }
675
676 #[tokio::test]
677 async fn a_connection_checked_while_draining_starts_is_not_handed_out() {
678 struct DrainsWhileChecking(Arc<Lifecycle>);
679 impl Policy for DrainsWhileChecking {
680 type Grant = ();
681 fn grant(&self, _: &Credentials) -> Result<(), crate::peer::Rejected> {
682 self.0.start_drain();
683 Ok(())
684 }
685 }
686 let lifecycle = Arc::new(Lifecycle::default());
687 let served = serve_scripted(
688 Scripted::new([Ok(same_user())]),
689 DrainsWhileChecking(Arc::clone(&lifecycle)),
690 lifecycle,
691 0,
692 )
693 .await
694 .unwrap();
695 assert_eq!(served.admitted, 0);
696 }
697
698 #[tokio::test]
699 async fn unless_draining_stops_a_read_once_draining_starts() {
700 let lifecycle = Lifecycle::default();
701 assert_eq!(lifecycle.unless_draining(async { 7 }).await, Some(7));
702 lifecycle.start_drain();
703 assert_eq!(
704 lifecycle
705 .unless_draining(std::future::pending::<()>())
706 .await,
707 None
708 );
709 }
710}