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(
177 &self,
178 proxy: &Authority,
179 target: &Authority,
180 ) -> Result<Tunnel, Error> {
181 let timeout = self.config.request_timeout;
182 tokio::time::timeout_at(deadline_after(timeout), self.connect_tunnel(proxy, target))
183 .await
184 .map_err(|_| {
185 Error::Timeout {
186 phase: TimeoutPhase::Request,
187 timeout,
188 }
189 })?
190 }
191
192 pub async fn warm_up(&self, url: impl IntoUrl) -> Result<(), Error> {
197 let request = Request::get(url).build()?;
198 let origin = Origin::from_request(&request)?;
199 let epoch = self.current_epoch().await?;
200 let origin_client = epoch.origin_client(origin, Instant::now(), &self.config)?;
201 origin_client.connection().await?;
202 Ok(())
203 }
204
205 pub fn reset(&self) {
213 self.reset_generation.fetch_add(1, Ordering::Release);
214 }
215
216 pub async fn close(&self) {
220 let previous = {
221 let mut slot = self.epoch.write().await;
222 std::mem::replace(&mut *slot, EpochSlot::Closed)
223 };
224 if let EpochSlot::Active(epoch, _) = previous {
225 epoch.shutdown().await;
226 }
227 }
228
229 async fn request_head(
234 &self,
235 request: &Request,
236 ) -> Result<(http::Response<H3ResponseBody>, Arc<Http3Client>), Error> {
237 let origin = Origin::from_request(request)?;
238 self.with_connection(origin, |connection| {
239 async move { Ok(connection.request(request.to_http()?).await?) }
240 })
241 .await
242 }
243
244 async fn connect_tunnel(&self, proxy: &Authority, target: &Authority) -> Result<Tunnel, Error> {
247 let origin = Origin::from_authority(proxy);
248 let (host, port) = (origin.host.clone(), origin.port);
249 let ((response, writer), connection) = self
250 .with_connection(origin, |connection| {
251 async move {
252 Ok(connection
253 .request_with_writer(connect_request(target))
254 .await?)
255 }
256 })
257 .await?;
258 let response = response
259 .await
260 .map_err(|e| Error::from_h3_request_error(&host, port, e))?;
261 if !response.status().is_success() {
262 return Err(Error::TunnelRefused {
263 status: response.status(),
264 });
265 }
266 let stream = H3DuplexStream::new(writer, response.into_body());
267 Ok(Tunnel::new(stream, connection))
268 }
269
270 async fn with_connection<T, F, Fut>(
276 &self,
277 origin: Origin,
278 send: F,
279 ) -> Result<(T, Arc<Http3Client>), Error>
280 where
281 F: Fn(Arc<Http3Client>) -> Fut,
282 Fut: Future<Output = Result<T, SendError>>,
283 {
284 let (host, port) = (origin.host.clone(), origin.port);
285 let epoch = self.current_epoch().await?;
286 let origin_client = epoch.origin_client(origin, Instant::now(), &self.config)?;
287
288 let (connection, generation) = origin_client.connection().await?;
289 match send(connection.clone()).await {
290 Ok(output) => Ok((output, connection)),
291 Err(SendError::Transport(H3RequestError::Establish(_))) => {
292 let (connection, _) = origin_client.reconnect(generation).await?;
293 let output = send(connection.clone())
294 .await
295 .map_err(|e| e.into_error(&host, port))?;
296 Ok((output, connection))
297 }
298 Err(e) => Err(e.into_error(&host, port)),
299 }
300 }
301
302 async fn current_epoch(&self) -> Result<Arc<Epoch>, Error> {
310 {
311 let slot = self.epoch.read().await;
312 match &*slot {
313 EpochSlot::Closed => return Err(Error::Closed),
314 EpochSlot::Active(epoch, built)
315 if *built == self.reset_generation.load(Ordering::Acquire) =>
316 {
317 return Ok(epoch.clone());
318 }
319 _ => {}
320 }
321 }
322
323 let mut slot = self.epoch.write().await;
324 match &*slot {
325 EpochSlot::Closed => Err(Error::Closed),
326 EpochSlot::Active(epoch, built)
327 if *built == self.reset_generation.load(Ordering::Acquire) =>
328 {
329 Ok(epoch.clone())
330 }
331 _ => {
332 let generation = self.reset_generation.load(Ordering::Acquire);
337 let fresh = Arc::new(self.build_epoch().await?);
338 let previous =
339 std::mem::replace(&mut *slot, EpochSlot::Active(fresh.clone(), generation));
340 if let EpochSlot::Active(previous, _) = previous {
341 tracing::debug!("Replacing SCION connectivity epoch");
344 tokio::spawn(previous.shutdown());
345 }
346 Ok(fresh)
347 }
348 }
349 }
350
351 async fn build_epoch(&self) -> Result<Epoch, Error> {
352 #[cfg(test)]
353 if let Some(factory) = &self.epoch_factory {
354 return factory().await;
355 }
356 Epoch::build(&self.config).await
357 }
358}
359
360enum SendError {
362 Transport(H3RequestError),
363 Request(Error),
364}
365
366impl SendError {
367 fn into_error(self, host: &str, port: u16) -> Error {
368 match self {
369 SendError::Transport(e) => Error::from_h3_request_error(host, port, e),
370 SendError::Request(e) => e,
371 }
372 }
373}
374
375impl From<H3RequestError> for SendError {
376 fn from(e: H3RequestError) -> Self {
377 SendError::Transport(e)
378 }
379}
380
381impl From<Error> for SendError {
382 fn from(e: Error) -> Self {
383 SendError::Request(e)
384 }
385}
386
387fn deadline_after(timeout: Duration) -> tokio::time::Instant {
390 let now = tokio::time::Instant::now();
391 now.checked_add(timeout)
392 .unwrap_or_else(|| now + Duration::from_hours(24 * 365 * 30))
393}
394
395#[cfg(test)]
396mod tests {
397 use std::{
398 sync::atomic::{AtomicUsize, Ordering::SeqCst},
399 time::Duration,
400 };
401
402 use scion_quic::quic::config::QuicConfig;
403 use test_log::test;
404 use tokio::io::{AsyncReadExt, AsyncWriteExt};
405
406 use super::*;
407 use crate::test_support::{
408 FailingResolver, SERVER_PORT, StaticResolver, TestServerHarness, TunnelEchoService,
409 server_scion_ip, test_config, test_router,
410 };
411
412 fn harness_client(
415 harness: Arc<TestServerHarness>,
416 resolver: Arc<dyn scion_stack::resolver::ScionDnsResolver>,
417 config: Config,
418 ) -> (Client, Arc<AtomicUsize>) {
419 let builds = Arc::new(AtomicUsize::new(0));
420 let factory_builds = builds.clone();
421 let factory_config = config.clone();
422 let client = Client::with_epoch_factory(
423 config,
424 Box::new(move || {
425 factory_builds.fetch_add(1, SeqCst);
426 let harness = harness.clone();
427 let resolver = resolver.clone();
428 let config = factory_config.clone();
429 Box::pin(async move { Ok(Epoch::new(harness, resolver, &config)) })
430 }),
431 );
432 (client, builds)
433 }
434
435 fn url(host: &str, path: &str) -> String {
436 format!("https://{host}:{SERVER_PORT}{path}")
437 }
438
439 fn authority(host: &str) -> Authority {
440 Authority::new(host, SERVER_PORT).unwrap()
441 }
442
443 fn tunnel_client(config: Config) -> (Client, Arc<TestServerHarness>, TunnelEchoService) {
444 let service = TunnelEchoService::default();
445 let harness = TestServerHarness::with_tunnel_service(service.clone());
446 let resolver = StaticResolver::new(vec![server_scion_ip()]);
447 let (client, _) = harness_client(harness.clone(), resolver, config);
448 (client, harness, service)
449 }
450
451 async fn echo_round_trip(tunnel: &mut Tunnel, payload: &[u8]) {
452 tunnel.write_all(payload).await.unwrap();
453 tunnel.flush().await.unwrap();
454 let mut echoed = vec![0u8; payload.len()];
455 tunnel.read_exact(&mut echoed).await.unwrap();
456 assert_eq!(echoed, payload);
457 }
458
459 #[test(tokio::test)]
460 #[ntest::timeout(10_000)]
461 async fn connect_tunnels_bytes_both_ways() {
462 let (client, _, service) = tunnel_client(test_config());
463
464 let mut tunnel = client.connect(&authority("localhost")).await.unwrap();
465 for round in 0..3 {
466 echo_round_trip(&mut tunnel, format!("chunk-{round}").as_bytes()).await;
467 }
468 assert_eq!(
469 service.requests(),
470 vec![(
471 http::Method::CONNECT,
472 Some(authority("localhost").to_string())
473 )]
474 );
475
476 tunnel.shutdown().await.unwrap();
477 let mut buf = [0u8; 16];
478 assert_eq!(tunnel.read(&mut buf).await.unwrap(), 0);
479 }
480
481 #[test(tokio::test)]
482 #[ntest::timeout(10_000)]
483 async fn connect_via_sends_the_target_to_the_proxy() {
484 let (client, _, service) = tunnel_client(test_config());
485 let target = Authority::new("backend.test", 8443).unwrap();
486
487 let mut tunnel = client
488 .connect_via(&authority("localhost"), &target)
489 .await
490 .unwrap();
491 echo_round_trip(&mut tunnel, b"through the proxy").await;
492 assert_eq!(
493 service.requests(),
494 vec![(http::Method::CONNECT, Some(target.to_string()))]
495 );
496 }
497
498 #[test(tokio::test)]
499 #[ntest::timeout(10_000)]
500 async fn connect_maps_non_2xx_to_tunnel_refused() {
501 let (client, _, service) = tunnel_client(test_config());
502
503 service.set_status(http::StatusCode::NOT_FOUND);
504 let err = client.connect(&authority("localhost")).await.err().unwrap();
505 assert!(
506 matches!(
507 err,
508 Error::TunnelRefused {
509 status: http::StatusCode::NOT_FOUND
510 }
511 ),
512 "{err}"
513 );
514 assert!(!err.is_retryable());
515
516 service.set_status(http::StatusCode::BAD_GATEWAY);
517 let err = client.connect(&authority("localhost")).await.err().unwrap();
518 assert!(
519 matches!(
520 err,
521 Error::TunnelRefused {
522 status: http::StatusCode::BAD_GATEWAY
523 }
524 ),
525 "{err}"
526 );
527 assert!(err.is_retryable());
528 }
529
530 #[test(tokio::test)]
531 #[ntest::timeout(10_000)]
532 async fn connect_shares_the_pooled_connection() {
533 let (client, harness, _) = tunnel_client(test_config());
534
535 let mut first = client.connect(&authority("localhost")).await.unwrap();
536 let mut second = client.connect(&authority("localhost")).await.unwrap();
537 echo_round_trip(&mut first, b"first").await;
538 echo_round_trip(&mut second, b"second").await;
539 assert_eq!(harness.binds.load(SeqCst), 1);
540 }
541
542 #[test(tokio::test)]
543 #[ntest::timeout(10_000)]
544 async fn connect_reuses_the_establish_retry() {
545 let (client, harness, _) = tunnel_client(test_config());
546
547 let tunnel = client.connect(&authority("localhost")).await.unwrap();
548 assert_eq!(harness.binds.load(SeqCst), 1);
549 drop(tunnel);
550
551 harness.break_sockets();
552 tokio::time::sleep(Duration::from_millis(200)).await;
553
554 let mut tunnel = client.connect(&authority("localhost")).await.unwrap();
555 echo_round_trip(&mut tunnel, b"after reconnect").await;
556 assert_eq!(harness.binds.load(SeqCst), 2);
557 }
558
559 #[test(tokio::test)]
560 #[ntest::timeout(10_000)]
561 async fn tunnel_survives_origin_eviction() {
562 let (client, ..) = tunnel_client(test_config().with_max_origins(1));
563
564 let mut tunnel = client.connect(&authority("a.local")).await.unwrap();
565 echo_round_trip(&mut tunnel, b"before eviction").await;
566
567 let mut other = client.connect(&authority("b.local")).await.unwrap();
568 echo_round_trip(&mut other, b"other origin").await;
569
570 echo_round_trip(&mut tunnel, b"after eviction").await;
571 }
572
573 #[test(tokio::test)]
574 #[ntest::timeout(10_000)]
575 async fn close_faults_open_tunnel() {
576 let (client, ..) = tunnel_client(test_config());
577 let client = Arc::new(client);
578
579 let mut tunnel = client.connect(&authority("localhost")).await.unwrap();
580 let pending = tokio::spawn(async move {
581 let mut buf = [0u8; 16];
582 tunnel.read(&mut buf).await
583 });
584 tokio::time::sleep(Duration::from_millis(100)).await;
585 client.close().await;
586
587 let start = tokio::time::Instant::now();
588 assert!(pending.await.unwrap().is_err());
589 assert!(start.elapsed() < Duration::from_millis(300));
590 }
591
592 #[test(tokio::test)]
593 #[ntest::timeout(10_000)]
594 async fn get_resolves_via_dns_path() {
595 let (router, hits) = test_router();
596 let harness = TestServerHarness::new(router);
597 let resolver = StaticResolver::new(vec![server_scion_ip()]);
598 let (client, _) = harness_client(harness, resolver, test_config());
599
600 let response = client.get(url("localhost", "/hello")).await.unwrap();
601 assert!(response.is_success());
602 let (body, _) = response.text(Some(1024)).await.unwrap();
603 assert_eq!(body, "world");
604 assert_eq!(hits.load(SeqCst), 1);
605 }
606
607 #[test(tokio::test)]
608 #[ntest::timeout(10_000)]
609 async fn duration_max_timeout_does_not_panic() {
610 let (router, _) = test_router();
611 let harness = TestServerHarness::new(router);
612 let resolver = StaticResolver::new(vec![server_scion_ip()]);
613 let (client, _) = harness_client(harness, resolver, test_config());
614
615 let request = Request::get(url("localhost", "/hello"))
616 .request_timeout(Duration::MAX)
617 .build()
618 .unwrap();
619 let response = client.request(request).await.unwrap();
620 assert!(response.is_success());
621 }
622
623 #[test(tokio::test)]
624 #[ntest::timeout(10_000)]
625 async fn resolution_failure_surfaces_as_retryable_resolution_error() {
626 let (router, _) = test_router();
627 let harness = TestServerHarness::new(router);
628 let (client, _) = harness_client(harness, Arc::new(FailingResolver), test_config());
629
630 let err = client.get(url("localhost", "/hello")).await.err().unwrap();
631 assert!(matches!(err, Error::Resolution { .. }), "{err}");
632 assert!(err.is_retryable());
633 }
634
635 #[test(tokio::test)]
636 #[ntest::timeout(10_000)]
637 async fn establish_failure_reconnects_once_and_retries() {
638 let (router, hits) = test_router();
639 let harness = TestServerHarness::new(router);
640 let resolver = StaticResolver::new(vec![server_scion_ip()]);
641 let (client, _) = harness_client(harness.clone(), resolver, test_config());
642
643 let response = client.get(url("localhost", "/hello")).await.unwrap();
644 assert!(response.is_success());
645 assert_eq!(harness.binds.load(SeqCst), 1);
646
647 harness.break_sockets();
652 tokio::time::sleep(Duration::from_millis(200)).await;
655
656 let response = client.get(url("localhost", "/hello")).await.unwrap();
657 assert!(response.is_success());
658 assert_eq!(harness.binds.load(SeqCst), 2);
659 assert_eq!(hits.load(SeqCst), 2);
660 }
661
662 #[test(tokio::test)]
663 #[ntest::timeout(10_000)]
664 async fn stream_level_errors_are_not_retried() {
665 let (router, _) = test_router();
666 let mut server_quic = QuicConfig::builder().verify_peer(false).build();
669 server_quic.initial_max_streams_bidi = 0;
670 let harness = TestServerHarness::with_server_quic(router, server_quic);
671 let resolver = StaticResolver::new(vec![server_scion_ip()]);
672 let (client, _) = harness_client(harness.clone(), resolver, test_config());
673
674 let err = client.get(url("localhost", "/hello")).await.err().unwrap();
675 assert!(matches!(err, Error::ConnectionLimit), "{err}");
676 assert_eq!(harness.binds.load(SeqCst), 1);
679 }
680
681 #[test(tokio::test)]
682 #[ntest::timeout(10_000)]
683 async fn reset_is_lazy_and_rebuild_is_single_flight() {
684 let (router, _) = test_router();
685 let harness = TestServerHarness::new(router);
686 let resolver = StaticResolver::new(vec![server_scion_ip()]);
687 let (client, builds) = harness_client(harness, resolver, test_config());
688 let client = Arc::new(client);
689
690 client.get(url("localhost", "/hello")).await.unwrap();
691 assert_eq!(builds.load(SeqCst), 1);
692
693 client.reset();
695 assert_eq!(builds.load(SeqCst), 1);
696
697 let tasks: Vec<_> = (0..3)
700 .map(|_| {
701 let client = client.clone();
702 tokio::spawn(async move { client.get(url("localhost", "/hello")).await })
703 })
704 .collect();
705 for task in tasks {
706 task.await.unwrap().unwrap();
707 }
708 assert_eq!(builds.load(SeqCst), 2);
709 }
710
711 #[test(tokio::test)]
712 #[ntest::timeout(10_000)]
713 async fn reset_during_rebuild_triggers_another_rebuild() {
714 let (router, _) = test_router();
715 let harness = TestServerHarness::new(router);
716 let resolver = StaticResolver::new(vec![server_scion_ip()]);
717 let builds = Arc::new(AtomicUsize::new(0));
718 let gate = Arc::new(tokio::sync::Semaphore::new(1));
721 let factory_builds = builds.clone();
722 let factory_gate = gate.clone();
723 let config = test_config();
724 let factory_config = config.clone();
725 let client = Arc::new(Client::with_epoch_factory(
726 config,
727 Box::new(move || {
728 let builds = factory_builds.clone();
729 let gate = factory_gate.clone();
730 let harness = harness.clone();
731 let resolver = resolver.clone();
732 let config = factory_config.clone();
733 Box::pin(async move {
734 gate.acquire().await.expect("gate closed").forget();
735 builds.fetch_add(1, SeqCst);
736 Ok(Epoch::new(harness, resolver, &config))
737 })
738 }),
739 ));
740
741 client.get(url("localhost", "/hello")).await.unwrap();
742 assert_eq!(builds.load(SeqCst), 1);
743
744 client.reset();
746 let pending = {
747 let client = client.clone();
748 tokio::spawn(async move { client.get(url("localhost", "/hello")).await })
749 };
750 tokio::time::sleep(Duration::from_millis(100)).await;
751 assert_eq!(builds.load(SeqCst), 1);
752
753 client.reset();
756 gate.add_permits(2);
757 pending.await.unwrap().unwrap();
758 assert_eq!(builds.load(SeqCst), 2);
759
760 client.get(url("localhost", "/hello")).await.unwrap();
764 assert_eq!(builds.load(SeqCst), 3);
765 }
766
767 #[test(tokio::test)]
768 #[ntest::timeout(10_000)]
769 async fn failed_rebuild_keeps_stale_and_retries_next_request() {
770 let (router, _) = test_router();
771 let harness = TestServerHarness::new(router);
772 let resolver = StaticResolver::new(vec![server_scion_ip()]);
773 let builds = Arc::new(AtomicUsize::new(0));
774 let factory_builds = builds.clone();
775 let config = test_config();
776 let factory_config = config.clone();
777 let client = Client::with_epoch_factory(
778 config,
779 Box::new(move || {
780 let build = factory_builds.fetch_add(1, SeqCst) + 1;
781 let harness = harness.clone();
782 let resolver = resolver.clone();
783 let config = factory_config.clone();
784 Box::pin(async move {
785 if build == 2 {
786 return Err(Error::StackBuild {
787 retryable: true,
788 source: "flaky discovery".into(),
789 });
790 }
791 Ok(Epoch::new(harness, resolver, &config))
792 })
793 }),
794 );
795
796 client.get(url("localhost", "/hello")).await.unwrap();
797 client.reset();
798
799 let err = client.get(url("localhost", "/hello")).await.err().unwrap();
801 assert!(matches!(err, Error::StackBuild { .. }), "{err}");
802 assert!(err.is_retryable());
803
804 client.get(url("localhost", "/hello")).await.unwrap();
806 assert_eq!(builds.load(SeqCst), 3);
807 }
808
809 #[test(tokio::test)]
810 #[ntest::timeout(10_000)]
811 async fn epochs_do_not_share_stacks_across_reset() {
812 let (router_one, _) = test_router();
814 let (router_two, _) = test_router();
815 let first = TestServerHarness::new(router_one);
816 let second = TestServerHarness::new(router_two);
817 let resolver = StaticResolver::new(vec![server_scion_ip()]);
818 let builds = Arc::new(AtomicUsize::new(0));
819 let factory_builds = builds.clone();
820 let config = test_config();
821 let factory_config = config.clone();
822 let factory_first = first.clone();
823 let factory_second = second.clone();
824 let client = Client::with_epoch_factory(
825 config,
826 Box::new(move || {
827 let build = factory_builds.fetch_add(1, SeqCst) + 1;
828 let harness = if build == 1 {
829 factory_first.clone()
830 } else {
831 factory_second.clone()
832 };
833 let resolver = resolver.clone();
834 let config = factory_config.clone();
835 Box::pin(async move { Ok(Epoch::new(harness, resolver, &config)) })
836 }),
837 );
838
839 client.get(url("localhost", "/hello")).await.unwrap();
840 assert_eq!(
841 (first.binds.load(SeqCst), second.binds.load(SeqCst)),
842 (1, 0)
843 );
844
845 client.reset();
846 client.get(url("localhost", "/hello")).await.unwrap();
847 assert_eq!(
848 (first.binds.load(SeqCst), second.binds.load(SeqCst)),
849 (1, 1)
850 );
851 }
852
853 #[test(tokio::test)]
854 #[ntest::timeout(10_000)]
855 async fn stalled_establishment_does_not_block_other_origins() {
856 let (router, _) = test_router();
859 let harness = TestServerHarness::new(router);
860 harness.stall_first_bind();
861 let resolver = StaticResolver::new(vec![server_scion_ip()]);
862 let (client, _) = harness_client(harness.clone(), resolver, test_config());
863 let client = Arc::new(client);
864
865 let stalled = {
866 let client = client.clone();
867 tokio::spawn(async move { client.get(url("a.local", "/hello")).await })
868 };
869 tokio::time::sleep(Duration::from_millis(100)).await;
870 assert!(!stalled.is_finished());
871
872 let response = client.get(url("b.local", "/hello")).await.unwrap();
875 assert!(response.is_success());
876 assert!(!stalled.is_finished());
877
878 harness.release_stalled();
879 stalled.await.unwrap().unwrap();
880 }
881
882 #[test(tokio::test)]
883 #[ntest::timeout(10_000)]
884 async fn evicted_origin_completes_in_flight_request() {
885 let (router, _) = test_router();
888 let harness = TestServerHarness::new(router);
889 let resolver = StaticResolver::new(vec![server_scion_ip()]);
890 let (client, _) = harness_client(harness, resolver, test_config().with_max_origins(1));
891 let client = Arc::new(client);
892
893 let slow = {
894 let client = client.clone();
895 tokio::spawn(async move {
896 let response = client.get(url("a.local", "/slow")).await?;
897 response.text(Some(1024)).await
898 })
899 };
900 tokio::time::sleep(Duration::from_millis(150)).await;
901
902 client.get(url("b.local", "/hello")).await.unwrap();
905
906 let (body, _) = slow.await.unwrap().unwrap();
907 assert_eq!(body, "slow");
908 }
909
910 #[test(tokio::test)]
911 #[ntest::timeout(10_000)]
912 async fn close_rejects_new_requests_and_faults_in_flight() {
913 let (router, _) = test_router();
914 let harness = TestServerHarness::new(router);
915 let resolver = StaticResolver::new(vec![server_scion_ip()]);
916 let (client, _) = harness_client(harness, resolver, test_config());
917 let client = Arc::new(client);
918
919 client.get(url("localhost", "/hello")).await.unwrap();
920
921 let in_flight = {
924 let client = client.clone();
925 tokio::spawn(async move {
926 let response = client.get(url("localhost", "/slow")).await?;
927 response.text(Some(1024)).await
928 })
929 };
930 tokio::time::sleep(Duration::from_millis(100)).await;
931 client.close().await;
932
933 let start = tokio::time::Instant::now();
934 let result = in_flight.await.unwrap();
935 assert!(result.is_err());
936 assert!(start.elapsed() < Duration::from_millis(300));
937
938 let err = client.get(url("localhost", "/hello")).await.err().unwrap();
940 assert!(matches!(err, Error::Closed), "{err}");
941 client.close().await;
942 }
943
944 #[test(tokio::test)]
945 #[ntest::timeout(10_000)]
946 async fn per_request_timeout_overrides_the_default() {
947 let (router, _) = test_router();
948 let harness = TestServerHarness::new(router);
949 let resolver = StaticResolver::new(vec![server_scion_ip()]);
950 let (client, _) = harness_client(harness, resolver, test_config());
951
952 let request = Request::get(url("localhost", "/slow"))
953 .request_timeout(Duration::from_millis(100))
954 .build()
955 .unwrap();
956 let err = client.request(request).await.err().unwrap();
957 assert!(
958 matches!(
959 err,
960 Error::Timeout {
961 phase: TimeoutPhase::Request,
962 ..
963 }
964 ),
965 "{err}"
966 );
967 }
968
969 #[test(tokio::test)]
970 #[ntest::timeout(10_000)]
971 async fn body_collection_respects_max_size() {
972 let (router, _) = test_router();
973 let harness = TestServerHarness::new(router);
974 let resolver = StaticResolver::new(vec![server_scion_ip()]);
975 let (client, _) = harness_client(harness, resolver, test_config());
976
977 let response = client.get(url("localhost", "/hello")).await.unwrap();
978 let err = response.bytes(Some(2)).await.err().unwrap();
979 assert!(matches!(err, Error::BodyTooLarge { limit: 2 }), "{err}");
980 }
981
982 #[test(tokio::test)]
983 #[ntest::timeout(10_000)]
984 async fn warm_up_establishes_without_a_request() {
985 let (router, hits) = test_router();
986 let harness = TestServerHarness::new(router);
987 let resolver = StaticResolver::new(vec![server_scion_ip()]);
988 let (client, builds) = harness_client(harness.clone(), resolver, test_config());
989
990 client.warm_up(url("localhost", "/")).await.unwrap();
991 assert_eq!(builds.load(SeqCst), 1);
992 assert_eq!(harness.binds.load(SeqCst), 1);
993 assert_eq!(hits.load(SeqCst), 0);
994
995 client.get(url("localhost", "/hello")).await.unwrap();
997 assert_eq!(harness.binds.load(SeqCst), 1);
998 assert_eq!(hits.load(SeqCst), 1);
999 }
1000}