1use super::admission::CapacityLease;
17use super::events::{LogicalCloseCause, SharedConnectionEventListener};
18use super::origin::OriginKey;
19#[cfg(test)]
20use super::partition::Spawn;
21use super::partition::{DriverSpawner, PartitionId};
22use super::stats::CellConnectionStats;
23use crate::client::connect::{ConnectPath, ConnectPathInner};
24use crate::sync::{Arc, Mutex};
25pub use aws_smithy_runtime_api::client::connection::ConnectionId;
26use aws_smithy_runtime_api::client::connection::{
27 ConnectionEstablishmentMetadata, ConnectionMetadata,
28};
29use http_1x::Extensions;
30use hyper::rt::{Read, ReadBufCursor, Write};
31use hyper_util::client::legacy::connect::{Connected, Connection, HttpInfo};
32use pin_project_lite::pin_project;
33use std::fmt;
34use std::io::{self, IoSlice};
35use std::net::SocketAddr;
36use std::pin::Pin;
37use std::sync::OnceLock;
38use std::task::{Context, Poll};
39
40#[derive(Clone, Copy, Debug, Eq, PartialEq)]
46#[non_exhaustive]
47pub enum CloseReason {
48 IdleTimeout,
50 Poisoned,
52 ProtocolClosed,
54 IncompleteH1Exchange,
56 Upgraded,
58 Reclaimed,
60 PoolDropped,
62 OwnerRuntimeShutdown,
64}
65
66#[derive(Clone, Copy, Debug, Eq, PartialEq)]
68#[non_exhaustive]
69pub enum ConnectionProtocol {
70 Http1,
72 Http2,
74}
75
76#[derive(Debug)]
82pub struct ConnectionInfo {
83 id: ConnectionId,
85 origin: OriginKey,
87 owner_partition: PartitionId,
89 protocol: ConnectionProtocol,
91 local_addr: Option<SocketAddr>,
93 remote_addr: Option<SocketAddr>,
95 connect_path: ConnectPathInner,
97 connected: Connected,
99 establishment: OnceLock<ConnectionEstablishmentMetadata>,
101}
102
103impl ConnectionInfo {
104 pub(super) fn new(
106 id: ConnectionId,
107 origin: OriginKey,
108 owner_partition: PartitionId,
109 protocol: ConnectionProtocol,
110 connected: Connected,
111 ) -> Arc<Self> {
112 let mut extras = Extensions::new();
113 connected.get_extras(&mut extras);
114 let http_info = extras.get::<HttpInfo>();
115 let connect_path = ConnectPathInner::from_connected(&connected, &extras);
116 Arc::new(Self {
117 id,
118 origin,
119 owner_partition,
120 protocol,
121 local_addr: http_info.map(HttpInfo::local_addr),
122 remote_addr: http_info.map(HttpInfo::remote_addr),
123 connect_path,
124 connected,
125 establishment: OnceLock::new(),
126 })
127 }
128
129 pub fn id(&self) -> ConnectionId {
131 self.id
132 }
133
134 pub fn origin(&self) -> &OriginKey {
136 &self.origin
137 }
138
139 pub fn owner_partition(&self) -> PartitionId {
141 self.owner_partition
142 }
143
144 pub fn protocol(&self) -> ConnectionProtocol {
146 self.protocol
147 }
148
149 pub fn local_addr(&self) -> Option<SocketAddr> {
151 self.local_addr
152 }
153
154 pub fn remote_addr(&self) -> Option<SocketAddr> {
156 self.remote_addr
157 }
158
159 pub fn connect_path(&self) -> ConnectPath {
161 self.connect_path.public()
162 }
163
164 pub fn is_proxied(&self) -> bool {
166 self.connect_path().is_proxied()
167 }
168
169 pub fn establishment(&self) -> Option<&ConnectionEstablishmentMetadata> {
171 self.establishment.get()
172 }
173
174 pub(super) fn set_establishment(&self, metadata: ConnectionEstablishmentMetadata) {
176 assert!(
177 self.establishment.set(metadata).is_ok(),
178 "connection establishment metadata was set more than once"
179 );
180 }
181
182 pub(super) fn connect_path_inner(&self) -> &ConnectPathInner {
184 &self.connect_path
185 }
186
187 pub(super) fn apply_connector_extras(&self, extensions: &mut Extensions) {
189 self.connected.get_extras(extensions);
190 }
191
192 pub(super) fn metadata(&self, close: super::cell::h1::H1CloseHandle) -> ConnectionMetadata {
194 let mut builder = ConnectionMetadata::builder()
195 .proxied(self.is_proxied())
196 .connection_id(self.id)
197 .poison_fn(move || {
198 close.close(CloseReason::Poisoned);
199 });
200 builder
201 .set_local_addr(self.local_addr)
202 .set_remote_addr(self.remote_addr)
203 .set_establishment(self.establishment().cloned());
204 builder.build()
205 }
206
207 pub(super) fn h2_metadata(&self, close: super::cell::h2::H2CloseHandle) -> ConnectionMetadata {
209 let mut builder = ConnectionMetadata::builder()
210 .proxied(self.is_proxied())
211 .connection_id(self.id)
212 .poison_fn(move || {
213 close.close(CloseReason::Poisoned);
214 });
215 builder
216 .set_local_addr(self.local_addr)
217 .set_remote_addr(self.remote_addr)
218 .set_establishment(self.establishment().cloned());
219 builder.build()
220 }
221
222 #[cfg(test)]
224 pub(super) fn for_test(id: ConnectionId, owner_partition: PartitionId) -> Arc<Self> {
225 Self::new(
226 id,
227 OriginKey::from_parts(http_1x::uri::Scheme::HTTPS, "example.com", None)
228 .expect("synthetic test origin is valid"),
229 owner_partition,
230 ConnectionProtocol::Http1,
231 Connected::new(),
232 )
233 }
234}
235
236pub(super) struct ConnectionState {
238 info: Arc<ConnectionInfo>,
240 owner_spawner: DriverSpawner,
242 stats: Arc<CellConnectionStats>,
244 lifecycle: Mutex<ConnectionLifecycle>,
246}
247
248#[cfg(test)]
249#[derive(Debug)]
250struct TestDriverSpawner;
251
252#[cfg(test)]
253impl Spawn for TestDriverSpawner {
254 fn spawn(&self, driver: Pin<Box<dyn std::future::Future<Output = ()> + Send + 'static>>) {
255 drop(driver);
256 }
257}
258
259#[derive(Debug)]
261struct ConnectionLifecycle {
262 logical: LogicalState,
264 in_flight: usize,
266 physical_connection_complete: bool,
268 counted_close: Option<CountedClose>,
270 events: LifecycleEventProgress,
272}
273
274#[derive(Debug)]
276enum LogicalState {
277 PendingOpen,
280 Open {
282 capacity: Option<CapacityLease>,
284 },
285 Closed {
287 cause: LogicalCloseCause,
289 disposition: CloseDisposition,
291 retained_capacity: Option<CapacityLease>,
293 },
294}
295
296#[derive(Clone, Copy, Debug, Eq, PartialEq)]
298enum CloseDisposition {
299 Http1(H1CloseDisposition),
301 Http2(CloseReason),
303}
304
305impl CloseDisposition {
306 fn final_reason(self) -> Option<CloseReason> {
308 match self {
309 Self::Http1(H1CloseDisposition::AwaitingExchange { .. }) => None,
310 Self::Http1(H1CloseDisposition::Final(reason)) | Self::Http2(reason) => Some(reason),
311 }
312 }
313}
314
315#[derive(Clone, Copy, Debug, Eq, PartialEq)]
317enum H1CloseDisposition {
318 AwaitingExchange {
320 fallback: CloseReason,
322 },
323 Final(CloseReason),
325}
326
327#[derive(Clone, Copy, Debug, Eq, PartialEq)]
329enum CountedClose {
330 H1Draining,
331 H1Upgraded,
332 H2Draining,
333}
334
335impl CountedClose {
336 fn start(self, stats: &CellConnectionStats) {
337 match self {
338 Self::H1Draining => stats.h1_drain_started(),
339 Self::H1Upgraded => stats.h1_upgrade_started(),
340 Self::H2Draining => stats.h2_drain_started(),
341 }
342 }
343
344 fn finish(self, stats: &CellConnectionStats) {
345 match self {
346 Self::H1Draining => stats.h1_drain_finished(),
347 Self::H1Upgraded => stats.h1_upgrade_finished(),
348 Self::H2Draining => stats.h2_drain_finished(),
349 }
350 }
351
352 fn h1_upgrade(self, stats: &CellConnectionStats) -> Self {
353 assert_eq!(Self::H1Draining, self, "non-HTTP/1 drain became an upgrade");
354 stats.h1_drain_upgraded();
355 Self::H1Upgraded
356 }
357}
358
359#[derive(Debug)]
366enum LifecycleEventProgress {
367 OpenedPending,
369 WaitingForLogicalClose {
371 listener: SharedConnectionEventListener,
372 emitting: bool,
373 },
374 WaitingForPhysicalClose {
376 listener: SharedConnectionEventListener,
377 emitting: bool,
378 },
379 Complete,
381}
382
383enum ReadyLifecycleEvent {
385 LogicalClose(LogicalCloseCause),
386 PhysicalClose(CloseReason),
387}
388
389impl LifecycleEventProgress {
390 fn opened_completed(&mut self, listener: Option<&SharedConnectionEventListener>) -> bool {
392 assert!(
393 matches!(self, Self::OpenedPending),
394 "connection opened event completed more than once"
395 );
396 let Some(listener) = listener else {
397 *self = Self::Complete;
398 return false;
399 };
400 *self = Self::WaitingForLogicalClose {
401 listener: listener.clone(),
402 emitting: true,
403 };
404 true
405 }
406
407 fn claim_emission(&mut self) -> bool {
409 let emitting = match self {
410 Self::WaitingForLogicalClose { emitting, .. }
411 | Self::WaitingForPhysicalClose { emitting, .. } => emitting,
412 Self::OpenedPending | Self::Complete => return false,
413 };
414 if *emitting {
415 return false;
416 }
417 *emitting = true;
418 true
419 }
420}
421
422impl ConnectionLifecycle {
423 fn take_ready_event(&mut self) -> Option<(SharedConnectionEventListener, ReadyLifecycleEvent)> {
425 match std::mem::replace(&mut self.events, LifecycleEventProgress::Complete) {
426 LifecycleEventProgress::OpenedPending => {
427 unreachable!("connection lifecycle event emission began before Opened completed")
428 }
429 LifecycleEventProgress::WaitingForLogicalClose { listener, emitting } => {
430 assert!(
431 emitting,
432 "connection lifecycle event emission was not claimed"
433 );
434 let cause = match &self.logical {
435 LogicalState::Closed { cause, .. } => *cause,
436 LogicalState::PendingOpen | LogicalState::Open { .. } => {
437 self.events = LifecycleEventProgress::WaitingForLogicalClose {
438 listener,
439 emitting: false,
440 };
441 return None;
442 }
443 };
444 self.events = LifecycleEventProgress::WaitingForPhysicalClose {
445 listener: listener.clone(),
446 emitting: true,
447 };
448 Some((listener, ReadyLifecycleEvent::LogicalClose(cause)))
449 }
450 LifecycleEventProgress::WaitingForPhysicalClose { listener, emitting } => {
451 assert!(
452 emitting,
453 "connection lifecycle event emission was not claimed"
454 );
455 if !self.physical_connection_complete {
456 self.events = LifecycleEventProgress::WaitingForPhysicalClose {
457 listener,
458 emitting: false,
459 };
460 return None;
461 }
462 let reason = match &self.logical {
463 LogicalState::Closed { disposition, .. } => disposition
464 .final_reason()
465 .expect("physical close completed before H1 classification"),
466 LogicalState::PendingOpen | LogicalState::Open { .. } => {
467 panic!("physical close followed an open connection")
468 }
469 };
470 Some((listener, ReadyLifecycleEvent::PhysicalClose(reason)))
471 }
472 LifecycleEventProgress::Complete => None,
473 }
474 }
475}
476impl ConnectionState {
477 pub(super) fn pending_open(
488 info: Arc<ConnectionInfo>,
489 owner_spawner: DriverSpawner,
490 stats: Arc<CellConnectionStats>,
491 ) -> (Arc<Self>, PhysicalConnectionGuard) {
492 stats.physical_connection_started();
493 let connection = Arc::new(Self {
494 info,
495 owner_spawner,
496 stats,
497 lifecycle: Mutex::new(ConnectionLifecycle {
498 logical: LogicalState::PendingOpen,
499 in_flight: 0,
500 physical_connection_complete: false,
501 counted_close: None,
502 events: LifecycleEventProgress::OpenedPending,
503 }),
504 });
505 let physical = PhysicalConnectionGuard {
506 connection: connection.clone(),
507 active: true,
508 };
509 (connection, physical)
510 }
511
512 #[cfg(test)]
513 pub(super) fn pending_open_for_test(
514 info: Arc<ConnectionInfo>,
515 ) -> (Arc<Self>, PhysicalConnectionGuard) {
516 Self::pending_open(
517 info,
518 DriverSpawner::new(TestDriverSpawner),
519 Arc::new(CellConnectionStats::default()),
520 )
521 }
522
523 pub(super) fn owner_spawner(&self) -> DriverSpawner {
525 self.owner_spawner.clone()
526 }
527
528 pub(super) fn open(&self, lease: Option<CapacityLease>) -> Result<(), Option<CapacityLease>> {
536 let mut lifecycle = self.lifecycle.lock();
537 if !matches!(lifecycle.logical, LogicalState::PendingOpen) {
538 return Err(lease);
539 }
540 lifecycle.logical = LogicalState::Open { capacity: lease };
541 Ok(())
542 }
543
544 #[cfg(test)]
548 pub(super) fn unbounded(info: Arc<ConnectionInfo>) -> (Arc<Self>, PhysicalConnectionGuard) {
549 let (connection, physical) = Self::pending_open_for_test(info);
550 connection
551 .open(None)
552 .expect("new unbounded connection could not open");
553 (connection, physical)
554 }
555
556 #[cfg(test)]
561 pub(super) fn bounded(
562 info: Arc<ConnectionInfo>,
563 lease: CapacityLease,
564 ) -> (Arc<Self>, PhysicalConnectionGuard) {
565 let (connection, physical) = Self::pending_open_for_test(info);
566 connection
567 .open(Some(lease))
568 .expect("new bounded connection could not open");
569 (connection, physical)
570 }
571
572 pub(super) fn id(&self) -> ConnectionId {
574 self.info.id()
575 }
576
577 pub(super) fn owner_partition(&self) -> PartitionId {
579 self.info.owner_partition()
580 }
581
582 pub(super) fn info(&self) -> &Arc<ConnectionInfo> {
584 &self.info
585 }
586
587 pub(super) fn try_commit_dispatch(connection: &Arc<Self>) -> Option<DispatchGuard> {
592 let mut lifecycle = connection.lifecycle.lock();
593 if !matches!(lifecycle.logical, LogicalState::Open { .. }) {
594 return None;
595 }
596 lifecycle.in_flight = lifecycle
597 .in_flight
598 .checked_add(1)
599 .expect("in-flight dispatch count exhausted");
600 drop(lifecycle);
601
602 Some(DispatchGuard {
603 connection: connection.clone(),
604 active: true,
605 })
606 }
607
608 pub(super) fn complete_opened_event(&self, listener: Option<&SharedConnectionEventListener>) {
616 let emit = {
617 let mut lifecycle = self.lifecycle.lock();
618 assert!(
619 !matches!(lifecycle.logical, LogicalState::PendingOpen),
620 "connection opened event completed before the connection opened"
621 );
622 lifecycle.events.opened_completed(listener)
623 };
624
625 if emit {
626 self.emit_ready_events();
627 }
628 }
629
630 fn emit_ready_events(&self) {
635 loop {
636 let ready = self.lifecycle.lock().take_ready_event();
637 let Some((listener, event)) = ready else {
638 return;
639 };
640 match event {
641 ReadyLifecycleEvent::LogicalClose(cause) => {
642 listener.logical_close(&self.info, cause);
643 }
644 ReadyLifecycleEvent::PhysicalClose(reason) => {
645 listener.physical_close(&self.info, reason);
646 }
647 }
648 }
649 }
650
651 pub(super) fn logical_close(&self, reason: CloseReason) -> bool {
661 let cause = LogicalCloseCause::from_reason(reason);
662 let (released_capacity, emit) = {
663 let mut lifecycle = self.lifecycle.lock();
664 let previous = std::mem::replace(&mut lifecycle.logical, LogicalState::PendingOpen);
665 let (disposition, retained_capacity, released_capacity, was_open) = match previous {
666 LogicalState::PendingOpen => {
667 let disposition = match self.info.protocol() {
668 ConnectionProtocol::Http1 => {
669 CloseDisposition::Http1(H1CloseDisposition::Final(reason))
670 }
671 ConnectionProtocol::Http2 => CloseDisposition::Http2(reason),
672 };
673 (disposition, None, None, false)
674 }
675 LogicalState::Open { mut capacity } => match self.info.protocol() {
676 ConnectionProtocol::Http1
677 if lifecycle.in_flight != 0
678 && !lifecycle.physical_connection_complete
679 && matches!(
680 reason,
681 CloseReason::ProtocolClosed | CloseReason::OwnerRuntimeShutdown
682 ) =>
683 {
684 (
685 CloseDisposition::Http1(H1CloseDisposition::AwaitingExchange {
686 fallback: reason,
687 }),
688 capacity.take(),
689 None,
690 true,
691 )
692 }
693 ConnectionProtocol::Http1 => {
694 let retain_for_upgrade = reason == CloseReason::Upgraded
695 && !lifecycle.physical_connection_complete;
696 let retained_capacity =
697 retain_for_upgrade.then(|| capacity.take()).flatten();
698 (
699 CloseDisposition::Http1(H1CloseDisposition::Final(reason)),
700 retained_capacity,
701 capacity,
702 true,
703 )
704 }
705 ConnectionProtocol::Http2 => {
706 (CloseDisposition::Http2(reason), None, capacity, true)
707 }
708 },
709 closed @ LogicalState::Closed { .. } => {
710 lifecycle.logical = closed;
711 return false;
712 }
713 };
714 if was_open && !lifecycle.physical_connection_complete {
715 let counted_close = match self.info.protocol() {
716 ConnectionProtocol::Http1 if reason == CloseReason::Upgraded => {
717 CountedClose::H1Upgraded
718 }
719 ConnectionProtocol::Http1 => CountedClose::H1Draining,
720 ConnectionProtocol::Http2 => CountedClose::H2Draining,
721 };
722 assert!(
723 lifecycle.counted_close.is_none(),
724 "connection close was counted more than once"
725 );
726 counted_close.start(&self.stats);
727 lifecycle.counted_close = Some(counted_close);
728 }
729 lifecycle.logical = LogicalState::Closed {
730 cause,
731 disposition,
732 retained_capacity,
733 };
734 let emit = lifecycle.events.claim_emission();
735 (released_capacity, emit)
736 };
737 drop(released_capacity);
738 if emit {
739 self.emit_ready_events();
740 }
741 tracing::debug!(
742 connection_id = %self.id(),
743 connection_partition = ?self.owner_partition(),
744 protocol = ?self.info.protocol(),
745 origin_scheme = %self.info.origin().scheme(),
746 origin_host = self.info.origin().host(),
747 origin_port = ?self.info.origin().port(),
748 close_reason = ?reason,
749 "connection logically closed"
750 );
751 true
752 }
753
754 pub(super) fn complete_h1_exchange(&self, exchange_reason: CloseReason) -> bool {
760 let (released_capacity, final_reason) = {
761 let mut lifecycle = self.lifecycle.lock();
762 let physical_connection_complete = lifecycle.physical_connection_complete;
763 let (released_capacity, final_reason) = match &mut lifecycle.logical {
764 LogicalState::Closed {
765 disposition: CloseDisposition::Http1(state),
766 retained_capacity,
767 ..
768 } => {
769 let H1CloseDisposition::AwaitingExchange { fallback } = *state else {
770 return false;
771 };
772 debug_assert!(
773 !physical_connection_complete,
774 "physical close left HTTP/1 exchange classification pending"
775 );
776 let final_reason = if exchange_reason == CloseReason::Upgraded {
777 CloseReason::Upgraded
778 } else {
779 fallback
780 };
781 *state = H1CloseDisposition::Final(final_reason);
782 let release_capacity = final_reason != CloseReason::Upgraded;
783 (
784 release_capacity.then(|| retained_capacity.take()).flatten(),
785 final_reason,
786 )
787 }
788 _ => return false,
789 };
790 if final_reason == CloseReason::Upgraded {
791 lifecycle.counted_close = Some(
792 lifecycle
793 .counted_close
794 .expect("upgraded HTTP/1 connection had no draining count")
795 .h1_upgrade(&self.stats),
796 );
797 }
798 (released_capacity, final_reason)
799 };
800 drop(released_capacity);
801 tracing::debug!(
802 connection_id = %self.id(),
803 connection_partition = ?self.owner_partition(),
804 origin_scheme = %self.info.origin().scheme(),
805 origin_host = self.info.origin().host(),
806 origin_port = ?self.info.origin().port(),
807 close_reason = ?final_reason,
808 exchange_reason = ?exchange_reason,
809 "HTTP/1 connection close classification completed"
810 );
811 true
812 }
813
814 fn release_dispatch(&self) {
816 let complete_reason = {
817 let mut lifecycle = self.lifecycle.lock();
818 lifecycle.in_flight = lifecycle
819 .in_flight
820 .checked_sub(1)
821 .expect("completed a dispatch that was not in flight");
822 if lifecycle.in_flight == 0 {
823 match lifecycle.logical {
824 LogicalState::Closed {
825 disposition:
826 CloseDisposition::Http1(H1CloseDisposition::AwaitingExchange { fallback }),
827 ..
828 } => Some(fallback),
829 _ => None,
830 }
831 } else {
832 None
833 }
834 };
835 if let Some(reason) = complete_reason {
836 self.complete_h1_exchange(reason);
837 }
838 }
839
840 fn complete_physical_connection(&self) {
846 let (released_capacity, emit) = {
847 let mut lifecycle = self.lifecycle.lock();
848 assert!(
849 !lifecycle.physical_connection_complete,
850 "physical connection ownership completed more than once"
851 );
852 lifecycle.physical_connection_complete = true;
853
854 let released_capacity = match &mut lifecycle.logical {
855 LogicalState::Closed {
856 disposition,
857 retained_capacity,
858 ..
859 } => {
860 if let CloseDisposition::Http1(H1CloseDisposition::AwaitingExchange {
861 fallback,
862 }) = *disposition
863 {
864 *disposition = CloseDisposition::Http1(H1CloseDisposition::Final(fallback));
865 }
866 retained_capacity.take()
867 }
868 LogicalState::PendingOpen | LogicalState::Open { .. } => None,
869 };
870 if let Some(counted_close) = lifecycle.counted_close.take() {
871 counted_close.finish(&self.stats);
872 }
873 self.stats.physical_connection_finished();
874 let emit = lifecycle.events.claim_emission();
875 (released_capacity, emit)
876 };
877 drop(released_capacity);
878 if emit {
879 self.emit_ready_events();
880 }
881 tracing::debug!(
882 connection_id = %self.id(),
883 connection_partition = ?self.owner_partition(),
884 origin_scheme = %self.info.origin().scheme(),
885 origin_host = self.info.origin().host(),
886 origin_port = ?self.info.origin().port(),
887 "physical connection ownership ended"
888 );
889 }
890
891 #[cfg(test)]
893 pub(super) fn probe(&self) -> ConnectionProbe {
894 let lifecycle = self.lifecycle.lock();
895 let (close_reason, awaiting_h1_exchange) = match &lifecycle.logical {
896 LogicalState::PendingOpen | LogicalState::Open { .. } => (None, false),
897 LogicalState::Closed { disposition, .. } => (
898 disposition.final_reason(),
899 matches!(
900 disposition,
901 CloseDisposition::Http1(H1CloseDisposition::AwaitingExchange { .. })
902 ),
903 ),
904 };
905 ConnectionProbe {
906 close_reason,
907 awaiting_h1_exchange,
908 in_flight: lifecycle.in_flight,
909 physical_connection_complete: lifecycle.physical_connection_complete,
910 }
911 }
912}
913
914impl fmt::Debug for ConnectionState {
915 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
916 f.debug_struct("ConnectionState")
917 .field("info", &self.info)
918 .field("lifecycle", &self.lifecycle)
919 .finish()
920 }
921}
922
923#[cfg(test)]
924#[derive(Clone, Copy, Debug, Eq, PartialEq)]
926pub(super) struct ConnectionProbe {
927 pub(super) close_reason: Option<CloseReason>,
929 pub(super) awaiting_h1_exchange: bool,
931 pub(super) in_flight: usize,
933 pub(super) physical_connection_complete: bool,
935}
936
937#[derive(Debug)]
941pub(super) struct DispatchGuard {
942 connection: Arc<ConnectionState>,
944 active: bool,
946}
947
948impl DispatchGuard {
949 #[cfg(test)]
951 pub(super) fn connection_id(&self) -> ConnectionId {
952 self.connection.id()
953 }
954
955 pub(super) fn release(mut self) {
960 self.active = false;
961 self.connection.release_dispatch();
962 }
963}
964
965impl Drop for DispatchGuard {
966 fn drop(&mut self) {
967 if self.active {
968 self.connection.release_dispatch();
969 }
970 }
971}
972
973#[derive(Debug)]
979pub(super) struct PhysicalConnectionGuard {
980 connection: Arc<ConnectionState>,
982 active: bool,
984}
985
986impl PhysicalConnectionGuard {
987 #[cfg(test)]
992 pub(super) fn release(mut self) {
993 self.active = false;
994 self.connection.complete_physical_connection();
995 }
996}
997
998impl Drop for PhysicalConnectionGuard {
999 fn drop(&mut self) {
1000 if self.active {
1001 self.connection.complete_physical_connection();
1002 }
1003 }
1004}
1005
1006pin_project! {
1007 pub(super) struct ConnectionIo<T> {
1014 #[pin]
1015 inner: T,
1016 physical: PhysicalConnectionGuard,
1019 }
1020}
1021
1022impl<T> ConnectionIo<T> {
1023 pub(super) fn new(inner: T, physical: PhysicalConnectionGuard) -> Self {
1025 Self { inner, physical }
1026 }
1027
1028 #[cfg(test)]
1030 pub(super) fn get_ref(&self) -> &T {
1031 &self.inner
1032 }
1033}
1034
1035impl<T> fmt::Debug for ConnectionIo<T>
1036where
1037 T: fmt::Debug,
1038{
1039 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1040 f.debug_struct("ConnectionIo")
1041 .field("inner", &self.inner)
1042 .finish_non_exhaustive()
1043 }
1044}
1045
1046impl<T> Connection for ConnectionIo<T>
1047where
1048 T: Connection,
1049{
1050 fn connected(&self) -> Connected {
1051 self.inner.connected()
1052 }
1053}
1054
1055impl<T> Read for ConnectionIo<T>
1056where
1057 T: Read,
1058{
1059 fn poll_read(
1060 self: Pin<&mut Self>,
1061 cx: &mut Context<'_>,
1062 buf: ReadBufCursor<'_>,
1063 ) -> Poll<io::Result<()>> {
1064 Read::poll_read(self.project().inner, cx, buf)
1065 }
1066}
1067
1068impl<T> Write for ConnectionIo<T>
1069where
1070 T: Write,
1071{
1072 fn poll_write(
1073 self: Pin<&mut Self>,
1074 cx: &mut Context<'_>,
1075 buf: &[u8],
1076 ) -> Poll<io::Result<usize>> {
1077 Write::poll_write(self.project().inner, cx, buf)
1078 }
1079
1080 fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
1081 Write::poll_flush(self.project().inner, cx)
1082 }
1083
1084 fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
1085 Write::poll_shutdown(self.project().inner, cx)
1086 }
1087
1088 fn is_write_vectored(&self) -> bool {
1089 self.inner.is_write_vectored()
1090 }
1091
1092 fn poll_write_vectored(
1093 self: Pin<&mut Self>,
1094 cx: &mut Context<'_>,
1095 bufs: &[IoSlice<'_>],
1096 ) -> Poll<io::Result<usize>> {
1097 Write::poll_write_vectored(self.project().inner, cx, bufs)
1098 }
1099}
1100
1101#[cfg(all(test, not(smithy_http_client_loom)))]
1102mod tests {
1103 use super::*;
1104 use crate::client::pool::admission::OriginAdmission;
1105 use crate::client::pool::events::{
1106 ConnectionEvent, ConnectionEvents, SharedConnectionEventListener,
1107 };
1108 use aws_smithy_async::time::SharedTimeSource;
1109 use std::num::NonZeroUsize;
1110 use std::sync::{Arc as StdArc, Mutex as StdMutex};
1111
1112 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1113 enum ObservedLifecycleEvent {
1114 Opened,
1115 LogicalClose(LogicalCloseCause),
1116 PhysicalClose(CloseReason),
1117 }
1118
1119 fn test_info(id: u64) -> Arc<ConnectionInfo> {
1120 test_info_for_protocol(id, ConnectionProtocol::Http1)
1121 }
1122
1123 fn test_info_for_protocol(id: u64, protocol: ConnectionProtocol) -> Arc<ConnectionInfo> {
1124 ConnectionInfo::new(
1125 ConnectionId::new(id),
1126 OriginKey::from_parts(http_1x::uri::Scheme::HTTPS, "example.com", None)
1127 .expect("synthetic test origin is valid"),
1128 PartitionId::from_index(0),
1129 protocol,
1130 Connected::new(),
1131 )
1132 }
1133
1134 fn attach_observer(
1135 connection: &Arc<ConnectionState>,
1136 observed: StdArc<StdMutex<Vec<ObservedLifecycleEvent>>>,
1137 ) {
1138 let events = ConnectionEvents::new(
1139 Some(SharedConnectionEventListener::new(
1140 move |event: &ConnectionEvent<'_>| {
1141 let event = match event {
1142 ConnectionEvent::Opened(_) => ObservedLifecycleEvent::Opened,
1143 ConnectionEvent::LogicalClose(closed) => {
1144 ObservedLifecycleEvent::LogicalClose(closed.cause())
1145 }
1146 ConnectionEvent::PhysicalClose(closed) => {
1147 ObservedLifecycleEvent::PhysicalClose(closed.reason())
1148 }
1149 ConnectionEvent::EstablishmentFailed(failed) => {
1150 panic!("installed connection failed: {failed:?}")
1151 }
1152 };
1153 observed.lock().unwrap().push(event);
1154 },
1155 )),
1156 SharedTimeSource::default(),
1157 );
1158 let establishment = events.establishment_started(
1159 connection.info().origin(),
1160 connection.owner_partition(),
1161 connection.stats.clone(),
1162 );
1163 establishment.opened(connection);
1164 }
1165
1166 #[test]
1167 fn lifecycle_callbacks_observe_updated_connection_stats() {
1168 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1169 enum EventKind {
1170 Opened,
1171 LogicalClose,
1172 PhysicalClose,
1173 }
1174
1175 let stats = Arc::new(CellConnectionStats::default());
1176 let observed = StdArc::new(StdMutex::new(Vec::new()));
1177 let events = ConnectionEvents::new(
1178 Some(SharedConnectionEventListener::new({
1179 let stats = stats.clone();
1180 let observed = observed.clone();
1181 move |event: &ConnectionEvent<'_>| {
1182 let kind = match event {
1183 ConnectionEvent::Opened(_) => EventKind::Opened,
1184 ConnectionEvent::LogicalClose(_) => EventKind::LogicalClose,
1185 ConnectionEvent::PhysicalClose(_) => EventKind::PhysicalClose,
1186 ConnectionEvent::EstablishmentFailed(failed) => {
1187 panic!("installed connection failed: {failed:?}")
1188 }
1189 };
1190 let snapshot = stats.snapshot(0, 0, 0, 0, 0);
1191 observed.lock().unwrap().push((
1192 kind,
1193 snapshot.establishing_connections(),
1194 snapshot.h1().draining(),
1195 snapshot.physically_live_connections(),
1196 ));
1197 }
1198 })),
1199 SharedTimeSource::default(),
1200 );
1201 let mut establishment = events.establishment_started(
1202 test_info(1).origin(),
1203 PartitionId::from_index(0),
1204 stats.clone(),
1205 );
1206 establishment.protocol_selected(ConnectionProtocol::Http1);
1207 let (connection, physical) = ConnectionState::pending_open(
1208 test_info(1),
1209 DriverSpawner::new(TestDriverSpawner),
1210 stats,
1211 );
1212 connection.open(None).unwrap();
1213
1214 establishment.opened(&connection);
1215 assert!(connection.logical_close(CloseReason::ProtocolClosed));
1216 physical.release();
1217
1218 assert_eq!(
1219 &[
1220 (EventKind::Opened, 0, 0, 1),
1221 (EventKind::LogicalClose, 0, 1, 1),
1222 (EventKind::PhysicalClose, 0, 0, 0),
1223 ],
1224 observed.lock().unwrap().as_slice()
1225 );
1226 }
1227
1228 #[test]
1229 fn logical_close_releases_capacity_before_physical_connection_completion() {
1230 let origin = OriginAdmission::for_test(NonZeroUsize::new(1).unwrap());
1231 let lease = OriginAdmission::lease_for_test(&origin);
1232 let (connection, physical) = ConnectionState::bounded(test_info(1), lease);
1233
1234 assert!(connection.logical_close(CloseReason::Reclaimed));
1235 assert!(!connection.logical_close(CloseReason::PoolDropped));
1236 assert_eq!(1, origin.available_capacity_for_test());
1237 assert!(!connection.probe().physical_connection_complete);
1238 assert_eq!(
1239 Some(CloseReason::Reclaimed),
1240 connection.probe().close_reason
1241 );
1242
1243 drop(physical);
1244 assert!(connection.probe().physical_connection_complete);
1245 }
1246
1247 #[test]
1248 fn installed_events_are_ordered_for_h1_and_h2() {
1249 for (id, protocol) in [
1250 (1, ConnectionProtocol::Http1),
1251 (2, ConnectionProtocol::Http2),
1252 ] {
1253 let observed = StdArc::new(StdMutex::new(Vec::new()));
1254 let (connection, physical) =
1255 ConnectionState::unbounded(test_info_for_protocol(id, protocol));
1256 attach_observer(&connection, observed.clone());
1257
1258 physical.release();
1259 assert_eq!(
1260 &[ObservedLifecycleEvent::Opened],
1261 observed.lock().unwrap().as_slice()
1262 );
1263
1264 assert!(connection.logical_close(CloseReason::ProtocolClosed));
1265 assert_eq!(
1266 &[
1267 ObservedLifecycleEvent::Opened,
1268 ObservedLifecycleEvent::LogicalClose(LogicalCloseCause::ProtocolEnded),
1269 ObservedLifecycleEvent::PhysicalClose(CloseReason::ProtocolClosed),
1270 ],
1271 observed.lock().unwrap().as_slice()
1272 );
1273 }
1274 }
1275
1276 #[test]
1277 fn close_before_opened_delivery_is_replayed_in_order() {
1278 let observed = StdArc::new(StdMutex::new(Vec::new()));
1279 let (connection, physical) = ConnectionState::unbounded(test_info(1));
1280
1281 assert!(connection.logical_close(CloseReason::PoolDropped));
1282 physical.release();
1283 assert!(observed.lock().unwrap().is_empty());
1284
1285 attach_observer(&connection, observed.clone());
1286 assert_eq!(
1287 &[
1288 ObservedLifecycleEvent::Opened,
1289 ObservedLifecycleEvent::LogicalClose(LogicalCloseCause::PoolDropped),
1290 ObservedLifecycleEvent::PhysicalClose(CloseReason::PoolDropped),
1291 ],
1292 observed.lock().unwrap().as_slice()
1293 );
1294 }
1295
1296 #[test]
1297 fn close_callbacks_may_reenter_connection_state() {
1298 let observed = StdArc::new(StdMutex::new(Vec::new()));
1299 let connection_slot = StdArc::new(StdMutex::new(None::<Arc<ConnectionState>>));
1300 let events = ConnectionEvents::new(
1301 Some(SharedConnectionEventListener::new({
1302 let observed = observed.clone();
1303 let connection_slot = connection_slot.clone();
1304 move |event: &ConnectionEvent<'_>| {
1305 let event = match event {
1306 ConnectionEvent::Opened(_) => return,
1307 ConnectionEvent::LogicalClose(closed) => {
1308 ObservedLifecycleEvent::LogicalClose(closed.cause())
1309 }
1310 ConnectionEvent::PhysicalClose(closed) => {
1311 ObservedLifecycleEvent::PhysicalClose(closed.reason())
1312 }
1313 ConnectionEvent::EstablishmentFailed(failed) => {
1314 panic!("installed connection failed: {failed:?}")
1315 }
1316 };
1317 let connection = connection_slot
1318 .lock()
1319 .unwrap()
1320 .clone()
1321 .expect("connection installed before callback");
1322 let closed_again = connection.logical_close(CloseReason::PoolDropped);
1325 observed.lock().unwrap().push((event, closed_again));
1326 }
1327 })),
1328 SharedTimeSource::default(),
1329 );
1330 let (connection, physical) = ConnectionState::unbounded(test_info(1));
1331 *connection_slot.lock().unwrap() = Some(connection.clone());
1332 let establishment = events.establishment_started(
1333 connection.info().origin(),
1334 connection.owner_partition(),
1335 connection.stats.clone(),
1336 );
1337 establishment.opened(&connection);
1338
1339 assert!(connection.logical_close(CloseReason::Poisoned));
1340 physical.release();
1341 assert_eq!(
1342 &[
1343 (
1344 ObservedLifecycleEvent::LogicalClose(LogicalCloseCause::Poisoned),
1345 false
1346 ),
1347 (
1348 ObservedLifecycleEvent::PhysicalClose(CloseReason::Poisoned),
1349 false
1350 ),
1351 ],
1352 observed.lock().unwrap().as_slice()
1353 );
1354 }
1355
1356 #[test]
1357 fn panicking_close_listener_does_not_change_lifecycle_state() {
1358 let origin = OriginAdmission::for_test(NonZeroUsize::new(1).unwrap());
1359 let lease = OriginAdmission::lease_for_test(&origin);
1360 let (connection, physical) = ConnectionState::bounded(test_info(1), lease);
1361 let events = ConnectionEvents::new(
1362 Some(SharedConnectionEventListener::new(
1363 |_: &ConnectionEvent<'_>| panic!("listener failed"),
1364 )),
1365 SharedTimeSource::default(),
1366 );
1367 let establishment = events.establishment_started(
1368 connection.info().origin(),
1369 connection.owner_partition(),
1370 connection.stats.clone(),
1371 );
1372 establishment.opened(&connection);
1373
1374 assert!(connection.logical_close(CloseReason::Reclaimed));
1375 physical.release();
1376
1377 assert_eq!(1, origin.available_capacity_for_test());
1378 assert_eq!(
1379 Some(CloseReason::Reclaimed),
1380 connection.probe().close_reason
1381 );
1382 assert!(connection.probe().physical_connection_complete);
1383 }
1384 #[test]
1385 fn driver_first_upgrade_retains_capacity_until_physical_completion() {
1386 let origin = OriginAdmission::for_test(NonZeroUsize::new(1).unwrap());
1387 let lease = OriginAdmission::lease_for_test(&origin);
1388 let (connection, physical) = ConnectionState::bounded(test_info(1), lease);
1389 let dispatch = ConnectionState::try_commit_dispatch(&connection).unwrap();
1390
1391 assert!(connection.logical_close(CloseReason::ProtocolClosed));
1392 assert!(connection.probe().awaiting_h1_exchange);
1393 assert_eq!(0, origin.available_capacity_for_test());
1394
1395 assert!(connection.complete_h1_exchange(CloseReason::Upgraded));
1396 assert_eq!(Some(CloseReason::Upgraded), connection.probe().close_reason);
1397 assert_eq!(0, origin.available_capacity_for_test());
1398 dispatch.release();
1399
1400 physical.release();
1401 assert_eq!(1, origin.available_capacity_for_test());
1402 }
1403
1404 #[test]
1405 fn exchange_first_upgrade_retains_capacity_until_physical_completion() {
1406 let origin = OriginAdmission::for_test(NonZeroUsize::new(1).unwrap());
1407 let lease = OriginAdmission::lease_for_test(&origin);
1408 let (connection, physical) = ConnectionState::bounded(test_info(1), lease);
1409 let dispatch = ConnectionState::try_commit_dispatch(&connection).unwrap();
1410
1411 assert!(connection.logical_close(CloseReason::Upgraded));
1412 assert_eq!(Some(CloseReason::Upgraded), connection.probe().close_reason);
1413 assert_eq!(0, origin.available_capacity_for_test());
1414 dispatch.release();
1415
1416 physical.release();
1417 assert_eq!(1, origin.available_capacity_for_test());
1418 }
1419
1420 #[test]
1421 fn non_upgrade_exchange_returns_capacity_before_physical_completion() {
1422 let origin = OriginAdmission::for_test(NonZeroUsize::new(1).unwrap());
1423 let lease = OriginAdmission::lease_for_test(&origin);
1424 let (connection, physical) = ConnectionState::bounded(test_info(1), lease);
1425 let dispatch = ConnectionState::try_commit_dispatch(&connection).unwrap();
1426
1427 assert!(connection.logical_close(CloseReason::ProtocolClosed));
1428 assert!(connection.complete_h1_exchange(CloseReason::ProtocolClosed));
1429 assert_eq!(
1430 Some(CloseReason::ProtocolClosed),
1431 connection.probe().close_reason
1432 );
1433 assert_eq!(1, origin.available_capacity_for_test());
1434 assert!(!connection.probe().physical_connection_complete);
1435
1436 dispatch.release();
1437 physical.release();
1438 }
1439
1440 #[test]
1441 fn non_upgrade_exchange_preserves_owner_runtime_shutdown_reason() {
1442 let observed = StdArc::new(StdMutex::new(Vec::new()));
1443 let origin = OriginAdmission::for_test(NonZeroUsize::new(1).unwrap());
1444 let lease = OriginAdmission::lease_for_test(&origin);
1445 let (connection, physical) = ConnectionState::bounded(test_info(1), lease);
1446 attach_observer(&connection, observed.clone());
1447 let dispatch = ConnectionState::try_commit_dispatch(&connection).unwrap();
1448
1449 assert!(connection.logical_close(CloseReason::OwnerRuntimeShutdown));
1450 assert!(connection.complete_h1_exchange(CloseReason::ProtocolClosed));
1451 assert_eq!(
1452 Some(CloseReason::OwnerRuntimeShutdown),
1453 connection.probe().close_reason
1454 );
1455 assert_eq!(1, origin.available_capacity_for_test());
1456 dispatch.release();
1457
1458 physical.release();
1459 assert_eq!(
1460 &[
1461 ObservedLifecycleEvent::Opened,
1462 ObservedLifecycleEvent::LogicalClose(LogicalCloseCause::OwnerRuntimeShutdown,),
1463 ObservedLifecycleEvent::PhysicalClose(CloseReason::OwnerRuntimeShutdown,),
1464 ],
1465 observed.lock().unwrap().as_slice()
1466 );
1467 }
1468
1469 #[test]
1470 fn exchange_classification_does_not_replace_a_policy_close() {
1471 let (connection, _physical) = ConnectionState::unbounded(test_info(1));
1472 assert!(connection.logical_close(CloseReason::Poisoned));
1473 assert!(!connection.complete_h1_exchange(CloseReason::Upgraded));
1474 assert_eq!(Some(CloseReason::Poisoned), connection.probe().close_reason);
1475 }
1476 #[test]
1477 fn committed_dispatch_drains_after_logical_close() {
1478 let (connection, _physical) = ConnectionState::unbounded(test_info(1));
1479 let dispatch = ConnectionState::try_commit_dispatch(&connection).unwrap();
1480 assert_eq!(ConnectionId::new(1), dispatch.connection_id());
1481
1482 assert!(connection.logical_close(CloseReason::ProtocolClosed));
1483 assert!(ConnectionState::try_commit_dispatch(&connection).is_none());
1484 assert_eq!(1, connection.probe().in_flight);
1485
1486 dispatch.release();
1487 assert_eq!(0, connection.probe().in_flight);
1488 }
1489
1490 #[test]
1491 fn physical_connection_guard_is_created_once_with_the_connection() {
1492 let (connection, physical) = ConnectionState::unbounded(test_info(1));
1493 assert_eq!(ConnectionId::new(1), connection.id());
1494 assert_eq!(PartitionId::from_index(0), connection.owner_partition());
1495 assert!(!connection.probe().physical_connection_complete);
1496 physical.release();
1497 assert!(connection.probe().physical_connection_complete);
1498 }
1499
1500 #[test]
1501 fn connection_state_retains_immutable_connection_info() {
1502 #[derive(Clone, Debug, Eq, PartialEq)]
1503 struct ConnectorMarker(&'static str);
1504
1505 let origin =
1506 OriginKey::from_parts(http_1x::uri::Scheme::HTTPS, "example.com", None).unwrap();
1507 let info = ConnectionInfo::new(
1508 ConnectionId::new(7),
1509 origin.clone(),
1510 PartitionId::from_index(2),
1511 ConnectionProtocol::Http1,
1512 Connected::new()
1513 .proxy(true)
1514 .extra(ConnectorMarker("connector-extra")),
1515 );
1516 let (connection, _physical) = ConnectionState::unbounded(info);
1517
1518 assert_eq!(ConnectionId::new(7), connection.info().id());
1519 assert_eq!(&origin, connection.info().origin());
1520 assert_eq!(PartitionId::from_index(2), connection.owner_partition());
1521 assert_eq!(ConnectionProtocol::Http1, connection.info().protocol());
1522 assert_eq!(None, connection.info().local_addr());
1523 assert_eq!(None, connection.info().remote_addr());
1524 assert_eq!(ConnectPath::ForwardProxy, connection.info().connect_path());
1525 assert_eq!(None, connection.info().establishment());
1526 let mut extensions = Extensions::new();
1527 connection.info().apply_connector_extras(&mut extensions);
1528 assert_eq!(
1529 Some(&ConnectorMarker("connector-extra")),
1530 extensions.get::<ConnectorMarker>()
1531 );
1532 }
1533
1534 #[test]
1535 fn dropping_connection_io_completes_physical_connection_ownership() {
1536 let (connection, physical) = ConnectionState::unbounded(test_info(1));
1537 let io = ConnectionIo::new("transport", physical);
1538 assert_eq!(&"transport", io.get_ref());
1539 assert!(!connection.probe().physical_connection_complete);
1540
1541 drop(io);
1542
1543 assert!(connection.probe().physical_connection_complete);
1544 }
1545}
1546
1547#[cfg(all(test, smithy_http_client_loom))]
1548mod loom_tests {
1549 use super::*;
1550 use crate::client::pool::admission::OriginAdmission;
1551 use crate::client::pool::events::{
1552 ConnectionEvent, ConnectionEvents, SharedConnectionEventListener,
1553 };
1554 use aws_smithy_async::time::SharedTimeSource;
1555 use loom::sync::atomic::{AtomicUsize, Ordering};
1556 use std::num::NonZeroUsize;
1557
1558 fn test_info(id: u64) -> Arc<ConnectionInfo> {
1559 ConnectionInfo::for_test(ConnectionId::new(id), PartitionId::from_index(0))
1560 }
1561
1562 #[test]
1567 fn dispatch_commit_linearizes_against_close() {
1568 loom::model(|| {
1569 let (connection, _physical) = ConnectionState::unbounded(test_info(1));
1570
1571 let dispatch_connection = connection.clone();
1572 let dispatch = loom::thread::spawn(move || {
1573 ConnectionState::try_commit_dispatch(&dispatch_connection)
1574 });
1575 let close_connection = connection.clone();
1576 let close = loom::thread::spawn(move || {
1577 close_connection.logical_close(CloseReason::ProtocolClosed)
1578 });
1579
1580 let dispatch = dispatch.join().unwrap();
1581 assert!(close.join().unwrap());
1582 assert!(ConnectionState::try_commit_dispatch(&connection).is_none());
1583 match dispatch {
1584 Some(dispatch) => {
1585 let probe = connection.probe();
1586 assert_eq!(None, probe.close_reason);
1587 assert!(probe.awaiting_h1_exchange);
1588 assert_eq!(1, probe.in_flight);
1589 drop(dispatch);
1590 let probe = connection.probe();
1591 assert_eq!(Some(CloseReason::ProtocolClosed), probe.close_reason);
1592 assert!(!probe.awaiting_h1_exchange);
1593 assert_eq!(0, probe.in_flight);
1594 }
1595 None => {
1596 let probe = connection.probe();
1597 assert_eq!(Some(CloseReason::ProtocolClosed), probe.close_reason);
1598 assert!(!probe.awaiting_h1_exchange);
1599 assert_eq!(0, probe.in_flight);
1600 }
1601 }
1602 });
1603 }
1604
1605 #[test]
1609 fn concurrent_logical_close_releases_one_capacity_lease() {
1610 loom::model(|| {
1611 let origin = OriginAdmission::for_test(NonZeroUsize::new(1).unwrap());
1612 let lease = OriginAdmission::lease_for_test(&origin);
1613 let (connection, _physical) = ConnectionState::bounded(test_info(1), lease);
1614 let first_connection = connection.clone();
1615 let first =
1616 loom::thread::spawn(move || first_connection.logical_close(CloseReason::Poisoned));
1617 let second_connection = connection.clone();
1618 let second = loom::thread::spawn(move || {
1619 second_connection.logical_close(CloseReason::PoolDropped)
1620 });
1621
1622 let first = first.join().unwrap();
1623 let second = second.join().unwrap();
1624 assert_ne!(first, second);
1625 assert_eq!(1, origin.available_capacity_for_test());
1626 let reason = connection.probe().close_reason.unwrap();
1627 assert!(matches!(
1628 reason,
1629 CloseReason::Poisoned | CloseReason::PoolDropped
1630 ));
1631 });
1632 }
1633
1634 #[test]
1639 fn h1_upgrade_classification_linearizes_against_physical_completion() {
1640 loom::model(|| {
1641 let origin = OriginAdmission::for_test(NonZeroUsize::new(1).unwrap());
1642 let lease = OriginAdmission::lease_for_test(&origin);
1643 let (connection, physical) = ConnectionState::bounded(test_info(1), lease);
1644 let dispatch = ConnectionState::try_commit_dispatch(&connection)
1645 .expect("open HTTP/1 connection rejected dispatch");
1646 assert!(connection.logical_close(CloseReason::ProtocolClosed));
1647
1648 let classify_connection = connection.clone();
1649 let classify = loom::thread::spawn(move || {
1650 classify_connection.complete_h1_exchange(CloseReason::Upgraded)
1651 });
1652 let complete_physical = loom::thread::spawn(move || physical.release());
1653
1654 let classified_as_upgrade = classify.join().unwrap();
1655 complete_physical.join().unwrap();
1656 dispatch.release();
1657
1658 let probe = connection.probe();
1659 assert_eq!(
1660 Some(if classified_as_upgrade {
1661 CloseReason::Upgraded
1662 } else {
1663 CloseReason::ProtocolClosed
1664 }),
1665 probe.close_reason
1666 );
1667 assert!(!probe.awaiting_h1_exchange);
1668 assert_eq!(0, probe.in_flight);
1669 assert!(probe.physical_connection_complete);
1670 assert_eq!(1, origin.available_capacity_for_test());
1671
1672 let stats = connection.stats.snapshot(0, 0, 0, 0, 0);
1673 assert_eq!(0, stats.h1().draining());
1674 assert_eq!(0, stats.h1().upgraded());
1675 assert_eq!(0, stats.physically_live_connections());
1676 });
1677 }
1678
1679 #[test]
1684 fn installed_events_remain_ordered_across_concurrent_close() {
1685 let mut model = loom::model::Builder::new();
1686 model.preemption_bound.get_or_insert(3);
1687 model.check(|| {
1688 let event_sequence = Arc::new(AtomicUsize::new(0));
1689 let observed = event_sequence.clone();
1690 let events = ConnectionEvents::new(
1691 Some(SharedConnectionEventListener::new(
1692 move |event: &ConnectionEvent<'_>| {
1693 let code = match event {
1694 ConnectionEvent::Opened(_) => 1,
1695 ConnectionEvent::LogicalClose(_) => 2,
1696 ConnectionEvent::PhysicalClose(_) => 3,
1697 ConnectionEvent::EstablishmentFailed(_) => 4,
1698 };
1699 observed
1700 .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |sequence| {
1701 Some(sequence * 4 + code)
1702 })
1703 .expect("event sequence update is infallible");
1704 },
1705 )),
1706 SharedTimeSource::default(),
1707 );
1708 let origin = OriginAdmission::for_test(NonZeroUsize::new(1).unwrap());
1709 let lease = OriginAdmission::lease_for_test(&origin);
1710 let (connection, physical) = ConnectionState::bounded(test_info(1), lease);
1711 let dispatch = ConnectionState::try_commit_dispatch(&connection)
1712 .expect("open HTTP/1 connection rejected dispatch");
1713 let establishment = events.establishment_started(
1714 connection.info().origin(),
1715 connection.owner_partition(),
1716 connection.stats.clone(),
1717 );
1718
1719 let opened_connection = connection.clone();
1720 let opened = loom::thread::spawn(move || establishment.opened(&opened_connection));
1721 let close_connection = connection.clone();
1722 let close = loom::thread::spawn(move || {
1723 close_connection.logical_close(CloseReason::ProtocolClosed)
1724 });
1725 let complete_physical = loom::thread::spawn(move || physical.release());
1726 let complete_dispatch = loom::thread::spawn(move || dispatch.release());
1727
1728 opened.join().unwrap();
1729 assert!(close.join().unwrap());
1730 complete_physical.join().unwrap();
1731 complete_dispatch.join().unwrap();
1732
1733 assert_eq!(
1734 27,
1735 event_sequence.load(Ordering::SeqCst),
1736 "installed connection events were missing, duplicated, or reordered"
1737 );
1738 assert_eq!(1, origin.available_capacity_for_test());
1739 let probe = connection.probe();
1740 assert_eq!(Some(CloseReason::ProtocolClosed), probe.close_reason);
1741 assert!(!probe.awaiting_h1_exchange);
1742 assert_eq!(0, probe.in_flight);
1743 assert!(probe.physical_connection_complete);
1744 });
1745 }
1746}