1#![allow(unsafe_code)]
23
24use std::{
25 cell::{RefCell, UnsafeCell},
26 fmt::Debug,
27 rc::Rc,
28};
29
30use ahash::{AHashMap, AHashSet};
31use nautilus_model::identifiers::{ComponentId, TraderId};
32use thiserror::Error;
33use ustr::Ustr;
34
35use crate::{
36 actor::{Actor, registry::with_actor_registry},
37 cache::Cache,
38 clock::Clock,
39 enums::{ComponentState, ComponentTrigger},
40};
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
47pub enum ComponentAccessError {
48 #[error("Cannot access {resource} during {operation}: the actor is not registered")]
50 NotRegistered {
51 resource: &'static str,
53 operation: &'static str,
55 },
56 #[error(
58 "Cannot read {resource} during {operation}: it is already mutably borrowed. Release the existing borrow before accessing it again; callback reentry can cause this conflict"
59 )]
60 ReadConflict {
61 resource: &'static str,
63 operation: &'static str,
65 },
66 #[error(
68 "Cannot modify {resource} during {operation}: it is already borrowed. Release existing borrows before accessing it mutably; callback reentry can cause this conflict"
69 )]
70 WriteConflict {
71 resource: &'static str,
73 operation: &'static str,
75 },
76}
77
78pub trait Component {
80 fn component_id(&self) -> ComponentId;
82
83 fn state(&self) -> ComponentState;
85
86 fn transition_state(&mut self, trigger: ComponentTrigger) -> anyhow::Result<()>;
92
93 fn is_ready(&self) -> bool {
95 self.state() == ComponentState::Ready
96 }
97
98 fn not_running(&self) -> bool {
100 !self.is_running()
101 }
102
103 fn is_running(&self) -> bool {
105 self.state() == ComponentState::Running
106 }
107
108 fn is_stopped(&self) -> bool {
110 self.state() == ComponentState::Stopped
111 }
112
113 fn is_degraded(&self) -> bool {
115 self.state() == ComponentState::Degraded
116 }
117
118 fn is_faulted(&self) -> bool {
120 self.state() == ComponentState::Faulted
121 }
122
123 fn is_disposed(&self) -> bool {
125 self.state() == ComponentState::Disposed
126 }
127
128 fn register(
134 &mut self,
135 trader_id: TraderId,
136 clock: Rc<RefCell<dyn Clock>>,
137 cache: Rc<RefCell<Cache>>,
138 ) -> anyhow::Result<()>;
139
140 fn initialize(&mut self) -> anyhow::Result<()> {
146 self.transition_state(ComponentTrigger::Initialize)
147 }
148
149 fn start(&mut self) -> anyhow::Result<()> {
155 self.transition_state(ComponentTrigger::Start)?; self.on_start()?;
158
159 self.transition_state(ComponentTrigger::StartCompleted)?;
160
161 Ok(())
162 }
163
164 fn stop(&mut self) -> anyhow::Result<()> {
170 self.transition_state(ComponentTrigger::Stop)?; self.on_stop()?;
173
174 self.transition_state(ComponentTrigger::StopCompleted)?;
175
176 Ok(())
177 }
178
179 fn resume(&mut self) -> anyhow::Result<()> {
185 self.transition_state(ComponentTrigger::Resume)?; self.on_resume()?;
188
189 self.transition_state(ComponentTrigger::ResumeCompleted)?;
190
191 Ok(())
192 }
193
194 fn degrade(&mut self) -> anyhow::Result<()> {
200 self.transition_state(ComponentTrigger::Degrade)?; self.on_degrade()?;
203
204 self.transition_state(ComponentTrigger::DegradeCompleted)?;
205
206 Ok(())
207 }
208
209 fn fault(&mut self) -> anyhow::Result<()> {
221 self.transition_state(ComponentTrigger::Fault)?; let result = self.on_fault();
224 self.release_subscriptions();
225
226 result?;
227
228 self.transition_state(ComponentTrigger::FaultCompleted)?;
229
230 Ok(())
231 }
232
233 fn reset(&mut self) -> anyhow::Result<()> {
245 self.transition_state(ComponentTrigger::Reset)?; self.on_reset()?;
248
249 self.release_subscriptions();
250 self.transition_state(ComponentTrigger::ResetCompleted)?;
251
252 Ok(())
253 }
254
255 fn dispose(&mut self) -> anyhow::Result<()> {
271 self.transition_state(ComponentTrigger::Dispose)?; if let Err(e) = self.on_dispose() {
274 self.transition_state(ComponentTrigger::Fault)?; self.transition_state(ComponentTrigger::FaultCompleted)?; return Err(e);
278 }
279
280 self.release_subscriptions();
281 self.transition_state(ComponentTrigger::DisposeCompleted)?;
282
283 Ok(())
284 }
285
286 fn release_subscriptions(&mut self) {}
292
293 fn on_start(&mut self) -> anyhow::Result<()> {
299 log::warn!(
300 "The `on_start` handler was called when not overridden, \
301 it's expected that any actions required when stopping the component \
302 occur here, such as unsubscribing from data",
303 );
304 Ok(())
305 }
306
307 fn on_stop(&mut self) -> anyhow::Result<()> {
313 log::warn!(
314 "The `on_stop` handler was called when not overridden, \
315 it's expected that any actions required when stopping the component \
316 occur here, such as unsubscribing from data",
317 );
318 Ok(())
319 }
320
321 fn on_resume(&mut self) -> anyhow::Result<()> {
327 log::warn!(
328 "The `on_resume` handler was called when not overridden, \
329 it's expected that any actions required when resuming the component \
330 following a stop occur here"
331 );
332 Ok(())
333 }
334
335 fn on_reset(&mut self) -> anyhow::Result<()> {
341 log::warn!(
342 "The `on_reset` handler was called when not overridden, \
343 it's expected that any actions required when resetting the component \
344 occur here, such as resetting indicators and other state"
345 );
346 Ok(())
347 }
348
349 fn on_dispose(&mut self) -> anyhow::Result<()> {
355 Ok(())
356 }
357
358 fn on_degrade(&mut self) -> anyhow::Result<()> {
364 Ok(())
365 }
366
367 fn on_fault(&mut self) -> anyhow::Result<()> {
373 Ok(())
374 }
375}
376
377#[rustfmt::skip]
378impl ComponentState {
379 pub fn transition(&mut self, trigger: &ComponentTrigger) -> anyhow::Result<Self> {
385 let new_state = match (&self, trigger) {
386 (Self::PreInitialized, ComponentTrigger::Initialize) => Self::Ready,
387 (Self::Ready, ComponentTrigger::Reset) => Self::Resetting,
388 (Self::Ready, ComponentTrigger::Start) => Self::Starting,
389 (Self::Ready, ComponentTrigger::Dispose) => Self::Disposing,
390 (Self::Resetting, ComponentTrigger::ResetCompleted) => Self::Ready,
391 (Self::Starting, ComponentTrigger::StartCompleted) => Self::Running,
392 (Self::Starting, ComponentTrigger::Stop) => Self::Stopping,
393 (Self::Starting, ComponentTrigger::Fault) => Self::Faulting,
394 (Self::Running, ComponentTrigger::Stop) => Self::Stopping,
395 (Self::Running, ComponentTrigger::Degrade) => Self::Degrading,
396 (Self::Running, ComponentTrigger::Fault) => Self::Faulting,
397 (Self::Resuming, ComponentTrigger::Stop) => Self::Stopping,
398 (Self::Resuming, ComponentTrigger::ResumeCompleted) => Self::Running,
399 (Self::Resuming, ComponentTrigger::Fault) => Self::Faulting,
400 (Self::Stopping, ComponentTrigger::StopCompleted) => Self::Stopped,
401 (Self::Stopping, ComponentTrigger::Dispose) => Self::Disposing,
402 (Self::Stopping, ComponentTrigger::Fault) => Self::Faulting,
403 (Self::Stopped, ComponentTrigger::Reset) => Self::Resetting,
404 (Self::Stopped, ComponentTrigger::Resume) => Self::Resuming,
405 (Self::Stopped, ComponentTrigger::Dispose) => Self::Disposing,
406 (Self::Stopped, ComponentTrigger::Fault) => Self::Faulting,
407 (Self::Degrading, ComponentTrigger::DegradeCompleted) => Self::Degraded,
408 (Self::Degraded, ComponentTrigger::Resume) => Self::Resuming,
409 (Self::Degraded, ComponentTrigger::Stop) => Self::Stopping,
410 (Self::Degraded, ComponentTrigger::Fault) => Self::Faulting,
411 (Self::Disposing, ComponentTrigger::DisposeCompleted) => Self::Disposed,
412 (Self::Disposing, ComponentTrigger::Fault) => Self::Faulting,
413 (Self::Faulting, ComponentTrigger::Dispose) => Self::Disposing,
414 (Self::Faulting, ComponentTrigger::FaultCompleted) => Self::Faulted,
415 _ => anyhow::bail!("Invalid state trigger {self} -> {trigger}"),
416 };
417 Ok(new_state)
418 }
419}
420
421thread_local! {
422 static COMPONENT_REGISTRY: ComponentRegistry = ComponentRegistry::new();
423}
424
425pub struct ComponentRegistry {
430 components: RefCell<AHashMap<Ustr, Rc<UnsafeCell<dyn Component>>>>,
431 borrows: RefCell<AHashSet<Ustr>>,
432}
433
434impl Debug for ComponentRegistry {
435 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
436 let components_ref = self.components.borrow();
437 let keys: Vec<&Ustr> = components_ref.keys().collect();
438 f.debug_struct(stringify!(ComponentRegistry))
439 .field("components", &keys)
440 .field("active_borrows", &self.borrows.borrow().len())
441 .finish()
442 }
443}
444
445impl Default for ComponentRegistry {
446 fn default() -> Self {
447 Self::new()
448 }
449}
450
451impl ComponentRegistry {
452 pub fn new() -> Self {
453 Self {
454 components: RefCell::new(AHashMap::new()),
455 borrows: RefCell::new(AHashSet::new()),
456 }
457 }
458
459 pub fn insert(&self, id: Ustr, component: Rc<UnsafeCell<dyn Component>>) {
460 self.components.borrow_mut().insert(id, component);
461 }
462
463 pub fn get(&self, id: &Ustr) -> Option<Rc<UnsafeCell<dyn Component>>> {
464 self.components.borrow().get(id).cloned()
465 }
466
467 pub fn remove(&self, id: &Ustr) -> Option<Rc<UnsafeCell<dyn Component>>> {
469 self.components.borrow_mut().remove(id)
470 }
471
472 pub fn is_borrowed(&self, id: &Ustr) -> bool {
474 self.borrows.borrow().contains(id)
475 }
476
477 fn try_borrow(&self, id: Ustr) -> bool {
479 let mut borrows = self.borrows.borrow_mut();
480 if borrows.contains(&id) {
481 false
482 } else {
483 borrows.insert(id);
484 true
485 }
486 }
487
488 fn release_borrow(&self, id: &Ustr) {
490 self.borrows.borrow_mut().remove(id);
491 }
492}
493
494struct BorrowGuard {
499 id: Ustr,
500}
501
502impl BorrowGuard {
503 fn new(id: Ustr) -> Self {
504 Self { id }
505 }
506}
507
508impl Drop for BorrowGuard {
509 fn drop(&mut self) {
510 with_component_registry(|registry| registry.release_borrow(&self.id));
511 }
512}
513
514pub fn with_component_registry<R>(f: impl FnOnce(&ComponentRegistry) -> R) -> R {
515 COMPONENT_REGISTRY.with(f)
516}
517
518pub fn register_component<T>(component: T) -> Rc<UnsafeCell<T>>
520where
521 T: Component + 'static,
522{
523 let component_id = component.component_id().inner();
524 let component_ref = Rc::new(UnsafeCell::new(component));
525
526 let component_trait_ref: Rc<UnsafeCell<dyn Component>> = component_ref.clone();
528 with_component_registry(|registry| registry.insert(component_id, component_trait_ref));
529
530 component_ref
531}
532
533pub fn register_component_actor<T>(component: T) -> Rc<UnsafeCell<T>>
535where
536 T: Component + Actor + 'static,
537{
538 let component_id = component.component_id().inner();
539 let actor_id = component.id();
540 let component_ref = Rc::new(UnsafeCell::new(component));
541
542 let component_trait_ref: Rc<UnsafeCell<dyn Component>> = component_ref.clone();
544 with_component_registry(|registry| registry.insert(component_id, component_trait_ref));
545
546 let actor_trait_ref: Rc<UnsafeCell<dyn Actor>> = component_ref.clone();
548 with_actor_registry(|registry| registry.insert(actor_id, actor_trait_ref));
549
550 component_ref
551}
552
553pub fn start_component(id: &Ustr) -> anyhow::Result<()> {
562 let component_ref = with_component_registry(|registry| {
563 let component_ref = registry
564 .get(id)
565 .ok_or_else(|| anyhow::anyhow!("Component '{id}' not found in global registry"))?;
566
567 if !registry.try_borrow(*id) {
568 anyhow::bail!(
569 "Component '{id}' is already mutably borrowed. \
570 This would create aliasing mutable references (undefined behavior)."
571 );
572 }
573
574 Ok::<_, anyhow::Error>(component_ref)
575 })?;
576
577 let _guard = BorrowGuard::new(*id);
578
579 unsafe {
581 let component = &mut *component_ref.get();
582 component.start()
583 }
584}
585
586pub fn component_state(id: &Ustr) -> anyhow::Result<ComponentState> {
594 let component_ref = with_component_registry(|registry| {
595 let component_ref = registry
596 .get(id)
597 .ok_or_else(|| anyhow::anyhow!("Component '{id}' not found in global registry"))?;
598
599 if !registry.try_borrow(*id) {
600 anyhow::bail!(
601 "Component '{id}' is already mutably borrowed. \
602 This would create aliasing mutable references (undefined behavior)."
603 );
604 }
605
606 Ok::<_, anyhow::Error>(component_ref)
607 })?;
608
609 let _guard = BorrowGuard::new(*id);
610
611 unsafe {
613 let component = &*component_ref.get();
614 Ok(component.state())
615 }
616}
617
618pub fn stop_component(id: &Ustr) -> anyhow::Result<()> {
627 let component_ref = with_component_registry(|registry| {
628 let component_ref = registry
629 .get(id)
630 .ok_or_else(|| anyhow::anyhow!("Component '{id}' not found in global registry"))?;
631
632 if !registry.try_borrow(*id) {
633 anyhow::bail!(
634 "Component '{id}' is already mutably borrowed. \
635 This would create aliasing mutable references (undefined behavior)."
636 );
637 }
638
639 Ok::<_, anyhow::Error>(component_ref)
640 })?;
641
642 let _guard = BorrowGuard::new(*id);
643
644 unsafe {
646 let component = &mut *component_ref.get();
647 component.stop()
648 }
649}
650
651pub fn reset_component(id: &Ustr) -> anyhow::Result<()> {
660 let component_ref = with_component_registry(|registry| {
661 let component_ref = registry
662 .get(id)
663 .ok_or_else(|| anyhow::anyhow!("Component '{id}' not found in global registry"))?;
664
665 if !registry.try_borrow(*id) {
666 anyhow::bail!(
667 "Component '{id}' is already mutably borrowed. \
668 This would create aliasing mutable references (undefined behavior)."
669 );
670 }
671
672 Ok::<_, anyhow::Error>(component_ref)
673 })?;
674
675 let _guard = BorrowGuard::new(*id);
676
677 unsafe {
679 let component = &mut *component_ref.get();
680 component.reset()
681 }
682}
683
684pub fn dispose_component(id: &Ustr) -> anyhow::Result<()> {
693 let component_ref = with_component_registry(|registry| {
694 let component_ref = registry
695 .get(id)
696 .ok_or_else(|| anyhow::anyhow!("Component '{id}' not found in global registry"))?;
697
698 if !registry.try_borrow(*id) {
699 anyhow::bail!(
700 "Component '{id}' is already mutably borrowed. \
701 This would create aliasing mutable references (undefined behavior)."
702 );
703 }
704
705 Ok::<_, anyhow::Error>(component_ref)
706 })?;
707
708 let _guard = BorrowGuard::new(*id);
709
710 unsafe {
712 let component = &mut *component_ref.get();
713 component.dispose()
714 }
715}
716
717pub fn release_component_subscriptions(id: &Ustr) -> anyhow::Result<()> {
728 let component_ref = with_component_registry(|registry| {
729 let component_ref = registry
730 .get(id)
731 .ok_or_else(|| anyhow::anyhow!("Component '{id}' not found in global registry"))?;
732
733 if !registry.try_borrow(*id) {
734 anyhow::bail!(
735 "Component '{id}' is already mutably borrowed. \
736 This would create aliasing mutable references (undefined behavior)."
737 );
738 }
739
740 Ok::<_, anyhow::Error>(component_ref)
741 })?;
742
743 let _guard = BorrowGuard::new(*id);
744
745 unsafe {
747 let component = &mut *component_ref.get();
748 component.release_subscriptions();
749 }
750
751 Ok(())
752}
753
754pub fn get_component(id: &Ustr) -> Option<Rc<UnsafeCell<dyn Component>>> {
756 with_component_registry(|registry| registry.get(id))
757}
758
759pub fn deregister_component(id: &Ustr) {
764 with_component_registry(|registry| registry.remove(id));
765}
766
767#[cfg(test)]
768pub fn clear_component_registry() {
770 with_component_registry(|registry| {
771 registry.components.borrow_mut().clear();
772 registry.borrows.borrow_mut().clear();
773 });
774}
775
776#[cfg(test)]
777mod tests {
778 use std::{
779 any::Any,
780 sync::atomic::{AtomicBool, Ordering},
781 };
782
783 use nautilus_core::UUID4;
784 use rstest::rstest;
785
786 use super::*;
787 use crate::logging::{
788 arm_shutdown_on_error, disarm_shutdown_on_error, init_logging,
789 take_shutdown_on_error_trigger,
790 };
791
792 #[derive(Debug)]
793 struct TestComponent {
794 id: ComponentId,
795 state: ComponentState,
796 should_panic: &'static AtomicBool,
797 hooks_fail: bool,
798 releases: usize,
799 }
800
801 impl TestComponent {
802 fn new(name: &str, should_panic: &'static AtomicBool) -> Self {
803 Self {
804 id: ComponentId::new(name),
805 state: ComponentState::Ready,
806 should_panic,
807 hooks_fail: false,
808 releases: 0,
809 }
810 }
811
812 fn register_in_global_registry(self) -> Ustr {
813 let id = self.id.inner();
814 let component_ref: Rc<UnsafeCell<dyn Component>> = Rc::new(UnsafeCell::new(self));
815 with_component_registry(|registry| registry.insert(id, component_ref));
816 id
817 }
818 }
819
820 impl Actor for TestComponent {
821 fn id(&self) -> Ustr {
822 self.id.inner()
823 }
824
825 fn handle(&mut self, _msg: &dyn Any) {}
826
827 fn as_any(&self) -> &dyn Any {
828 self
829 }
830 }
831
832 impl Component for TestComponent {
833 fn component_id(&self) -> ComponentId {
834 self.id
835 }
836
837 fn state(&self) -> ComponentState {
838 self.state
839 }
840
841 fn transition_state(&mut self, trigger: ComponentTrigger) -> anyhow::Result<()> {
842 self.state = self.state.transition(&trigger)?;
843 Ok(())
844 }
845
846 fn register(
847 &mut self,
848 _trader_id: TraderId,
849 _clock: Rc<RefCell<dyn Clock>>,
850 _cache: Rc<RefCell<Cache>>,
851 ) -> anyhow::Result<()> {
852 Ok(())
853 }
854
855 #[expect(clippy::panic_in_result_fn)] fn on_start(&mut self) -> anyhow::Result<()> {
857 assert!(
858 !self.should_panic.load(Ordering::SeqCst),
859 "Intentional panic for testing"
860 );
861
862 if self.hooks_fail {
863 anyhow::bail!("on_start failed");
864 }
865
866 Ok(())
867 }
868
869 fn on_stop(&mut self) -> anyhow::Result<()> {
870 if self.hooks_fail {
871 anyhow::bail!("on_stop failed");
872 }
873
874 Ok(())
875 }
876
877 fn on_resume(&mut self) -> anyhow::Result<()> {
878 if self.hooks_fail {
879 anyhow::bail!("on_resume failed");
880 }
881
882 Ok(())
883 }
884
885 fn on_reset(&mut self) -> anyhow::Result<()> {
886 if self.hooks_fail {
887 anyhow::bail!("on_reset failed");
888 }
889
890 Ok(())
891 }
892
893 fn on_dispose(&mut self) -> anyhow::Result<()> {
894 if self.hooks_fail {
895 anyhow::bail!("on_dispose failed");
896 }
897
898 Ok(())
899 }
900
901 fn on_fault(&mut self) -> anyhow::Result<()> {
902 if self.hooks_fail {
903 anyhow::bail!("on_fault failed");
904 }
905
906 Ok(())
907 }
908
909 fn on_degrade(&mut self) -> anyhow::Result<()> {
910 if self.hooks_fail {
911 anyhow::bail!("on_degrade failed");
912 }
913
914 Ok(())
915 }
916
917 fn release_subscriptions(&mut self) {
918 self.releases += 1;
919 }
920 }
921
922 static NO_PANIC: AtomicBool = AtomicBool::new(false);
923 static DO_PANIC: AtomicBool = AtomicBool::new(true);
924
925 #[rstest]
926 fn test_component_borrow_tracking_prevents_double_borrow() {
927 clear_component_registry();
928
929 let id = Ustr::from("test-component-1");
930 let component = TestComponent::new("test-component-1", &NO_PANIC);
931 let component_id = component.id.inner();
932
933 let component_ref = Rc::new(UnsafeCell::new(component));
934 with_component_registry(|registry| registry.insert(component_id, component_ref));
935
936 start_component(&id).unwrap();
938 assert_eq!(component_state(&id).unwrap(), ComponentState::Running);
939
940 stop_component(&id).unwrap();
942 assert_eq!(component_state(&id).unwrap(), ComponentState::Stopped);
943 }
944
945 #[rstest]
946 fn test_component_borrow_released_after_lifecycle_call() {
947 clear_component_registry();
948
949 let id = Ustr::from("test-component-2");
950 let component = TestComponent::new("test-component-2", &NO_PANIC);
951 let component_id = component.id.inner();
952
953 let component_ref = Rc::new(UnsafeCell::new(component));
954 with_component_registry(|registry| registry.insert(component_id, component_ref));
955
956 let _ = start_component(&id);
958
959 assert!(!with_component_registry(
961 |registry| registry.is_borrowed(&id)
962 ));
963 }
964
965 #[rstest]
966 fn test_component_borrow_released_on_panic() {
967 clear_component_registry();
968
969 let id = Ustr::from("test-component-panic");
970 let component = TestComponent::new("test-component-panic", &DO_PANIC);
971 let component_id = component.id.inner();
972
973 let component_ref = Rc::new(UnsafeCell::new(component));
974 with_component_registry(|registry| registry.insert(component_id, component_ref));
975
976 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
978 let _ = start_component(&id);
979 }));
980 assert!(result.is_err(), "Expected panic from on_start");
981
982 assert!(
984 !with_component_registry(|registry| registry.is_borrowed(&id)),
985 "Borrow was not released after panic"
986 );
987 }
988
989 #[rstest]
990 #[case(ComponentState::PreInitialized, ComponentTrigger::Start)]
991 #[case(ComponentState::Ready, ComponentTrigger::Resume)]
992 #[case(ComponentState::Running, ComponentTrigger::Start)]
993 #[case(ComponentState::Stopped, ComponentTrigger::Stop)]
994 #[case(ComponentState::Disposed, ComponentTrigger::Dispose)]
995 #[case(ComponentState::Faulted, ComponentTrigger::Fault)]
996 fn test_transition_rejects_invalid_trigger(
997 #[case] state: ComponentState,
998 #[case] trigger: ComponentTrigger,
999 ) {
1000 let mut current = state;
1001
1002 let error = current.transition(&trigger).unwrap_err();
1003
1004 assert_eq!(
1005 error.to_string(),
1006 format!("Invalid state trigger {state} -> {trigger}")
1007 );
1008 assert_eq!(current, state, "Rejected trigger must not mutate the state");
1009 }
1010
1011 #[rstest]
1013 #[case(
1014 ComponentState::PreInitialized,
1015 ComponentTrigger::Initialize,
1016 ComponentState::Ready
1017 )]
1018 #[case(
1019 ComponentState::Ready,
1020 ComponentTrigger::Reset,
1021 ComponentState::Resetting
1022 )]
1023 #[case(
1024 ComponentState::Ready,
1025 ComponentTrigger::Start,
1026 ComponentState::Starting
1027 )]
1028 #[case(
1029 ComponentState::Ready,
1030 ComponentTrigger::Dispose,
1031 ComponentState::Disposing
1032 )]
1033 #[case(
1034 ComponentState::Resetting,
1035 ComponentTrigger::ResetCompleted,
1036 ComponentState::Ready
1037 )]
1038 #[case(
1039 ComponentState::Starting,
1040 ComponentTrigger::StartCompleted,
1041 ComponentState::Running
1042 )]
1043 #[case(
1044 ComponentState::Starting,
1045 ComponentTrigger::Stop,
1046 ComponentState::Stopping
1047 )]
1048 #[case(
1049 ComponentState::Starting,
1050 ComponentTrigger::Fault,
1051 ComponentState::Faulting
1052 )]
1053 #[case(
1054 ComponentState::Running,
1055 ComponentTrigger::Stop,
1056 ComponentState::Stopping
1057 )]
1058 #[case(
1059 ComponentState::Running,
1060 ComponentTrigger::Degrade,
1061 ComponentState::Degrading
1062 )]
1063 #[case(
1064 ComponentState::Running,
1065 ComponentTrigger::Fault,
1066 ComponentState::Faulting
1067 )]
1068 #[case(
1069 ComponentState::Resuming,
1070 ComponentTrigger::Stop,
1071 ComponentState::Stopping
1072 )]
1073 #[case(
1074 ComponentState::Resuming,
1075 ComponentTrigger::ResumeCompleted,
1076 ComponentState::Running
1077 )]
1078 #[case(
1079 ComponentState::Resuming,
1080 ComponentTrigger::Fault,
1081 ComponentState::Faulting
1082 )]
1083 #[case(
1084 ComponentState::Stopping,
1085 ComponentTrigger::StopCompleted,
1086 ComponentState::Stopped
1087 )]
1088 #[case(
1089 ComponentState::Stopping,
1090 ComponentTrigger::Dispose,
1091 ComponentState::Disposing
1092 )]
1093 #[case(
1094 ComponentState::Stopping,
1095 ComponentTrigger::Fault,
1096 ComponentState::Faulting
1097 )]
1098 #[case(
1099 ComponentState::Stopped,
1100 ComponentTrigger::Reset,
1101 ComponentState::Resetting
1102 )]
1103 #[case(
1104 ComponentState::Stopped,
1105 ComponentTrigger::Resume,
1106 ComponentState::Resuming
1107 )]
1108 #[case(
1109 ComponentState::Stopped,
1110 ComponentTrigger::Dispose,
1111 ComponentState::Disposing
1112 )]
1113 #[case(
1114 ComponentState::Stopped,
1115 ComponentTrigger::Fault,
1116 ComponentState::Faulting
1117 )]
1118 #[case(
1119 ComponentState::Degrading,
1120 ComponentTrigger::DegradeCompleted,
1121 ComponentState::Degraded
1122 )]
1123 #[case(
1124 ComponentState::Degraded,
1125 ComponentTrigger::Resume,
1126 ComponentState::Resuming
1127 )]
1128 #[case(
1129 ComponentState::Degraded,
1130 ComponentTrigger::Stop,
1131 ComponentState::Stopping
1132 )]
1133 #[case(
1134 ComponentState::Degraded,
1135 ComponentTrigger::Fault,
1136 ComponentState::Faulting
1137 )]
1138 #[case(
1139 ComponentState::Disposing,
1140 ComponentTrigger::DisposeCompleted,
1141 ComponentState::Disposed
1142 )]
1143 #[case(
1144 ComponentState::Disposing,
1145 ComponentTrigger::Fault,
1146 ComponentState::Faulting
1147 )]
1148 #[case(
1149 ComponentState::Faulting,
1150 ComponentTrigger::Dispose,
1151 ComponentState::Disposing
1152 )]
1153 #[case(
1154 ComponentState::Faulting,
1155 ComponentTrigger::FaultCompleted,
1156 ComponentState::Faulted
1157 )]
1158 fn test_transition_accepts_valid_trigger(
1159 #[case] state: ComponentState,
1160 #[case] trigger: ComponentTrigger,
1161 #[case] expected: ComponentState,
1162 ) {
1163 let mut current = state;
1164
1165 assert_eq!(current.transition(&trigger).unwrap(), expected);
1166 }
1167
1168 #[rstest]
1169 fn test_state_predicates_match_the_current_state() {
1170 let mut component = TestComponent::new("predicates", &NO_PANIC);
1171
1172 assert!(component.is_ready());
1173 assert!(component.not_running());
1174
1175 component.start().unwrap();
1176 assert!(component.is_running());
1177 assert!(!component.not_running());
1178 assert!(!component.is_ready());
1179
1180 component.stop().unwrap();
1181 assert!(component.is_stopped());
1182 assert!(!component.is_running());
1183
1184 component.resume().unwrap();
1185 component.degrade().unwrap();
1186 assert!(component.is_degraded());
1187 assert!(!component.is_stopped());
1188
1189 component.fault().unwrap();
1190 assert!(component.is_faulted());
1191 assert!(!component.is_degraded());
1192 assert!(!component.is_disposed());
1193 }
1194
1195 #[rstest]
1196 fn test_is_disposed_only_after_disposal() {
1197 let mut component = TestComponent::new("disposable", &NO_PANIC);
1198
1199 assert!(!component.is_disposed());
1200
1201 component.dispose().unwrap();
1202
1203 assert!(component.is_disposed());
1204 assert!(!component.is_ready());
1205 assert!(!component.is_faulted());
1206 }
1207
1208 #[rstest]
1209 fn test_degrade_halts_transition_when_on_degrade_fails() {
1210 let mut component = TestComponent::new("failing-degrade", &NO_PANIC);
1211 component.start().unwrap();
1212 component.hooks_fail = true;
1213
1214 let error = component.degrade().unwrap_err();
1215
1216 assert_eq!(error.to_string(), "on_degrade failed");
1217 assert_eq!(component.state(), ComponentState::Degrading);
1218 }
1219
1220 #[rstest]
1221 fn test_initialize_advances_from_pre_initialized() {
1222 let mut component = TestComponent::new("initializing", &NO_PANIC);
1223 component.state = ComponentState::PreInitialized;
1224
1225 component.initialize().unwrap();
1226
1227 assert_eq!(component.state(), ComponentState::Ready);
1228 }
1229
1230 #[rstest]
1231 fn test_start_halts_transition_when_on_start_fails() {
1232 let mut component = TestComponent::new("failing-start", &NO_PANIC);
1233 component.hooks_fail = true;
1234
1235 let error = component.start().unwrap_err();
1236
1237 assert_eq!(error.to_string(), "on_start failed");
1238 assert_eq!(component.state(), ComponentState::Starting);
1239 }
1240
1241 #[rstest]
1242 fn test_stop_halts_transition_when_on_stop_fails() {
1243 let mut component = TestComponent::new("failing-stop", &NO_PANIC);
1244 component.start().unwrap();
1245 component.hooks_fail = true;
1246
1247 let error = component.stop().unwrap_err();
1248
1249 assert_eq!(error.to_string(), "on_stop failed");
1250 assert_eq!(component.state(), ComponentState::Stopping);
1251 }
1252
1253 #[rstest]
1254 fn test_resume_halts_transition_when_on_resume_fails() {
1255 let mut component = TestComponent::new("failing-resume", &NO_PANIC);
1256 component.start().unwrap();
1257 component.stop().unwrap();
1258 component.hooks_fail = true;
1259
1260 let error = component.resume().unwrap_err();
1261
1262 assert_eq!(error.to_string(), "on_resume failed");
1263 assert_eq!(component.state(), ComponentState::Resuming);
1264 }
1265
1266 #[rstest]
1267 fn test_reset_retains_subscriptions_when_on_reset_fails() {
1268 let mut component = TestComponent::new("failing-reset", &NO_PANIC);
1269 component.hooks_fail = true;
1270
1271 let error = component.reset().unwrap_err();
1272
1273 assert_eq!(error.to_string(), "on_reset failed");
1274 assert_eq!(component.state(), ComponentState::Resetting);
1275 assert_eq!(component.releases, 0);
1276 }
1277
1278 #[rstest]
1279 fn test_reset_releases_subscriptions_on_success() {
1280 let mut component = TestComponent::new("resetting", &NO_PANIC);
1281
1282 component.reset().unwrap();
1283
1284 assert_eq!(component.state(), ComponentState::Ready);
1285 assert_eq!(component.releases, 1);
1286 }
1287
1288 #[rstest]
1289 fn test_dispose_faults_and_retains_subscriptions_when_on_dispose_fails() {
1290 let mut component = TestComponent::new("failing-dispose", &NO_PANIC);
1291 component.hooks_fail = true;
1292
1293 let error = component.dispose().unwrap_err();
1294
1295 assert_eq!(error.to_string(), "on_dispose failed");
1296 assert_eq!(component.state(), ComponentState::Faulted);
1297 assert_eq!(component.releases, 0);
1298 }
1299
1300 #[rstest]
1301 fn test_dispose_releases_subscriptions_on_success() {
1302 let mut component = TestComponent::new("disposing", &NO_PANIC);
1303
1304 component.dispose().unwrap();
1305
1306 assert_eq!(component.state(), ComponentState::Disposed);
1307 assert_eq!(component.releases, 1);
1308 }
1309
1310 #[rstest]
1311 fn test_fault_releases_subscriptions_even_when_on_fault_fails() {
1312 let mut component = TestComponent::new("failing-fault", &NO_PANIC);
1313 component.start().unwrap();
1314 component.hooks_fail = true;
1315
1316 let error = component.fault().unwrap_err();
1317
1318 assert_eq!(error.to_string(), "on_fault failed");
1319 assert_eq!(component.state(), ComponentState::Faulting);
1320 assert_eq!(component.releases, 1);
1321 }
1322
1323 #[rstest]
1324 fn test_fault_releases_subscriptions_on_success() {
1325 let mut component = TestComponent::new("faulting", &NO_PANIC);
1326 component.start().unwrap();
1327
1328 component.fault().unwrap();
1329
1330 assert_eq!(component.state(), ComponentState::Faulted);
1331 assert_eq!(component.releases, 1);
1332 }
1333
1334 #[rstest]
1335 fn test_registry_entry_points_reject_unknown_component() {
1336 clear_component_registry();
1337
1338 let id = Ustr::from("absent-component");
1339
1340 for (name, entry_point) in registry_entry_points() {
1341 let error = entry_point(&id).unwrap_err();
1342 assert_eq!(
1343 error.to_string(),
1344 "Component 'absent-component' not found in global registry",
1345 "unexpected error from {name}"
1346 );
1347 }
1348
1349 let error = component_state(&id).unwrap_err();
1350 assert_eq!(
1351 error.to_string(),
1352 "Component 'absent-component' not found in global registry"
1353 );
1354 }
1355
1356 #[rstest]
1357 fn test_registry_entry_points_reject_borrowed_component() {
1358 clear_component_registry();
1359
1360 let id = TestComponent::new("borrowed-component", &NO_PANIC).register_in_global_registry();
1361 assert!(with_component_registry(|registry| registry.try_borrow(id)));
1362
1363 for (name, entry_point) in registry_entry_points() {
1364 let error = entry_point(&id).unwrap_err();
1365 assert!(
1366 error
1367 .to_string()
1368 .starts_with("Component 'borrowed-component' is already mutably borrowed."),
1369 "unexpected error from {name}: {error}"
1370 );
1371 }
1372
1373 let error = component_state(&id).unwrap_err();
1374 assert!(
1375 error
1376 .to_string()
1377 .starts_with("Component 'borrowed-component' is already mutably borrowed.")
1378 );
1379
1380 assert!(with_component_registry(|registry| registry.is_borrowed(&id)));
1382 }
1383
1384 type RegistryEntryPoint = (&'static str, fn(&Ustr) -> anyhow::Result<()>);
1385
1386 fn registry_entry_points() -> [RegistryEntryPoint; 5] {
1388 [
1389 ("start", start_component),
1390 ("stop", stop_component),
1391 ("reset", reset_component),
1392 ("dispose", dispose_component),
1393 ("release", release_component_subscriptions),
1394 ]
1395 }
1396
1397 #[rstest]
1398 fn test_registry_entry_points_drive_the_full_lifecycle() {
1399 clear_component_registry();
1400
1401 let id = TestComponent::new("lifecycle-component", &NO_PANIC).register_in_global_registry();
1402
1403 assert_eq!(component_state(&id).unwrap(), ComponentState::Ready);
1404
1405 start_component(&id).unwrap();
1406 assert_eq!(component_state(&id).unwrap(), ComponentState::Running);
1407
1408 stop_component(&id).unwrap();
1409 assert_eq!(component_state(&id).unwrap(), ComponentState::Stopped);
1410
1411 reset_component(&id).unwrap();
1412 assert_eq!(component_state(&id).unwrap(), ComponentState::Ready);
1413
1414 dispose_component(&id).unwrap();
1415 assert_eq!(component_state(&id).unwrap(), ComponentState::Disposed);
1416
1417 assert!(!with_component_registry(
1418 |registry| registry.is_borrowed(&id)
1419 ));
1420 }
1421
1422 #[rstest]
1423 fn test_release_component_subscriptions_invokes_the_component_hook() {
1424 clear_component_registry();
1425
1426 let component = TestComponent::new("releasing-component", &NO_PANIC);
1427 let id = component.id.inner();
1428 let component_ref = Rc::new(UnsafeCell::new(component));
1429 let observed = component_ref.clone();
1430 with_component_registry(|registry| {
1431 registry.insert(id, component_ref as Rc<UnsafeCell<dyn Component>>);
1432 });
1433
1434 release_component_subscriptions(&id).unwrap();
1435 release_component_subscriptions(&id).unwrap();
1436
1437 assert_eq!(unsafe { (*observed.get()).releases }, 2);
1439 }
1440
1441 #[rstest]
1442 fn test_deregister_component_removes_only_the_named_component() {
1443 clear_component_registry();
1444
1445 let kept = TestComponent::new("kept-component", &NO_PANIC).register_in_global_registry();
1446 let removed =
1447 TestComponent::new("removed-component", &NO_PANIC).register_in_global_registry();
1448
1449 deregister_component(&removed);
1450
1451 assert!(get_component(&removed).is_none());
1452 assert!(get_component(&kept).is_some());
1453 assert_eq!(component_state(&kept).unwrap(), ComponentState::Ready);
1454 }
1455
1456 #[rstest]
1457 fn test_component_registry_debug_reports_components_and_borrows() {
1458 clear_component_registry();
1459
1460 let id = TestComponent::new("debug-component", &NO_PANIC).register_in_global_registry();
1461 assert!(with_component_registry(|registry| registry.try_borrow(id)));
1462
1463 let debug = with_component_registry(|registry| format!("{registry:?}"));
1464
1465 assert!(debug.contains("debug-component"), "{debug}");
1466 assert!(debug.contains("active_borrows: 1"), "{debug}");
1467 }
1468
1469 #[rstest]
1470 fn test_registry_remove_returns_the_registered_component() {
1471 clear_component_registry();
1472
1473 let id = TestComponent::new("removable", &NO_PANIC).register_in_global_registry();
1474
1475 assert!(with_component_registry(|registry| registry.remove(&id)).is_some());
1476 assert!(with_component_registry(|registry| registry.remove(&id)).is_none());
1477 }
1478
1479 #[rstest]
1480 #[case("start", ComponentState::Ready, ComponentState::Starting, 0)]
1481 #[case("stop", ComponentState::Running, ComponentState::Stopping, 0)]
1482 #[case("resume", ComponentState::Stopped, ComponentState::Resuming, 0)]
1483 #[case("degrade", ComponentState::Running, ComponentState::Degrading, 0)]
1484 #[case("fault", ComponentState::Running, ComponentState::Faulting, 1)]
1485 #[case("reset", ComponentState::Ready, ComponentState::Resetting, 0)]
1486 #[case("dispose", ComponentState::Ready, ComponentState::Faulted, 0)]
1487 fn test_lifecycle_error_returns_without_logging(
1488 #[case] method: &str,
1489 #[case] initial: ComponentState,
1490 #[case] expected: ComponentState,
1491 #[case] releases: usize,
1492 ) {
1493 let _guard = init_logging(
1494 TraderId::from("TRADER-001"),
1495 UUID4::new(),
1496 Default::default(),
1497 Default::default(),
1498 )
1499 .unwrap();
1500 let mut component = TestComponent::new("failing-hook", &NO_PANIC);
1501 component.state = initial;
1502 component.hooks_fail = true;
1503 arm_shutdown_on_error(true);
1504
1505 let result = match method {
1506 "start" => component.start(),
1507 "stop" => component.stop(),
1508 "resume" => component.resume(),
1509 "degrade" => component.degrade(),
1510 "fault" => component.fault(),
1511 "reset" => component.reset(),
1512 "dispose" => component.dispose(),
1513 _ => unreachable!(),
1514 };
1515
1516 let trigger = take_shutdown_on_error_trigger();
1517 disarm_shutdown_on_error();
1518
1519 assert_eq!(
1520 result.unwrap_err().to_string(),
1521 format!("on_{method} failed")
1522 );
1523 assert_eq!(component.state(), expected);
1524 assert_eq!(component.releases, releases);
1525 assert_eq!(trigger, None);
1526 }
1527}