1use std::{
19 cell::RefCell,
20 fmt::Debug,
21 sync::{
22 Arc, Weak,
23 atomic::{AtomicU64, Ordering},
24 },
25};
26
27use ahash::{AHashMap, AHashSet};
28use nautilus_common::{
29 live::{runner::try_get_system_event_sender, sender::EventSender},
30 messages::{
31 SystemEvent,
32 system::{SocketState as SystemSocketState, SocketStateChange},
33 },
34};
35use nautilus_model::identifiers::{ClientId, Venue};
36pub use nautilus_network::mode::ReconnectRequestOutcome as SocketReconnectRequestOutcome;
37use nautilus_network::{SocketState, SocketStateSink, mode::ReconnectRequestOutcome};
38use parking_lot::Mutex;
39use ustr::Ustr;
40
41thread_local! {
42 static SOCKET_REGISTRARS: RefCell<Vec<SocketReconnectRegistrar>> = const { RefCell::new(Vec::new()) };
43}
44
45#[derive(Clone, Debug)]
47pub enum SocketReconnectLookup {
48 ClientNotFound,
50 Unsupported,
52 EndpointNotFound,
54 AmbiguousEndpoint,
56 Handle(SocketReconnectHandle),
58}
59
60#[derive(Clone)]
62pub struct SocketReconnectHandle {
63 request: Arc<dyn Fn() -> ReconnectRequestOutcome + Send + Sync>,
64}
65
66impl SocketReconnectHandle {
67 fn new<F>(request: F) -> Self
68 where
69 F: Fn() -> ReconnectRequestOutcome + Send + Sync + 'static,
70 {
71 Self {
72 request: Arc::new(request),
73 }
74 }
75
76 #[must_use]
78 pub fn request_reconnect(&self) -> ReconnectRequestOutcome {
79 (self.request)()
80 }
81}
82
83impl Debug for SocketReconnectHandle {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 f.debug_struct(stringify!(SocketReconnectHandle))
86 .finish_non_exhaustive()
87 }
88}
89
90#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
91struct SocketEndpoint {
92 client_id: ClientId,
93 endpoint: Ustr,
94}
95
96#[derive(Debug)]
97struct RegistryEntry {
98 generation: u64,
99 handle: SocketReconnectHandle,
100 request: Arc<Mutex<Option<SocketReconnectHandle>>>,
101}
102
103#[derive(Debug, Default)]
104struct RegistryInner {
105 clients: AHashSet<ClientId>,
106 supported: AHashSet<ClientId>,
107 entries: AHashMap<SocketEndpoint, AHashMap<u64, RegistryEntry>>,
108 owners: AHashMap<ClientId, AHashSet<u64>>,
109 next_generation: u64,
110 next_owner: u64,
111}
112
113#[derive(Debug, Default)]
115pub struct SocketReconnectRegistry {
116 inner: Arc<Mutex<RegistryInner>>,
117}
118
119impl SocketReconnectRegistry {
120 pub fn scope<T>(&self, f: impl FnOnce() -> T) -> T {
122 SOCKET_REGISTRARS.with(|registrars| {
123 registrars.borrow_mut().push(self.registrar());
124 });
125
126 let _scope = SocketRegistryScope;
127 f()
128 }
129
130 #[cfg(any(feature = "node", test))]
131 pub(crate) fn register_client(&self, client_id: ClientId) {
132 self.inner.lock().clients.insert(client_id);
133 }
134
135 #[must_use]
137 pub fn get(&self, client_id: ClientId, endpoint: Ustr) -> SocketReconnectLookup {
138 let inner = self.inner.lock();
139
140 let key = SocketEndpoint {
141 client_id,
142 endpoint,
143 };
144
145 match inner.entries.get(&key) {
146 Some(entries) if entries.len() > 1 => SocketReconnectLookup::AmbiguousEndpoint,
147 Some(entries) => entries
148 .values()
149 .next()
150 .map_or(SocketReconnectLookup::EndpointNotFound, |entry| {
151 SocketReconnectLookup::Handle(entry.handle.clone())
152 }),
153 None if inner.supported.contains(&client_id) => SocketReconnectLookup::EndpointNotFound,
154 None if inner.clients.contains(&client_id) => SocketReconnectLookup::Unsupported,
155 None => SocketReconnectLookup::ClientNotFound,
156 }
157 }
158
159 #[must_use]
161 pub fn handle(&self, client_id: ClientId, endpoint: Ustr) -> Option<SocketReconnectHandle> {
162 match self.get(client_id, endpoint) {
163 SocketReconnectLookup::Handle(handle) => Some(handle),
164 _ => None,
165 }
166 }
167
168 fn registrar(&self) -> SocketReconnectRegistrar {
169 SocketReconnectRegistrar {
170 registry: Arc::downgrade(&self.inner),
171 }
172 }
173}
174
175struct SocketRegistryScope;
176
177impl Drop for SocketRegistryScope {
178 fn drop(&mut self) {
179 SOCKET_REGISTRARS.with(|registrars| {
180 registrars.borrow_mut().pop();
181 });
182 }
183}
184
185#[derive(Clone, Debug)]
186struct SocketReconnectRegistrar {
187 registry: Weak<Mutex<RegistryInner>>,
188}
189
190impl SocketReconnectRegistrar {
191 fn owner(&self, client_id: ClientId) -> Option<SocketReconnectOwner> {
192 let registry = self.registry.upgrade()?;
193
194 let owner_id = {
195 let mut inner = registry.lock();
196 inner.next_owner = inner.next_owner.wrapping_add(1).max(1);
197 let owner_id = inner.next_owner;
198 inner.supported.insert(client_id);
199 inner.owners.entry(client_id).or_default().insert(owner_id);
200 owner_id
201 };
202
203 Some(SocketReconnectOwner(Arc::new(SocketReconnectOwnerInner {
204 registry: Arc::downgrade(®istry),
205 client_id,
206 owner_id,
207 })))
208 }
209}
210
211#[derive(Clone, Debug)]
212struct SocketReconnectOwner(Arc<SocketReconnectOwnerInner>);
213
214impl SocketReconnectOwner {
215 fn register(
216 &self,
217 endpoint: Ustr,
218 handle: SocketReconnectHandle,
219 ) -> (Option<SocketReconnectRegistration>, Option<RegistryEntry>) {
220 let Some(registry) = self.0.registry.upgrade() else {
221 return (None, None);
222 };
223
224 let key = SocketEndpoint {
225 client_id: self.0.client_id,
226 endpoint,
227 };
228
229 let replaced = remove_entry(®istry, key, self.0.owner_id, None);
230
231 let request = Arc::new(Mutex::new(Some(handle)));
232 let guarded_request = Arc::clone(&request);
233 let registry_ref = Arc::downgrade(®istry);
234
235 let guarded_handle = SocketReconnectHandle::new(move || {
236 let Some(_registry) = registry_ref.upgrade() else {
237 return ReconnectRequestOutcome::Closed;
238 };
239
240 let request = guarded_request.lock();
241 request
242 .as_ref()
243 .map_or(ReconnectRequestOutcome::Closed, |handle| {
244 handle.request_reconnect()
245 })
246 });
247
248 let generation = {
249 let mut inner = registry.lock();
250 inner.next_generation = inner.next_generation.wrapping_add(1).max(1);
251 let generation = inner.next_generation;
252 inner.entries.entry(key).or_default().insert(
253 self.0.owner_id,
254 RegistryEntry {
255 generation,
256 handle: guarded_handle,
257 request,
258 },
259 );
260
261 generation
262 };
263
264 (
265 Some(SocketReconnectRegistration {
266 registry: Arc::downgrade(®istry),
267 key,
268 owner_id: self.0.owner_id,
269 generation,
270 }),
271 replaced,
272 )
273 }
274
275 fn remove(&self, endpoint: Ustr) -> Option<RegistryEntry> {
276 let registry = self.0.registry.upgrade()?;
277
278 let key = SocketEndpoint {
279 client_id: self.0.client_id,
280 endpoint,
281 };
282
283 remove_entry(®istry, key, self.0.owner_id, None)
284 }
285}
286
287#[derive(Debug)]
288struct SocketReconnectOwnerInner {
289 registry: Weak<Mutex<RegistryInner>>,
290 client_id: ClientId,
291 owner_id: u64,
292}
293
294impl Drop for SocketReconnectOwnerInner {
295 fn drop(&mut self) {
296 let Some(registry) = self.registry.upgrade() else {
297 return;
298 };
299
300 let removed = {
301 let mut inner = registry.lock();
302 if let Some(owners) = inner.owners.get_mut(&self.client_id) {
303 owners.remove(&self.owner_id);
304 if owners.is_empty() {
305 inner.owners.remove(&self.client_id);
306 }
307 }
308
309 let mut removed = Vec::new();
310 inner.entries.retain(|key, entries| {
311 if key.client_id == self.client_id
312 && let Some(entry) = entries.remove(&self.owner_id)
313 {
314 removed.push(entry);
315 }
316
317 !entries.is_empty()
318 });
319
320 removed
321 };
322
323 for entry in removed {
324 deactivate(Some(entry));
325 }
326 }
327}
328
329#[derive(Debug)]
330struct SocketReconnectRegistration {
331 registry: Weak<Mutex<RegistryInner>>,
332 key: SocketEndpoint,
333 owner_id: u64,
334 generation: u64,
335}
336
337impl Drop for SocketReconnectRegistration {
338 fn drop(&mut self) {
339 let Some(registry) = self.registry.upgrade() else {
340 return;
341 };
342
343 let entry = remove_entry(®istry, self.key, self.owner_id, Some(self.generation));
344 deactivate(entry);
345 }
346}
347
348fn remove_entry(
349 registry: &Arc<Mutex<RegistryInner>>,
350 key: SocketEndpoint,
351 owner_id: u64,
352 generation: Option<u64>,
353) -> Option<RegistryEntry> {
354 let mut inner = registry.lock();
355 let mut entry = None;
356 let mut remove_endpoint = false;
357
358 if let Some(entries) = inner.entries.get_mut(&key) {
359 let matches = entries
360 .get(&owner_id)
361 .is_some_and(|entry| generation.is_none_or(|value| entry.generation == value));
362
363 if matches {
364 entry = entries.remove(&owner_id);
365 }
366
367 remove_endpoint = entries.is_empty();
368 }
369
370 if remove_endpoint {
371 inner.entries.remove(&key);
372 }
373
374 entry
375}
376
377fn deactivate(entry: Option<RegistryEntry>) {
378 if let Some(entry) = entry {
379 *entry.request.lock() = None;
380 }
381}
382
383#[derive(Clone, Debug)]
385pub struct SocketControlFactory {
386 client_id: ClientId,
387 venue: Option<Venue>,
388 sender: Option<EventSender<SystemEvent>>,
389 owner: Option<SocketReconnectOwner>,
390 controls: Arc<Mutex<AHashMap<Ustr, SocketControl>>>,
391}
392
393impl SocketControlFactory {
394 #[must_use]
396 pub fn new(client_id: ClientId, venue: Option<Venue>) -> Self {
397 let registrar = SOCKET_REGISTRARS.with(|registrars| registrars.borrow().last().cloned());
398 Self::from_registrar(client_id, venue, registrar)
399 }
400
401 #[must_use]
403 pub fn with_registry(
404 client_id: ClientId,
405 venue: Option<Venue>,
406 registry: &SocketReconnectRegistry,
407 ) -> Self {
408 Self::from_registrar(client_id, venue, Some(registry.registrar()))
409 }
410
411 #[must_use]
413 pub fn control(&self, endpoint: impl AsRef<str>) -> SocketControl {
414 let endpoint = Ustr::from(endpoint.as_ref());
415 let mut controls = self.controls.lock();
416 if let Some(control) = controls.get(&endpoint) {
417 return control.clone();
418 }
419
420 let control = SocketControl {
421 publisher: SocketStatePublisher {
422 client_id: self.client_id,
423 venue: self.venue,
424 endpoint,
425 sender: self.sender.clone(),
426 active_generation: Arc::new(AtomicU64::new(0)),
427 publish_lock: Arc::new(Mutex::new(())),
428 },
429 owner: self.owner.clone(),
430 generation: AtomicU64::new(0),
431 registration: Mutex::new(None),
432 };
433
434 controls.insert(endpoint, control.clone());
435 control
436 }
437
438 fn from_registrar(
439 client_id: ClientId,
440 venue: Option<Venue>,
441 registrar: Option<SocketReconnectRegistrar>,
442 ) -> Self {
443 Self {
444 client_id,
445 venue,
446 sender: try_get_system_event_sender(),
447 owner: registrar.and_then(|registrar| registrar.owner(client_id)),
448 controls: Arc::new(Mutex::new(AHashMap::new())),
449 }
450 }
451}
452
453#[derive(Clone, Debug)]
454struct SocketStatePublisher {
455 client_id: ClientId,
456 venue: Option<Venue>,
457 endpoint: Ustr,
458 sender: Option<EventSender<SystemEvent>>,
459 active_generation: Arc<AtomicU64>,
460 publish_lock: Arc<Mutex<()>>,
461}
462
463impl SocketStatePublisher {
464 fn publish(&self, state: SocketState) {
465 let Some(sender) = &self.sender else {
466 return;
467 };
468
469 let state = match state {
470 SocketState::Connected => SystemSocketState::Connected,
471 SocketState::Disconnected => SystemSocketState::Disconnected,
472 };
473
474 let change = SocketStateChange::new(self.client_id, self.venue, self.endpoint, state);
475 if let Err(e) = sender.send(SystemEvent::SocketState(change)) {
476 log::error!("Failed to emit socket state change: {e}");
477 }
478 }
479
480 fn is_current(&self, generation: u64) -> bool {
481 self.active_generation.load(Ordering::Acquire) == generation
482 }
483
484 fn publish_if_current<F>(&self, generation: u64, state: SocketState, on_state: &F)
485 where
486 F: Fn(SocketState),
487 {
488 let _guard = self.publish_lock.lock();
489
490 if self.is_current(generation) {
491 on_state(state);
492 self.publish(state);
493 }
494 }
495}
496
497#[derive(Debug)]
501pub struct SocketControl {
502 publisher: SocketStatePublisher,
503 owner: Option<SocketReconnectOwner>,
504 generation: AtomicU64,
505 registration: Mutex<Option<SocketReconnectRegistration>>,
506}
507
508impl Clone for SocketControl {
509 fn clone(&self) -> Self {
510 Self {
511 publisher: self.publisher.clone(),
512 owner: self.owner.clone(),
513 generation: AtomicU64::new(0),
514 registration: Mutex::new(None),
515 }
516 }
517}
518
519impl SocketControl {
520 #[must_use]
522 pub fn new(client_id: ClientId, venue: Option<Venue>, endpoint: impl AsRef<str>) -> Self {
523 SocketControlFactory::new(client_id, venue).control(endpoint)
524 }
525
526 #[must_use]
528 pub fn with_registry(
529 client_id: ClientId,
530 venue: Option<Venue>,
531 endpoint: impl AsRef<str>,
532 registry: &SocketReconnectRegistry,
533 ) -> Self {
534 SocketControlFactory::with_registry(client_id, venue, registry).control(endpoint)
535 }
536
537 #[must_use]
539 pub fn sink(&self) -> SocketStateSink {
540 self.sink_with(|_| {})
541 }
542
543 #[must_use]
547 pub fn sink_with<F>(&self, on_state: F) -> SocketStateSink
548 where
549 F: Fn(SocketState) + Send + Sync + 'static,
550 {
551 let (registration, replaced, generation) = {
552 let _guard = self.publisher.publish_lock.lock();
553 let replaced = self
554 .owner
555 .as_ref()
556 .and_then(|owner| owner.remove(self.publisher.endpoint));
557 let generation = advance_generation(&self.publisher.active_generation);
558 self.generation.store(generation, Ordering::Release);
559 let registration = self.registration.lock().take();
560 (registration, replaced, generation)
561 };
562
563 deactivate(replaced);
564 drop(registration);
565
566 let publisher = self.publisher.clone();
567
568 SocketStateSink::new(move |state| {
569 publisher.publish_if_current(generation, state, &on_state);
570 })
571 }
572
573 pub fn register<F>(&self, request: F)
575 where
576 F: Fn() -> ReconnectRequestOutcome + Send + Sync + 'static,
577 {
578 let (replaced, old_registration) = {
579 let _guard = self.publisher.publish_lock.lock();
580 let generation = self.generation.load(Ordering::Acquire);
581 if generation == 0 || !self.publisher.is_current(generation) {
582 return;
583 }
584
585 let (registration, replaced) = if let Some(owner) = &self.owner {
586 owner.register(self.publisher.endpoint, SocketReconnectHandle::new(request))
587 } else {
588 (None, None)
589 };
590
591 let mut current = self.registration.lock();
592 let old_registration = std::mem::replace(&mut *current, registration);
593 (replaced, old_registration)
594 };
595
596 deactivate(replaced);
597 drop(old_registration);
598 }
599
600 pub fn deregister(&self) {
602 let registration = {
603 let _guard = self.publisher.publish_lock.lock();
604 self.generation.store(0, Ordering::Release);
605 self.registration.lock().take()
606 };
607
608 drop(registration);
609 }
610}
611
612impl Drop for SocketControl {
613 fn drop(&mut self) {
614 self.deregister();
615 }
616}
617
618fn advance_generation(counter: &AtomicU64) -> u64 {
619 let mut current = counter.load(Ordering::Relaxed);
620
621 loop {
622 let next = current.wrapping_add(1).max(1);
623 match counter.compare_exchange_weak(current, next, Ordering::Release, Ordering::Relaxed) {
624 Ok(_) => return next,
625 Err(actual) => current = actual,
626 }
627 }
628}
629
630#[cfg(test)]
631mod tests {
632 use std::{
633 sync::{
634 Arc, Barrier,
635 atomic::{AtomicUsize, Ordering},
636 },
637 thread,
638 time::{Duration, Instant},
639 };
640
641 use rstest::rstest;
642
643 use super::*;
644
645 const ENDPOINT: &str = "test-streams";
646
647 fn control(registry: &SocketReconnectRegistry) -> SocketControl {
648 SocketControl::with_registry(
649 ClientId::from("TEST"),
650 Some(Venue::from("TEST")),
651 ENDPOINT,
652 registry,
653 )
654 }
655
656 fn handle(registry: &SocketReconnectRegistry) -> SocketReconnectHandle {
657 let SocketReconnectLookup::Handle(handle) =
658 registry.get(ClientId::from("TEST"), Ustr::from(ENDPOINT))
659 else {
660 panic!("test socket endpoint should be registered");
661 };
662
663 handle
664 }
665
666 #[rstest]
667 fn publishes_endpoint_state() {
668 let registry = SocketReconnectRegistry::default();
669 let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel();
670 let mut control = control(®istry);
671 control.publisher.sender = Some(sender.into());
672 let _sink = control.sink();
673
674 control.publisher.publish(SocketState::Connected);
675 control.publisher.publish(SocketState::Disconnected);
676
677 let SystemEvent::SocketState(connected) = receiver.try_recv().unwrap();
678 let SystemEvent::SocketState(disconnected) = receiver.try_recv().unwrap();
679 assert_eq!(connected.client_id, ClientId::from("TEST"));
680 assert_eq!(connected.venue, Some(Venue::from("TEST")));
681 assert_eq!(connected.endpoint, Ustr::from(ENDPOINT));
682 assert_eq!(connected.state, SystemSocketState::Connected);
683 assert_eq!(disconnected.client_id, ClientId::from("TEST"));
684 assert_eq!(disconnected.venue, Some(Venue::from("TEST")));
685 assert_eq!(disconnected.endpoint, Ustr::from(ENDPOINT));
686 assert_eq!(disconnected.state, SystemSocketState::Disconnected);
687 }
688
689 #[rstest]
690 fn replacement_sink_suppresses_stale_transport_state() {
691 let registry = SocketReconnectRegistry::default();
692 let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel();
693 let mut first = control(®istry);
694 first.publisher.sender = Some(sender.clone().into());
695 let mut replacement = first.clone();
696 replacement.publisher.sender = Some(sender.into());
697 let _stale_sink = first.sink();
698 let stale_generation = first.generation.load(Ordering::Acquire);
699 let _current_sink = replacement.sink();
700 let current_generation = replacement.generation.load(Ordering::Acquire);
701
702 first
703 .publisher
704 .publish_if_current(stale_generation, SocketState::Disconnected, &|_| {});
705 replacement.publisher.publish_if_current(
706 current_generation,
707 SocketState::Connected,
708 &|_| {},
709 );
710
711 let SystemEvent::SocketState(change) = receiver.try_recv().unwrap();
712 assert_eq!(change.state, SystemSocketState::Connected);
713 assert!(receiver.try_recv().is_err());
714 }
715
716 #[rstest]
717 #[case(ReconnectRequestOutcome::Accepted)]
718 #[case(ReconnectRequestOutcome::AlreadyReconnecting)]
719 #[case(ReconnectRequestOutcome::Disconnected)]
720 #[case(ReconnectRequestOutcome::Closed)]
721 #[case(ReconnectRequestOutcome::Unsupported)]
722 fn register_preserves_transport_outcome(#[case] expected: ReconnectRequestOutcome) {
723 let registry = SocketReconnectRegistry::default();
724 let control = control(®istry);
725 let _sink = control.sink();
726 control.register(move || expected);
727
728 assert_eq!(handle(®istry).request_reconnect(), expected);
729 }
730
731 #[rstest]
732 fn replacement_invalidates_stale_handle() {
733 let registry = SocketReconnectRegistry::default();
734 let factory = SocketControlFactory::with_registry(
735 ClientId::from("TEST"),
736 Some(Venue::from("TEST")),
737 ®istry,
738 );
739 let first = factory.control(ENDPOINT);
740 let _first_sink = first.sink();
741 first.register(|| ReconnectRequestOutcome::Accepted);
742 let stale_handle = handle(®istry);
743 let replacement = factory.control(ENDPOINT);
744 let _replacement_sink = replacement.sink();
745 replacement.register(|| ReconnectRequestOutcome::AlreadyReconnecting);
746
747 first.register(|| ReconnectRequestOutcome::Disconnected);
748 first.deregister();
749
750 assert_eq!(
751 stale_handle.request_reconnect(),
752 ReconnectRequestOutcome::Closed
753 );
754 assert_eq!(
755 handle(®istry).request_reconnect(),
756 ReconnectRequestOutcome::AlreadyReconnecting
757 );
758 }
759
760 #[rstest]
761 fn replacement_waits_for_an_inflight_request_that_publishes_state() {
762 let registry = SocketReconnectRegistry::default();
763 let factory = SocketControlFactory::with_registry(
764 ClientId::from("TEST"),
765 Some(Venue::from("TEST")),
766 ®istry,
767 );
768 let first = factory.control(ENDPOINT);
769 let _first_sink = first.sink();
770 let generation = first.generation.load(Ordering::Acquire);
771 let publisher = first.publisher.clone();
772 let entered = Arc::new(Barrier::new(2));
773 let release = Arc::new(Barrier::new(2));
774 let request_entered = Arc::clone(&entered);
775 let request_release = Arc::clone(&release);
776 first.register(move || {
777 request_entered.wait();
778 request_release.wait();
779 publisher.publish_if_current(generation, SocketState::Disconnected, &|_| {});
780 ReconnectRequestOutcome::Accepted
781 });
782
783 let stale_handle = handle(®istry);
784 let request_handle = stale_handle.clone();
785
786 let request = thread::spawn(move || request_handle.request_reconnect());
787 entered.wait();
788
789 let replacement = factory.control(ENDPOINT);
790 let (replaced_tx, replaced_rx) = std::sync::mpsc::channel();
791
792 let replace = thread::spawn(move || {
793 let _replacement_sink = replacement.sink();
794 replaced_tx.send(()).unwrap();
795 });
796
797 let deadline = Instant::now() + Duration::from_secs(1);
798
799 while !matches!(
800 registry.get(ClientId::from("TEST"), Ustr::from(ENDPOINT)),
801 SocketReconnectLookup::EndpointNotFound
802 ) {
803 assert!(
804 Instant::now() < deadline,
805 "replacement did not remove the stale entry"
806 );
807 thread::yield_now();
808 }
809
810 assert!(replaced_rx.try_recv().is_err());
811 release.wait();
812 assert_eq!(request.join().unwrap(), ReconnectRequestOutcome::Accepted);
813 replace.join().unwrap();
814 assert!(replaced_rx.try_recv().is_ok());
815 assert_eq!(
816 stale_handle.request_reconnect(),
817 ReconnectRequestOutcome::Closed
818 );
819 }
820
821 #[rstest]
822 fn reregistration_waits_for_an_inflight_request_that_publishes_state() {
823 let registry = SocketReconnectRegistry::default();
824 let control = Arc::new(control(®istry));
825 let _sink = control.sink();
826 let generation = control.generation.load(Ordering::Acquire);
827 let publisher = control.publisher.clone();
828 let entered = Arc::new(Barrier::new(2));
829 let release = Arc::new(Barrier::new(2));
830 let request_entered = Arc::clone(&entered);
831 let request_release = Arc::clone(&release);
832 control.register(move || {
833 request_entered.wait();
834 request_release.wait();
835 publisher.publish_if_current(generation, SocketState::Disconnected, &|_| {});
836 ReconnectRequestOutcome::Accepted
837 });
838
839 let stale_handle = handle(®istry);
840
841 let key = SocketEndpoint {
842 client_id: ClientId::from("TEST"),
843 endpoint: Ustr::from(ENDPOINT),
844 };
845
846 let old_generation = registry
847 .inner
848 .lock()
849 .entries
850 .get(&key)
851 .and_then(|entries| entries.values().next())
852 .map(|entry| entry.generation)
853 .unwrap();
854 let request_handle = stale_handle.clone();
855
856 let request = thread::spawn(move || request_handle.request_reconnect());
857 entered.wait();
858
859 let replacement = Arc::clone(&control);
860 let (registered_tx, registered_rx) = std::sync::mpsc::channel();
861
862 let register = thread::spawn(move || {
863 replacement.register(|| ReconnectRequestOutcome::AlreadyReconnecting);
864 registered_tx.send(()).unwrap();
865 });
866
867 let deadline = Instant::now() + Duration::from_secs(1);
868
869 loop {
870 let old_entry_is_registered = registry
871 .inner
872 .lock()
873 .entries
874 .get(&key)
875 .and_then(|entries| entries.values().next())
876 .is_some_and(|entry| entry.generation == old_generation);
877
878 if !old_entry_is_registered {
879 break;
880 }
881
882 assert!(
883 Instant::now() < deadline,
884 "replacement reconnect handle was not registered"
885 );
886 thread::yield_now();
887 }
888
889 assert!(registered_rx.try_recv().is_err());
890 release.wait();
891 assert_eq!(request.join().unwrap(), ReconnectRequestOutcome::Accepted);
892 register.join().unwrap();
893 assert!(registered_rx.try_recv().is_ok());
894 assert_eq!(
895 stale_handle.request_reconnect(),
896 ReconnectRequestOutcome::Closed
897 );
898 assert_eq!(
899 handle(®istry).request_reconnect(),
900 ReconnectRequestOutcome::AlreadyReconnecting
901 );
902 }
903
904 #[rstest]
905 fn registry_drop_revokes_a_retained_handle() {
906 let registry = SocketReconnectRegistry::default();
907 let control = control(®istry);
908 let _sink = control.sink();
909 control.register(|| ReconnectRequestOutcome::Accepted);
910 let retained = handle(®istry);
911
912 drop(registry);
913
914 assert_eq!(
915 retained.request_reconnect(),
916 ReconnectRequestOutcome::Closed
917 );
918 }
919
920 #[rstest]
921 fn deregister_prevents_delayed_registration() {
922 let registry = SocketReconnectRegistry::default();
923 let control = control(®istry);
924 let _sink = control.sink();
925
926 control.deregister();
927 control.register(|| ReconnectRequestOutcome::Accepted);
928
929 assert!(matches!(
930 registry.get(ClientId::from("TEST"), Ustr::from(ENDPOINT)),
931 SocketReconnectLookup::EndpointNotFound
932 ));
933 }
934
935 #[rstest]
936 fn ambiguous_endpoint_does_not_invoke_either_owner() {
937 let registry = SocketReconnectRegistry::default();
938 let first_count = Arc::new(AtomicUsize::new(0));
939 let second_count = Arc::new(AtomicUsize::new(0));
940 let first = control(®istry);
941 let second = control(®istry);
942 let _first_sink = first.sink();
943 let _second_sink = second.sink();
944 let first_callback = Arc::clone(&first_count);
945 first.register(move || {
946 first_callback.fetch_add(1, Ordering::SeqCst);
947 ReconnectRequestOutcome::Accepted
948 });
949
950 let second_callback = Arc::clone(&second_count);
951 second.register(move || {
952 second_callback.fetch_add(1, Ordering::SeqCst);
953 ReconnectRequestOutcome::Accepted
954 });
955
956 assert!(matches!(
957 registry.get(ClientId::from("TEST"), Ustr::from(ENDPOINT)),
958 SocketReconnectLookup::AmbiguousEndpoint
959 ));
960 assert_eq!(first_count.load(Ordering::SeqCst), 0);
961 assert_eq!(second_count.load(Ordering::SeqCst), 0);
962 }
963
964 #[rstest]
965 fn distinguishes_unknown_unsupported_and_missing_endpoint() {
966 let registry = SocketReconnectRegistry::default();
967 registry.register_client(ClientId::from("UNSUPPORTED"));
968 let _control = control(®istry);
969
970 assert!(matches!(
971 registry.get(ClientId::from("UNKNOWN"), Ustr::from(ENDPOINT)),
972 SocketReconnectLookup::ClientNotFound
973 ));
974 assert!(matches!(
975 registry.get(ClientId::from("UNSUPPORTED"), Ustr::from(ENDPOINT)),
976 SocketReconnectLookup::Unsupported
977 ));
978 assert!(matches!(
979 registry.get(ClientId::from("TEST"), Ustr::from("unknown")),
980 SocketReconnectLookup::EndpointNotFound
981 ));
982 }
983
984 #[rstest]
985 fn scope_restores_the_prior_registry() {
986 let outer = SocketReconnectRegistry::default();
987 let inner = SocketReconnectRegistry::default();
988
989 let (outer_control, inner_control) = outer.scope(|| {
990 let outer_control =
991 SocketControl::new(ClientId::from("OUTER"), Some(Venue::from("TEST")), ENDPOINT);
992
993 let inner_control = inner.scope(|| {
994 SocketControl::new(ClientId::from("INNER"), Some(Venue::from("TEST")), ENDPOINT)
995 });
996
997 let _sink = inner_control.sink();
998 inner_control.register(|| ReconnectRequestOutcome::Accepted);
999 (outer_control, inner_control)
1000 });
1001
1002 let _sink = outer_control.sink();
1003 outer_control.register(|| ReconnectRequestOutcome::Accepted);
1004
1005 assert!(matches!(
1006 outer.get(ClientId::from("OUTER"), Ustr::from(ENDPOINT)),
1007 SocketReconnectLookup::Handle(_)
1008 ));
1009 assert!(matches!(
1010 inner.get(ClientId::from("INNER"), Ustr::from(ENDPOINT)),
1011 SocketReconnectLookup::Handle(_)
1012 ));
1013 drop(inner_control);
1014 }
1015}