1use std::{
18 sync::{
19 Arc,
20 atomic::{AtomicU64, Ordering},
21 },
22 time::{Duration, Instant},
23};
24
25use bytes::Bytes;
26use scion_quic::h3::client::{
27 H3DuplexStream, H3ResponseBody, Http3Client, RequestError as H3RequestError,
28};
29
30use crate::{
31 authority::Authority,
32 config::Config,
33 epoch::Epoch,
34 error::{Error, TimeoutPhase},
35 origin::Origin,
36 request::{IntoUrl, Request},
37 response::Response,
38 tunnel::{Tunnel, connect_request},
39};
40
41enum EpochSlot {
43 Init,
46 Active(Arc<Epoch>, u64),
50 Closed,
52}
53
54pub struct Client {
74 epoch: tokio::sync::RwLock<EpochSlot>,
76 reset_generation: AtomicU64,
82 config: Config,
84 #[cfg(test)]
87 epoch_factory: Option<EpochFactory>,
88}
89
90#[cfg(test)]
91type EpochFactory =
92 Box<dyn Fn() -> futures::future::BoxFuture<'static, Result<Epoch, Error>> + Send + Sync>;
93
94impl Client {
95 #[must_use]
98 pub fn new(config: Config) -> Self {
99 Client {
100 epoch: tokio::sync::RwLock::new(EpochSlot::Init),
101 reset_generation: AtomicU64::new(0),
102 config,
103 #[cfg(test)]
104 epoch_factory: None,
105 }
106 }
107
108 #[cfg(test)]
111 pub(crate) fn with_epoch_factory(config: Config, factory: EpochFactory) -> Self {
112 let mut client = Client::new(config);
113 client.epoch_factory = Some(factory);
114 client
115 }
116
117 pub async fn request(&self, request: Request) -> Result<Response, Error> {
132 let timeout = request
133 .request_timeout()
134 .unwrap_or(self.config.request_timeout);
135 let deadline = deadline_after(timeout);
136 let (response, connection) = tokio::time::timeout_at(deadline, self.request_head(&request))
137 .await
138 .map_err(|_| {
139 Error::Timeout {
140 phase: TimeoutPhase::Request,
141 timeout,
142 }
143 })??;
144 Ok(Response::new(response, deadline, timeout, connection))
145 }
146
147 pub async fn get(&self, url: impl IntoUrl) -> Result<Response, Error> {
150 let request = Request::get(url).build()?;
151 self.request(request).await
152 }
153
154 pub async fn post(&self, url: impl IntoUrl, body: impl Into<Bytes>) -> Result<Response, Error> {
158 let request = Request::post(url).body(body).build()?;
159 self.request(request).await
160 }
161
162 pub async fn connect(&self, authority: &Authority) -> Result<Tunnel, Error> {
169 self.connect_via(authority, authority).await
170 }
171
172 pub async fn connect_via(&self, via: &Authority, target: &Authority) -> Result<Tunnel, Error> {
177 self.connect_tunnel(Origin::from_authority(via), target)
178 .await
179 }
180
181 pub async fn connect_via_unverified(
187 &self,
188 via: &Authority,
189 target: &Authority,
190 ) -> Result<Tunnel, Error> {
191 self.connect_tunnel(Origin::unverified(via), target).await
192 }
193
194 pub async fn warm_up(&self, url: impl IntoUrl) -> Result<(), Error> {
199 let request = Request::get(url).build()?;
200 let origin = Origin::from_request(&request)?;
201 let epoch = self.current_epoch().await?;
202 let origin_client = epoch.origin_client(origin, Instant::now(), &self.config)?;
203 origin_client.connection().await?;
204 Ok(())
205 }
206
207 pub fn reset(&self) {
215 self.reset_generation.fetch_add(1, Ordering::Release);
216 }
217
218 pub async fn close(&self) {
222 let previous = {
223 let mut slot = self.epoch.write().await;
224 std::mem::replace(&mut *slot, EpochSlot::Closed)
225 };
226 if let EpochSlot::Active(epoch, _) = previous {
227 epoch.shutdown().await;
228 }
229 }
230
231 async fn request_head(
236 &self,
237 request: &Request,
238 ) -> Result<(http::Response<H3ResponseBody>, Arc<Http3Client>), Error> {
239 let origin = Origin::from_request(request)?;
240 self.with_connection(origin, |connection| {
241 async move { Ok(connection.request(request.to_http()?).await?) }
242 })
243 .await
244 }
245
246 async fn connect_tunnel(&self, origin: Origin, target: &Authority) -> Result<Tunnel, Error> {
249 let timeout = self.config.request_timeout;
250 tokio::time::timeout_at(deadline_after(timeout), self.open_tunnel(origin, target))
251 .await
252 .map_err(|_| {
253 Error::Timeout {
254 phase: TimeoutPhase::Request,
255 timeout,
256 }
257 })?
258 }
259
260 async fn open_tunnel(&self, origin: Origin, target: &Authority) -> Result<Tunnel, Error> {
261 let (host, port) = (origin.host.clone(), origin.port);
262 let ((response, writer), connection) = self
263 .with_connection(origin, |connection| {
264 async move {
265 Ok(connection
266 .request_with_writer(connect_request(target))
267 .await?)
268 }
269 })
270 .await?;
271 let response = response
272 .await
273 .map_err(|e| Error::from_h3_request_error(&host, port, e))?;
274 if !response.status().is_success() {
275 return Err(Error::TunnelRefused {
276 status: response.status(),
277 });
278 }
279 let stream = H3DuplexStream::new(writer, response.into_body());
280 Ok(Tunnel::new(stream, connection))
281 }
282
283 async fn with_connection<T, F, Fut>(
289 &self,
290 origin: Origin,
291 send: F,
292 ) -> Result<(T, Arc<Http3Client>), Error>
293 where
294 F: Fn(Arc<Http3Client>) -> Fut,
295 Fut: Future<Output = Result<T, SendError>>,
296 {
297 let (host, port) = (origin.host.clone(), origin.port);
298 let epoch = self.current_epoch().await?;
299 let origin_client = epoch.origin_client(origin, Instant::now(), &self.config)?;
300
301 let (connection, generation) = origin_client.connection().await?;
302 match send(connection.clone()).await {
303 Ok(output) => Ok((output, connection)),
304 Err(SendError::Transport(H3RequestError::Establish(_))) => {
305 let (connection, _) = origin_client.reconnect(generation).await?;
306 let output = send(connection.clone())
307 .await
308 .map_err(|e| e.into_error(&host, port))?;
309 Ok((output, connection))
310 }
311 Err(e) => Err(e.into_error(&host, port)),
312 }
313 }
314
315 async fn current_epoch(&self) -> Result<Arc<Epoch>, Error> {
323 {
324 let slot = self.epoch.read().await;
325 match &*slot {
326 EpochSlot::Closed => return Err(Error::Closed),
327 EpochSlot::Active(epoch, built)
328 if *built == self.reset_generation.load(Ordering::Acquire) =>
329 {
330 return Ok(epoch.clone());
331 }
332 _ => {}
333 }
334 }
335
336 let mut slot = self.epoch.write().await;
337 match &*slot {
338 EpochSlot::Closed => Err(Error::Closed),
339 EpochSlot::Active(epoch, built)
340 if *built == self.reset_generation.load(Ordering::Acquire) =>
341 {
342 Ok(epoch.clone())
343 }
344 _ => {
345 let generation = self.reset_generation.load(Ordering::Acquire);
350 let fresh = Arc::new(self.build_epoch().await?);
351 let previous =
352 std::mem::replace(&mut *slot, EpochSlot::Active(fresh.clone(), generation));
353 if let EpochSlot::Active(previous, _) = previous {
354 tracing::debug!("Replacing SCION connectivity epoch");
357 tokio::spawn(previous.shutdown());
358 }
359 Ok(fresh)
360 }
361 }
362 }
363
364 async fn build_epoch(&self) -> Result<Epoch, Error> {
365 #[cfg(test)]
366 if let Some(factory) = &self.epoch_factory {
367 return factory().await;
368 }
369 Epoch::build(&self.config).await
370 }
371}
372
373enum SendError {
375 Transport(H3RequestError),
376 Request(Error),
377}
378
379impl SendError {
380 fn into_error(self, host: &str, port: u16) -> Error {
381 match self {
382 SendError::Transport(e) => Error::from_h3_request_error(host, port, e),
383 SendError::Request(e) => e,
384 }
385 }
386}
387
388impl From<H3RequestError> for SendError {
389 fn from(e: H3RequestError) -> Self {
390 SendError::Transport(e)
391 }
392}
393
394impl From<Error> for SendError {
395 fn from(e: Error) -> Self {
396 SendError::Request(e)
397 }
398}
399
400fn deadline_after(timeout: Duration) -> tokio::time::Instant {
403 let now = tokio::time::Instant::now();
404 now.checked_add(timeout)
405 .unwrap_or_else(|| now + Duration::from_hours(24 * 365 * 30))
406}
407
408#[cfg(test)]
409mod tests {
410 use std::{
411 sync::atomic::{AtomicUsize, Ordering::SeqCst},
412 time::Duration,
413 };
414
415 use scion_quic::quic::config::QuicConfig;
416 use test_log::test;
417 use tokio::io::{AsyncReadExt, AsyncWriteExt};
418
419 use super::*;
420 use crate::test_support::{
421 FailingResolver, SERVER_PORT, StaticResolver, TestServerHarness, TunnelEchoService,
422 server_scion_ip, test_config, test_router,
423 };
424
425 fn harness_client(
428 harness: Arc<TestServerHarness>,
429 resolver: Arc<dyn scion_stack::resolver::ScionDnsResolver>,
430 config: Config,
431 ) -> (Client, Arc<AtomicUsize>) {
432 let builds = Arc::new(AtomicUsize::new(0));
433 let factory_builds = builds.clone();
434 let factory_config = config.clone();
435 let client = Client::with_epoch_factory(
436 config,
437 Box::new(move || {
438 factory_builds.fetch_add(1, SeqCst);
439 let harness = harness.clone();
440 let resolver = resolver.clone();
441 let config = factory_config.clone();
442 Box::pin(async move { Ok(Epoch::new(harness, resolver, &config)) })
443 }),
444 );
445 (client, builds)
446 }
447
448 fn url(host: &str, path: &str) -> String {
449 format!("https://{host}:{SERVER_PORT}{path}")
450 }
451
452 fn authority(host: &str) -> Authority {
453 Authority::new(host, SERVER_PORT).unwrap()
454 }
455
456 fn tunnel_client(config: Config) -> (Client, Arc<TestServerHarness>, TunnelEchoService) {
457 let service = TunnelEchoService::default();
458 let harness = TestServerHarness::with_tunnel_service(service.clone());
459 let resolver = StaticResolver::new(vec![server_scion_ip()]);
460 let (client, _) = harness_client(harness.clone(), resolver, config);
461 (client, harness, service)
462 }
463
464 async fn echo_round_trip(tunnel: &mut Tunnel, payload: &[u8]) {
465 tunnel.write_all(payload).await.unwrap();
466 tunnel.flush().await.unwrap();
467 let mut echoed = vec![0u8; payload.len()];
468 tunnel.read_exact(&mut echoed).await.unwrap();
469 assert_eq!(echoed, payload);
470 }
471
472 #[test(tokio::test)]
473 #[ntest::timeout(10_000)]
474 async fn connect_tunnels_bytes_both_ways() {
475 let (client, _, service) = tunnel_client(test_config());
476
477 let mut tunnel = client.connect(&authority("localhost")).await.unwrap();
478 for round in 0..3 {
479 echo_round_trip(&mut tunnel, format!("chunk-{round}").as_bytes()).await;
480 }
481 assert_eq!(
482 service.requests(),
483 vec![(
484 http::Method::CONNECT,
485 Some(authority("localhost").to_string())
486 )]
487 );
488
489 tunnel.shutdown().await.unwrap();
490 let mut buf = [0u8; 16];
491 assert_eq!(tunnel.read(&mut buf).await.unwrap(), 0);
492 }
493
494 #[test(tokio::test)]
495 #[ntest::timeout(10_000)]
496 async fn connect_via_sends_the_target_to_the_tunnel_server() {
497 let (client, _, service) = tunnel_client(test_config());
498 let target = Authority::new("backend.test", 8443).unwrap();
499
500 let mut tunnel = client
501 .connect_via(&authority("localhost"), &target)
502 .await
503 .unwrap();
504 echo_round_trip(&mut tunnel, b"through the tunnel server").await;
505 assert_eq!(
506 service.requests(),
507 vec![(http::Method::CONNECT, Some(target.to_string()))]
508 );
509 }
510
511 fn verifying_config() -> Config {
514 let mut config = test_config();
515 config.quic.verify_peer = true;
516 config
517 }
518
519 #[test(tokio::test)]
520 #[ntest::timeout(10_000)]
521 async fn connect_via_unverified_skips_the_peer_check() {
522 let (client, _, service) = tunnel_client(verifying_config());
523 let target = Authority::new("backend.test", 5222).unwrap();
524
525 let mut tunnel = client
526 .connect_via_unverified(&authority("localhost"), &target)
527 .await
528 .unwrap();
529 echo_round_trip(&mut tunnel, b"through the tunnel server").await;
530 assert_eq!(
531 service.requests(),
532 vec![(http::Method::CONNECT, Some(target.to_string()))]
533 );
534 }
535
536 #[test(tokio::test)]
537 #[ntest::timeout(10_000)]
538 async fn an_unverified_connection_does_not_carry_a_verified_tunnel() {
539 let (client, ..) = tunnel_client(verifying_config());
540 let _unverified = client
541 .connect_via_unverified(&authority("localhost"), &authority("localhost"))
542 .await
543 .unwrap();
544
545 let err = client.connect(&authority("localhost")).await.err().unwrap();
546 assert!(
547 matches!(err, Error::Connect { .. } | Error::Tls { .. }),
548 "{err:?}"
549 );
550 }
551
552 #[test(tokio::test)]
553 #[ntest::timeout(10_000)]
554 async fn connect_maps_non_2xx_to_tunnel_refused() {
555 let (client, _, service) = tunnel_client(test_config());
556
557 service.set_status(http::StatusCode::NOT_FOUND);
558 let err = client.connect(&authority("localhost")).await.err().unwrap();
559 assert!(
560 matches!(
561 err,
562 Error::TunnelRefused {
563 status: http::StatusCode::NOT_FOUND
564 }
565 ),
566 "{err}"
567 );
568 assert!(!err.is_retryable());
569
570 service.set_status(http::StatusCode::BAD_GATEWAY);
571 let err = client.connect(&authority("localhost")).await.err().unwrap();
572 assert!(
573 matches!(
574 err,
575 Error::TunnelRefused {
576 status: http::StatusCode::BAD_GATEWAY
577 }
578 ),
579 "{err}"
580 );
581 assert!(err.is_retryable());
582 }
583
584 #[test(tokio::test)]
585 #[ntest::timeout(10_000)]
586 async fn connect_shares_the_pooled_connection() {
587 let (client, harness, _) = tunnel_client(test_config());
588
589 let mut first = client.connect(&authority("localhost")).await.unwrap();
590 let mut second = client.connect(&authority("localhost")).await.unwrap();
591 echo_round_trip(&mut first, b"first").await;
592 echo_round_trip(&mut second, b"second").await;
593 assert_eq!(harness.binds.load(SeqCst), 1);
594 }
595
596 #[test(tokio::test)]
597 #[ntest::timeout(10_000)]
598 async fn connect_reuses_the_establish_retry() {
599 let (client, harness, _) = tunnel_client(test_config());
600
601 let tunnel = client.connect(&authority("localhost")).await.unwrap();
602 assert_eq!(harness.binds.load(SeqCst), 1);
603 drop(tunnel);
604
605 harness.break_sockets();
606 tokio::time::sleep(Duration::from_millis(200)).await;
607
608 let mut tunnel = client.connect(&authority("localhost")).await.unwrap();
609 echo_round_trip(&mut tunnel, b"after reconnect").await;
610 assert_eq!(harness.binds.load(SeqCst), 2);
611 }
612
613 #[test(tokio::test)]
614 #[ntest::timeout(10_000)]
615 async fn tunnel_survives_origin_eviction() {
616 let (client, ..) = tunnel_client(test_config().with_max_origins(1));
617
618 let mut tunnel = client.connect(&authority("a.local")).await.unwrap();
619 echo_round_trip(&mut tunnel, b"before eviction").await;
620
621 let mut other = client.connect(&authority("b.local")).await.unwrap();
622 echo_round_trip(&mut other, b"other origin").await;
623
624 echo_round_trip(&mut tunnel, b"after eviction").await;
625 }
626
627 #[test(tokio::test)]
628 #[ntest::timeout(10_000)]
629 async fn close_faults_open_tunnel() {
630 let (client, ..) = tunnel_client(test_config());
631 let client = Arc::new(client);
632
633 let mut tunnel = client.connect(&authority("localhost")).await.unwrap();
634 let pending = tokio::spawn(async move {
635 let mut buf = [0u8; 16];
636 tunnel.read(&mut buf).await
637 });
638 tokio::time::sleep(Duration::from_millis(100)).await;
639 client.close().await;
640
641 let start = tokio::time::Instant::now();
642 assert!(pending.await.unwrap().is_err());
643 assert!(start.elapsed() < Duration::from_millis(300));
644 }
645
646 #[test(tokio::test)]
647 #[ntest::timeout(10_000)]
648 async fn get_resolves_via_dns_path() {
649 let (router, hits) = test_router();
650 let harness = TestServerHarness::new(router);
651 let resolver = StaticResolver::new(vec![server_scion_ip()]);
652 let (client, _) = harness_client(harness, resolver, test_config());
653
654 let response = client.get(url("localhost", "/hello")).await.unwrap();
655 assert!(response.is_success());
656 let (body, _) = response.text(Some(1024)).await.unwrap();
657 assert_eq!(body, "world");
658 assert_eq!(hits.load(SeqCst), 1);
659 }
660
661 #[test(tokio::test)]
662 #[ntest::timeout(10_000)]
663 async fn duration_max_timeout_does_not_panic() {
664 let (router, _) = test_router();
665 let harness = TestServerHarness::new(router);
666 let resolver = StaticResolver::new(vec![server_scion_ip()]);
667 let (client, _) = harness_client(harness, resolver, test_config());
668
669 let request = Request::get(url("localhost", "/hello"))
670 .request_timeout(Duration::MAX)
671 .build()
672 .unwrap();
673 let response = client.request(request).await.unwrap();
674 assert!(response.is_success());
675 }
676
677 #[test(tokio::test)]
678 #[ntest::timeout(10_000)]
679 async fn resolution_failure_surfaces_as_retryable_resolution_error() {
680 let (router, _) = test_router();
681 let harness = TestServerHarness::new(router);
682 let (client, _) = harness_client(harness, Arc::new(FailingResolver), test_config());
683
684 let err = client.get(url("localhost", "/hello")).await.err().unwrap();
685 assert!(matches!(err, Error::Resolution { .. }), "{err}");
686 assert!(err.is_retryable());
687 }
688
689 #[test(tokio::test)]
690 #[ntest::timeout(10_000)]
691 async fn establish_failure_reconnects_once_and_retries() {
692 let (router, hits) = test_router();
693 let harness = TestServerHarness::new(router);
694 let resolver = StaticResolver::new(vec![server_scion_ip()]);
695 let (client, _) = harness_client(harness.clone(), resolver, test_config());
696
697 let response = client.get(url("localhost", "/hello")).await.unwrap();
698 assert!(response.is_success());
699 assert_eq!(harness.binds.load(SeqCst), 1);
700
701 harness.break_sockets();
706 tokio::time::sleep(Duration::from_millis(200)).await;
709
710 let response = client.get(url("localhost", "/hello")).await.unwrap();
711 assert!(response.is_success());
712 assert_eq!(harness.binds.load(SeqCst), 2);
713 assert_eq!(hits.load(SeqCst), 2);
714 }
715
716 #[test(tokio::test)]
717 #[ntest::timeout(10_000)]
718 async fn stream_level_errors_are_not_retried() {
719 let (router, _) = test_router();
720 let mut server_quic = QuicConfig::builder().verify_peer(false).build();
723 server_quic.initial_max_streams_bidi = 0;
724 let harness = TestServerHarness::with_server_quic(router, server_quic);
725 let resolver = StaticResolver::new(vec![server_scion_ip()]);
726 let (client, _) = harness_client(harness.clone(), resolver, test_config());
727
728 let err = client.get(url("localhost", "/hello")).await.err().unwrap();
729 assert!(matches!(err, Error::ConnectionLimit), "{err}");
730 assert_eq!(harness.binds.load(SeqCst), 1);
733 }
734
735 #[test(tokio::test)]
736 #[ntest::timeout(10_000)]
737 async fn reset_is_lazy_and_rebuild_is_single_flight() {
738 let (router, _) = test_router();
739 let harness = TestServerHarness::new(router);
740 let resolver = StaticResolver::new(vec![server_scion_ip()]);
741 let (client, builds) = harness_client(harness, resolver, test_config());
742 let client = Arc::new(client);
743
744 client.get(url("localhost", "/hello")).await.unwrap();
745 assert_eq!(builds.load(SeqCst), 1);
746
747 client.reset();
749 assert_eq!(builds.load(SeqCst), 1);
750
751 let tasks: Vec<_> = (0..3)
754 .map(|_| {
755 let client = client.clone();
756 tokio::spawn(async move { client.get(url("localhost", "/hello")).await })
757 })
758 .collect();
759 for task in tasks {
760 task.await.unwrap().unwrap();
761 }
762 assert_eq!(builds.load(SeqCst), 2);
763 }
764
765 #[test(tokio::test)]
766 #[ntest::timeout(10_000)]
767 async fn reset_during_rebuild_triggers_another_rebuild() {
768 let (router, _) = test_router();
769 let harness = TestServerHarness::new(router);
770 let resolver = StaticResolver::new(vec![server_scion_ip()]);
771 let builds = Arc::new(AtomicUsize::new(0));
772 let gate = Arc::new(tokio::sync::Semaphore::new(1));
775 let factory_builds = builds.clone();
776 let factory_gate = gate.clone();
777 let config = test_config();
778 let factory_config = config.clone();
779 let client = Arc::new(Client::with_epoch_factory(
780 config,
781 Box::new(move || {
782 let builds = factory_builds.clone();
783 let gate = factory_gate.clone();
784 let harness = harness.clone();
785 let resolver = resolver.clone();
786 let config = factory_config.clone();
787 Box::pin(async move {
788 gate.acquire().await.expect("gate closed").forget();
789 builds.fetch_add(1, SeqCst);
790 Ok(Epoch::new(harness, resolver, &config))
791 })
792 }),
793 ));
794
795 client.get(url("localhost", "/hello")).await.unwrap();
796 assert_eq!(builds.load(SeqCst), 1);
797
798 client.reset();
800 let pending = {
801 let client = client.clone();
802 tokio::spawn(async move { client.get(url("localhost", "/hello")).await })
803 };
804 tokio::time::sleep(Duration::from_millis(100)).await;
805 assert_eq!(builds.load(SeqCst), 1);
806
807 client.reset();
810 gate.add_permits(2);
811 pending.await.unwrap().unwrap();
812 assert_eq!(builds.load(SeqCst), 2);
813
814 client.get(url("localhost", "/hello")).await.unwrap();
818 assert_eq!(builds.load(SeqCst), 3);
819 }
820
821 #[test(tokio::test)]
822 #[ntest::timeout(10_000)]
823 async fn failed_rebuild_keeps_stale_and_retries_next_request() {
824 let (router, _) = test_router();
825 let harness = TestServerHarness::new(router);
826 let resolver = StaticResolver::new(vec![server_scion_ip()]);
827 let builds = Arc::new(AtomicUsize::new(0));
828 let factory_builds = builds.clone();
829 let config = test_config();
830 let factory_config = config.clone();
831 let client = Client::with_epoch_factory(
832 config,
833 Box::new(move || {
834 let build = factory_builds.fetch_add(1, SeqCst) + 1;
835 let harness = harness.clone();
836 let resolver = resolver.clone();
837 let config = factory_config.clone();
838 Box::pin(async move {
839 if build == 2 {
840 return Err(Error::StackBuild {
841 retryable: true,
842 source: "flaky discovery".into(),
843 });
844 }
845 Ok(Epoch::new(harness, resolver, &config))
846 })
847 }),
848 );
849
850 client.get(url("localhost", "/hello")).await.unwrap();
851 client.reset();
852
853 let err = client.get(url("localhost", "/hello")).await.err().unwrap();
855 assert!(matches!(err, Error::StackBuild { .. }), "{err}");
856 assert!(err.is_retryable());
857
858 client.get(url("localhost", "/hello")).await.unwrap();
860 assert_eq!(builds.load(SeqCst), 3);
861 }
862
863 #[test(tokio::test)]
864 #[ntest::timeout(10_000)]
865 async fn epochs_do_not_share_stacks_across_reset() {
866 let (router_one, _) = test_router();
868 let (router_two, _) = test_router();
869 let first = TestServerHarness::new(router_one);
870 let second = TestServerHarness::new(router_two);
871 let resolver = StaticResolver::new(vec![server_scion_ip()]);
872 let builds = Arc::new(AtomicUsize::new(0));
873 let factory_builds = builds.clone();
874 let config = test_config();
875 let factory_config = config.clone();
876 let factory_first = first.clone();
877 let factory_second = second.clone();
878 let client = Client::with_epoch_factory(
879 config,
880 Box::new(move || {
881 let build = factory_builds.fetch_add(1, SeqCst) + 1;
882 let harness = if build == 1 {
883 factory_first.clone()
884 } else {
885 factory_second.clone()
886 };
887 let resolver = resolver.clone();
888 let config = factory_config.clone();
889 Box::pin(async move { Ok(Epoch::new(harness, resolver, &config)) })
890 }),
891 );
892
893 client.get(url("localhost", "/hello")).await.unwrap();
894 assert_eq!(
895 (first.binds.load(SeqCst), second.binds.load(SeqCst)),
896 (1, 0)
897 );
898
899 client.reset();
900 client.get(url("localhost", "/hello")).await.unwrap();
901 assert_eq!(
902 (first.binds.load(SeqCst), second.binds.load(SeqCst)),
903 (1, 1)
904 );
905 }
906
907 #[test(tokio::test)]
908 #[ntest::timeout(10_000)]
909 async fn stalled_establishment_does_not_block_other_origins() {
910 let (router, _) = test_router();
913 let harness = TestServerHarness::new(router);
914 harness.stall_first_bind();
915 let resolver = StaticResolver::new(vec![server_scion_ip()]);
916 let (client, _) = harness_client(harness.clone(), resolver, test_config());
917 let client = Arc::new(client);
918
919 let stalled = {
920 let client = client.clone();
921 tokio::spawn(async move { client.get(url("a.local", "/hello")).await })
922 };
923 tokio::time::sleep(Duration::from_millis(100)).await;
924 assert!(!stalled.is_finished());
925
926 let response = client.get(url("b.local", "/hello")).await.unwrap();
929 assert!(response.is_success());
930 assert!(!stalled.is_finished());
931
932 harness.release_stalled();
933 stalled.await.unwrap().unwrap();
934 }
935
936 #[test(tokio::test)]
937 #[ntest::timeout(10_000)]
938 async fn evicted_origin_completes_in_flight_request() {
939 let (router, _) = test_router();
942 let harness = TestServerHarness::new(router);
943 let resolver = StaticResolver::new(vec![server_scion_ip()]);
944 let (client, _) = harness_client(harness, resolver, test_config().with_max_origins(1));
945 let client = Arc::new(client);
946
947 let slow = {
948 let client = client.clone();
949 tokio::spawn(async move {
950 let response = client.get(url("a.local", "/slow")).await?;
951 response.text(Some(1024)).await
952 })
953 };
954 tokio::time::sleep(Duration::from_millis(150)).await;
955
956 client.get(url("b.local", "/hello")).await.unwrap();
959
960 let (body, _) = slow.await.unwrap().unwrap();
961 assert_eq!(body, "slow");
962 }
963
964 #[test(tokio::test)]
965 #[ntest::timeout(10_000)]
966 async fn close_rejects_new_requests_and_faults_in_flight() {
967 let (router, _) = test_router();
968 let harness = TestServerHarness::new(router);
969 let resolver = StaticResolver::new(vec![server_scion_ip()]);
970 let (client, _) = harness_client(harness, resolver, test_config());
971 let client = Arc::new(client);
972
973 client.get(url("localhost", "/hello")).await.unwrap();
974
975 let in_flight = {
978 let client = client.clone();
979 tokio::spawn(async move {
980 let response = client.get(url("localhost", "/slow")).await?;
981 response.text(Some(1024)).await
982 })
983 };
984 tokio::time::sleep(Duration::from_millis(100)).await;
985 client.close().await;
986
987 let start = tokio::time::Instant::now();
988 let result = in_flight.await.unwrap();
989 assert!(result.is_err());
990 assert!(start.elapsed() < Duration::from_millis(300));
991
992 let err = client.get(url("localhost", "/hello")).await.err().unwrap();
994 assert!(matches!(err, Error::Closed), "{err}");
995 client.close().await;
996 }
997
998 #[test(tokio::test)]
999 #[ntest::timeout(10_000)]
1000 async fn per_request_timeout_overrides_the_default() {
1001 let (router, _) = test_router();
1002 let harness = TestServerHarness::new(router);
1003 let resolver = StaticResolver::new(vec![server_scion_ip()]);
1004 let (client, _) = harness_client(harness, resolver, test_config());
1005
1006 let request = Request::get(url("localhost", "/slow"))
1007 .request_timeout(Duration::from_millis(100))
1008 .build()
1009 .unwrap();
1010 let err = client.request(request).await.err().unwrap();
1011 assert!(
1012 matches!(
1013 err,
1014 Error::Timeout {
1015 phase: TimeoutPhase::Request,
1016 ..
1017 }
1018 ),
1019 "{err}"
1020 );
1021 }
1022
1023 #[test(tokio::test)]
1024 #[ntest::timeout(10_000)]
1025 async fn body_collection_respects_max_size() {
1026 let (router, _) = test_router();
1027 let harness = TestServerHarness::new(router);
1028 let resolver = StaticResolver::new(vec![server_scion_ip()]);
1029 let (client, _) = harness_client(harness, resolver, test_config());
1030
1031 let response = client.get(url("localhost", "/hello")).await.unwrap();
1032 let err = response.bytes(Some(2)).await.err().unwrap();
1033 assert!(matches!(err, Error::BodyTooLarge { limit: 2 }), "{err}");
1034 }
1035
1036 #[test(tokio::test)]
1037 #[ntest::timeout(10_000)]
1038 async fn warm_up_establishes_without_a_request() {
1039 let (router, hits) = test_router();
1040 let harness = TestServerHarness::new(router);
1041 let resolver = StaticResolver::new(vec![server_scion_ip()]);
1042 let (client, builds) = harness_client(harness.clone(), resolver, test_config());
1043
1044 client.warm_up(url("localhost", "/")).await.unwrap();
1045 assert_eq!(builds.load(SeqCst), 1);
1046 assert_eq!(harness.binds.load(SeqCst), 1);
1047 assert_eq!(hits.load(SeqCst), 0);
1048
1049 client.get(url("localhost", "/hello")).await.unwrap();
1051 assert_eq!(harness.binds.load(SeqCst), 1);
1052 assert_eq!(hits.load(SeqCst), 1);
1053 }
1054}