Skip to main content

nautilus_common/
component.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Component system for managing stateful system entities.
17//!
18//! This module provides the component framework for managing the lifecycle and state
19//! of system entities. Components have defined states (pre-initialized, ready, running,
20//! stopped, etc.) and provide a consistent interface for state management and transitions.
21
22#![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 ustr::Ustr;
33
34use crate::{
35    actor::{Actor, registry::with_actor_registry},
36    cache::Cache,
37    clock::Clock,
38    enums::{ComponentState, ComponentTrigger},
39};
40
41/// Components have state and lifecycle management capabilities.
42pub trait Component {
43    /// Returns the unique identifier for this component.
44    fn component_id(&self) -> ComponentId;
45
46    /// Returns the current state of the component.
47    fn state(&self) -> ComponentState;
48
49    /// Transition the component with the state trigger.
50    ///
51    /// # Errors
52    ///
53    /// Returns an error if the `trigger` is an invalid transition from the current state.
54    fn transition_state(&mut self, trigger: ComponentTrigger) -> anyhow::Result<()>;
55
56    /// Returns whether the component is ready.
57    fn is_ready(&self) -> bool {
58        self.state() == ComponentState::Ready
59    }
60
61    /// Returns whether the component is *not* running.
62    fn not_running(&self) -> bool {
63        !self.is_running()
64    }
65
66    /// Returns whether the component is running.
67    fn is_running(&self) -> bool {
68        self.state() == ComponentState::Running
69    }
70
71    /// Returns whether the component is stopped.
72    fn is_stopped(&self) -> bool {
73        self.state() == ComponentState::Stopped
74    }
75
76    /// Returns whether the component has been degraded.
77    fn is_degraded(&self) -> bool {
78        self.state() == ComponentState::Degraded
79    }
80
81    /// Returns whether the component has been faulted.
82    fn is_faulted(&self) -> bool {
83        self.state() == ComponentState::Faulted
84    }
85
86    /// Returns whether the component has been disposed.
87    fn is_disposed(&self) -> bool {
88        self.state() == ComponentState::Disposed
89    }
90
91    /// Registers the component with a system.
92    ///
93    /// # Errors
94    ///
95    /// Returns an error if the component fails to register.
96    fn register(
97        &mut self,
98        trader_id: TraderId,
99        clock: Rc<RefCell<dyn Clock>>,
100        cache: Rc<RefCell<Cache>>,
101    ) -> anyhow::Result<()>;
102
103    /// Initializes the component.
104    ///
105    /// # Errors
106    ///
107    /// Returns an error if the initialization state transition fails.
108    fn initialize(&mut self) -> anyhow::Result<()> {
109        self.transition_state(ComponentTrigger::Initialize)
110    }
111
112    /// Starts the component.
113    ///
114    /// # Errors
115    ///
116    /// Returns an error if the component fails to start.
117    fn start(&mut self) -> anyhow::Result<()> {
118        self.transition_state(ComponentTrigger::Start)?; // -> Starting
119
120        if let Err(e) = self.on_start() {
121            log_error(self.component_id(), &e);
122            return Err(e); // Halt state transition
123        }
124
125        self.transition_state(ComponentTrigger::StartCompleted)?;
126
127        Ok(())
128    }
129
130    /// Stops the component.
131    ///
132    /// # Errors
133    ///
134    /// Returns an error if the component fails to stop.
135    fn stop(&mut self) -> anyhow::Result<()> {
136        self.transition_state(ComponentTrigger::Stop)?; // -> Stopping
137
138        if let Err(e) = self.on_stop() {
139            log_error(self.component_id(), &e);
140            return Err(e); // Halt state transition
141        }
142
143        self.transition_state(ComponentTrigger::StopCompleted)?;
144
145        Ok(())
146    }
147
148    /// Resumes the component.
149    ///
150    /// # Errors
151    ///
152    /// Returns an error if the component fails to resume.
153    fn resume(&mut self) -> anyhow::Result<()> {
154        self.transition_state(ComponentTrigger::Resume)?; // -> Resuming
155
156        if let Err(e) = self.on_resume() {
157            log_error(self.component_id(), &e);
158            return Err(e); // Halt state transition
159        }
160
161        self.transition_state(ComponentTrigger::ResumeCompleted)?;
162
163        Ok(())
164    }
165
166    /// Degrades the component.
167    ///
168    /// # Errors
169    ///
170    /// Returns an error if the component fails to degrade.
171    fn degrade(&mut self) -> anyhow::Result<()> {
172        self.transition_state(ComponentTrigger::Degrade)?; // -> Degrading
173
174        if let Err(e) = self.on_degrade() {
175            log_error(self.component_id(), &e);
176            return Err(e); // Halt state transition
177        }
178
179        self.transition_state(ComponentTrigger::DegradeCompleted)?;
180
181        Ok(())
182    }
183
184    /// Faults the component.
185    ///
186    /// # Errors
187    ///
188    /// Returns an error if the component fails to fault.
189    ///
190    /// # Notes
191    ///
192    /// Subscriptions are released whether or not `on_fault` succeeds, so a faulted component never
193    /// keeps message bus handlers installed. Retirement relies on this: it deregisters a `Faulted`
194    /// component without disposing it, which would otherwise leave those handlers behind.
195    fn fault(&mut self) -> anyhow::Result<()> {
196        self.transition_state(ComponentTrigger::Fault)?; // -> Faulting
197
198        let result = self.on_fault();
199        self.release_subscriptions();
200
201        if let Err(e) = result {
202            log_error(self.component_id(), &e);
203            return Err(e); // Halt state transition
204        }
205
206        self.transition_state(ComponentTrigger::FaultCompleted)?;
207
208        Ok(())
209    }
210
211    /// Resets the component to its initial state.
212    ///
213    /// # Errors
214    ///
215    /// Returns an error if the component fails to reset.
216    fn reset(&mut self) -> anyhow::Result<()> {
217        self.transition_state(ComponentTrigger::Reset)?; // -> Resetting
218
219        if let Err(e) = self.on_reset() {
220            log_error(self.component_id(), &e);
221            return Err(e); // Halt state transition
222        }
223
224        self.transition_state(ComponentTrigger::ResetCompleted)?;
225
226        Ok(())
227    }
228
229    /// Disposes of the component, releasing any resources.
230    ///
231    /// # Errors
232    ///
233    /// Returns an error if the component fails to dispose.
234    ///
235    /// # Notes
236    ///
237    /// A failing `on_dispose` releases subscriptions and moves the component to `Faulted`, then
238    /// returns the error. The trader's registry entries and retained Python wrapper, if any, are
239    /// deliberately kept, so the component stays inspectable, while `Faulted` leaves it retirable.
240    /// Subscriptions are released on this path too, because a handler left installed would resolve
241    /// a component the trader can now deregister.
242    ///
243    /// `on_fault` does not run, since invoking a second user hook immediately after `on_dispose`
244    /// failed can fail again.
245    fn dispose(&mut self) -> anyhow::Result<()> {
246        self.transition_state(ComponentTrigger::Dispose)?; // -> Disposing
247
248        let result = self.on_dispose();
249        self.release_subscriptions();
250
251        if let Err(e) = result {
252            log_error(self.component_id(), &e);
253
254            self.transition_state(ComponentTrigger::Fault)?; // -> Faulting
255            self.transition_state(ComponentTrigger::FaultCompleted)?; // -> Faulted
256
257            return Err(e);
258        }
259
260        self.transition_state(ComponentTrigger::DisposeCompleted)?;
261
262        Ok(())
263    }
264
265    /// Releases the message bus registrations this component installed.
266    ///
267    /// Runs on disposal after `on_dispose` and on faulting after `on_fault`, so a component the
268    /// trader then deregisters leaves behind no handler which would resolve an actor that is no
269    /// longer registered. An override must suit both routes rather than assume disposal, and should
270    /// be idempotent: a component that faults from inside its own `on_dispose` releases twice.
271    fn release_subscriptions(&mut self) {}
272
273    /// Actions to be performed on start.
274    ///
275    /// # Errors
276    ///
277    /// Returns an error if starting the actor fails.
278    fn on_start(&mut self) -> anyhow::Result<()> {
279        log::warn!(
280            "The `on_start` handler was called when not overridden, \
281            it's expected that any actions required when stopping the component \
282            occur here, such as unsubscribing from data",
283        );
284        Ok(())
285    }
286
287    /// Actions to be performed on stop.
288    ///
289    /// # Errors
290    ///
291    /// Returns an error if stopping the actor fails.
292    fn on_stop(&mut self) -> anyhow::Result<()> {
293        log::warn!(
294            "The `on_stop` handler was called when not overridden, \
295            it's expected that any actions required when stopping the component \
296            occur here, such as unsubscribing from data",
297        );
298        Ok(())
299    }
300
301    /// Actions to be performed on resume.
302    ///
303    /// # Errors
304    ///
305    /// Returns an error if resuming the actor fails.
306    fn on_resume(&mut self) -> anyhow::Result<()> {
307        log::warn!(
308            "The `on_resume` handler was called when not overridden, \
309            it's expected that any actions required when resuming the component \
310            following a stop occur here"
311        );
312        Ok(())
313    }
314
315    /// Actions to be performed on reset.
316    ///
317    /// # Errors
318    ///
319    /// Returns an error if resetting the actor fails.
320    fn on_reset(&mut self) -> anyhow::Result<()> {
321        log::warn!(
322            "The `on_reset` handler was called when not overridden, \
323            it's expected that any actions required when resetting the component \
324            occur here, such as resetting indicators and other state"
325        );
326        Ok(())
327    }
328
329    /// Actions to be performed on dispose.
330    ///
331    /// # Errors
332    ///
333    /// Returns an error if disposing the actor fails.
334    fn on_dispose(&mut self) -> anyhow::Result<()> {
335        Ok(())
336    }
337
338    /// Actions to be performed on degrade.
339    ///
340    /// # Errors
341    ///
342    /// Returns an error if degrading the actor fails.
343    fn on_degrade(&mut self) -> anyhow::Result<()> {
344        Ok(())
345    }
346
347    /// Actions to be performed on fault.
348    ///
349    /// # Errors
350    ///
351    /// Returns an error if faulting the actor fails.
352    fn on_fault(&mut self) -> anyhow::Result<()> {
353        Ok(())
354    }
355}
356
357fn log_error(component: ComponentId, e: &anyhow::Error) {
358    log::error!(component = component.as_str(); "{e}");
359}
360
361#[rustfmt::skip]
362impl ComponentState {
363    /// Transition the state machine with the component `trigger`.
364    ///
365    /// # Errors
366    ///
367    /// Returns an error if `trigger` is invalid for the current state.
368    pub fn transition(&mut self, trigger: &ComponentTrigger) -> anyhow::Result<Self> {
369        let new_state = match (&self, trigger) {
370            (Self::PreInitialized, ComponentTrigger::Initialize) => Self::Ready,
371            (Self::Ready, ComponentTrigger::Reset) => Self::Resetting,
372            (Self::Ready, ComponentTrigger::Start) => Self::Starting,
373            (Self::Ready, ComponentTrigger::Dispose) => Self::Disposing,
374            (Self::Resetting, ComponentTrigger::ResetCompleted) => Self::Ready,
375            (Self::Starting, ComponentTrigger::StartCompleted) => Self::Running,
376            (Self::Starting, ComponentTrigger::Stop) => Self::Stopping,
377            (Self::Starting, ComponentTrigger::Fault) => Self::Faulting,
378            (Self::Running, ComponentTrigger::Stop) => Self::Stopping,
379            (Self::Running, ComponentTrigger::Degrade) => Self::Degrading,
380            (Self::Running, ComponentTrigger::Fault) => Self::Faulting,
381            (Self::Resuming, ComponentTrigger::Stop) => Self::Stopping,
382            (Self::Resuming, ComponentTrigger::ResumeCompleted) => Self::Running,
383            (Self::Resuming, ComponentTrigger::Fault) => Self::Faulting,
384            (Self::Stopping, ComponentTrigger::StopCompleted) => Self::Stopped,
385            (Self::Stopping, ComponentTrigger::Fault) => Self::Faulting,
386            (Self::Stopped, ComponentTrigger::Reset) => Self::Resetting,
387            (Self::Stopped, ComponentTrigger::Resume) => Self::Resuming,
388            (Self::Stopped, ComponentTrigger::Dispose) => Self::Disposing,
389            (Self::Stopped, ComponentTrigger::Fault) => Self::Faulting,
390            (Self::Degrading, ComponentTrigger::DegradeCompleted) => Self::Degraded,
391            (Self::Degraded, ComponentTrigger::Resume) => Self::Resuming,
392            (Self::Degraded, ComponentTrigger::Stop) => Self::Stopping,
393            (Self::Degraded, ComponentTrigger::Fault) => Self::Faulting,
394            (Self::Disposing, ComponentTrigger::DisposeCompleted) => Self::Disposed,
395            (Self::Disposing, ComponentTrigger::Fault) => Self::Faulting,
396            (Self::Faulting, ComponentTrigger::FaultCompleted) => Self::Faulted,
397            _ => anyhow::bail!("Invalid state trigger {self} -> {trigger}"),
398        };
399        Ok(new_state)
400    }
401}
402
403thread_local! {
404    static COMPONENT_REGISTRY: ComponentRegistry = ComponentRegistry::new();
405}
406
407/// Registry for storing components with runtime borrow tracking.
408///
409/// The registry tracks which components are currently mutably borrowed to prevent
410/// multiple simultaneous mutable borrows (which would be undefined behavior).
411pub struct ComponentRegistry {
412    components: RefCell<AHashMap<Ustr, Rc<UnsafeCell<dyn Component>>>>,
413    borrows: RefCell<AHashSet<Ustr>>,
414}
415
416impl Debug for ComponentRegistry {
417    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
418        let components_ref = self.components.borrow();
419        let keys: Vec<&Ustr> = components_ref.keys().collect();
420        f.debug_struct(stringify!(ComponentRegistry))
421            .field("components", &keys)
422            .field("active_borrows", &self.borrows.borrow().len())
423            .finish()
424    }
425}
426
427impl Default for ComponentRegistry {
428    fn default() -> Self {
429        Self::new()
430    }
431}
432
433impl ComponentRegistry {
434    pub fn new() -> Self {
435        Self {
436            components: RefCell::new(AHashMap::new()),
437            borrows: RefCell::new(AHashSet::new()),
438        }
439    }
440
441    pub fn insert(&self, id: Ustr, component: Rc<UnsafeCell<dyn Component>>) {
442        self.components.borrow_mut().insert(id, component);
443    }
444
445    pub fn get(&self, id: &Ustr) -> Option<Rc<UnsafeCell<dyn Component>>> {
446        self.components.borrow().get(id).cloned()
447    }
448
449    /// Removes the component with `id`, returning it when it was registered.
450    pub fn remove(&self, id: &Ustr) -> Option<Rc<UnsafeCell<dyn Component>>> {
451        self.components.borrow_mut().remove(id)
452    }
453
454    /// Checks if a component is currently borrowed.
455    pub fn is_borrowed(&self, id: &Ustr) -> bool {
456        self.borrows.borrow().contains(id)
457    }
458
459    /// Marks a component as borrowed. Returns false if already borrowed.
460    fn try_borrow(&self, id: Ustr) -> bool {
461        let mut borrows = self.borrows.borrow_mut();
462        if borrows.contains(&id) {
463            false
464        } else {
465            borrows.insert(id);
466            true
467        }
468    }
469
470    /// Releases a borrow on a component.
471    fn release_borrow(&self, id: &Ustr) {
472        self.borrows.borrow_mut().remove(id);
473    }
474}
475
476/// Guard that releases a component borrow when dropped.
477///
478/// This ensures borrows are released even if the code panics during
479/// a lifecycle method call.
480struct BorrowGuard {
481    id: Ustr,
482}
483
484impl BorrowGuard {
485    fn new(id: Ustr) -> Self {
486        Self { id }
487    }
488}
489
490impl Drop for BorrowGuard {
491    fn drop(&mut self) {
492        with_component_registry(|registry| registry.release_borrow(&self.id));
493    }
494}
495
496pub fn with_component_registry<R>(f: impl FnOnce(&ComponentRegistry) -> R) -> R {
497    COMPONENT_REGISTRY.with(f)
498}
499
500/// Registers a component.
501pub fn register_component<T>(component: T) -> Rc<UnsafeCell<T>>
502where
503    T: Component + 'static,
504{
505    let component_id = component.component_id().inner();
506    let component_ref = Rc::new(UnsafeCell::new(component));
507
508    // Register in component registry
509    let component_trait_ref: Rc<UnsafeCell<dyn Component>> = component_ref.clone();
510    with_component_registry(|registry| registry.insert(component_id, component_trait_ref));
511
512    component_ref
513}
514
515/// Registers a component that also implements Actor.
516pub fn register_component_actor<T>(component: T) -> Rc<UnsafeCell<T>>
517where
518    T: Component + Actor + 'static,
519{
520    let component_id = component.component_id().inner();
521    let actor_id = component.id();
522    let component_ref = Rc::new(UnsafeCell::new(component));
523
524    // Register in component registry
525    let component_trait_ref: Rc<UnsafeCell<dyn Component>> = component_ref.clone();
526    with_component_registry(|registry| registry.insert(component_id, component_trait_ref));
527
528    // Register in actor registry
529    let actor_trait_ref: Rc<UnsafeCell<dyn Actor>> = component_ref.clone();
530    with_actor_registry(|registry| registry.insert(actor_id, actor_trait_ref));
531
532    component_ref
533}
534
535/// Safely calls `start()` on a component in the global registry.
536///
537/// # Errors
538///
539/// - Returns an error if the component is not found.
540/// - Returns an error if the component is already borrowed.
541/// - Returns an error if `start()` fails.
542pub fn start_component(id: &Ustr) -> anyhow::Result<()> {
543    let component_ref = with_component_registry(|registry| {
544        let component_ref = registry
545            .get(id)
546            .ok_or_else(|| anyhow::anyhow!("Component '{id}' not found in global registry"))?;
547
548        if !registry.try_borrow(*id) {
549            anyhow::bail!(
550                "Component '{id}' is already mutably borrowed. \
551                 This would create aliasing mutable references (undefined behavior)."
552            );
553        }
554
555        Ok::<_, anyhow::Error>(component_ref)
556    })?;
557
558    let _guard = BorrowGuard::new(*id);
559
560    // SAFETY: Borrow tracking ensures exclusive access
561    unsafe {
562        let component = &mut *component_ref.get();
563        component.start()
564    }
565}
566
567/// Returns the state of a component in the global registry.
568///
569/// # Errors
570///
571/// - Returns an error if the component is not found.
572/// - Returns an error if the component is already borrowed.
573pub fn component_state(id: &Ustr) -> anyhow::Result<ComponentState> {
574    let component_ref = with_component_registry(|registry| {
575        let component_ref = registry
576            .get(id)
577            .ok_or_else(|| anyhow::anyhow!("Component '{id}' not found in global registry"))?;
578
579        if !registry.try_borrow(*id) {
580            anyhow::bail!(
581                "Component '{id}' is already mutably borrowed. \
582                 This would create aliasing mutable references (undefined behavior)."
583            );
584        }
585
586        Ok::<_, anyhow::Error>(component_ref)
587    })?;
588
589    let _guard = BorrowGuard::new(*id);
590
591    // SAFETY: Borrow tracking ensures there is no concurrent mutable lifecycle access.
592    unsafe {
593        let component = &*component_ref.get();
594        Ok(component.state())
595    }
596}
597
598/// Safely calls `stop()` on a component in the global registry.
599///
600/// # Errors
601///
602/// - Returns an error if the component is not found.
603/// - Returns an error if the component is already borrowed.
604/// - Returns an error if `stop()` fails.
605pub fn stop_component(id: &Ustr) -> anyhow::Result<()> {
606    let component_ref = with_component_registry(|registry| {
607        let component_ref = registry
608            .get(id)
609            .ok_or_else(|| anyhow::anyhow!("Component '{id}' not found in global registry"))?;
610
611        if !registry.try_borrow(*id) {
612            anyhow::bail!(
613                "Component '{id}' is already mutably borrowed. \
614                 This would create aliasing mutable references (undefined behavior)."
615            );
616        }
617
618        Ok::<_, anyhow::Error>(component_ref)
619    })?;
620
621    let _guard = BorrowGuard::new(*id);
622
623    // SAFETY: Borrow tracking ensures exclusive access
624    unsafe {
625        let component = &mut *component_ref.get();
626        component.stop()
627    }
628}
629
630/// Safely calls `reset()` on a component in the global registry.
631///
632/// # Errors
633///
634/// - Returns an error if the component is not found.
635/// - Returns an error if the component is already borrowed.
636/// - Returns an error if `reset()` fails.
637pub fn reset_component(id: &Ustr) -> anyhow::Result<()> {
638    let component_ref = with_component_registry(|registry| {
639        let component_ref = registry
640            .get(id)
641            .ok_or_else(|| anyhow::anyhow!("Component '{id}' not found in global registry"))?;
642
643        if !registry.try_borrow(*id) {
644            anyhow::bail!(
645                "Component '{id}' is already mutably borrowed. \
646                 This would create aliasing mutable references (undefined behavior)."
647            );
648        }
649
650        Ok::<_, anyhow::Error>(component_ref)
651    })?;
652
653    let _guard = BorrowGuard::new(*id);
654
655    // SAFETY: Borrow tracking ensures exclusive access
656    unsafe {
657        let component = &mut *component_ref.get();
658        component.reset()
659    }
660}
661
662/// Safely calls `dispose()` on a component in the global registry.
663///
664/// # Errors
665///
666/// - Returns an error if the component is not found.
667/// - Returns an error if the component is already borrowed.
668/// - Returns an error if `dispose()` fails.
669pub fn dispose_component(id: &Ustr) -> anyhow::Result<()> {
670    let component_ref = with_component_registry(|registry| {
671        let component_ref = registry
672            .get(id)
673            .ok_or_else(|| anyhow::anyhow!("Component '{id}' not found in global registry"))?;
674
675        if !registry.try_borrow(*id) {
676            anyhow::bail!(
677                "Component '{id}' is already mutably borrowed. \
678                 This would create aliasing mutable references (undefined behavior)."
679            );
680        }
681
682        Ok::<_, anyhow::Error>(component_ref)
683    })?;
684
685    let _guard = BorrowGuard::new(*id);
686
687    // SAFETY: Borrow tracking ensures exclusive access
688    unsafe {
689        let component = &mut *component_ref.get();
690        component.dispose()
691    }
692}
693
694/// Returns a component from the global registry by ID.
695pub fn get_component(id: &Ustr) -> Option<Rc<UnsafeCell<dyn Component>>> {
696    with_component_registry(|registry| registry.get(id))
697}
698
699/// Removes the component with `id` from the global registry.
700///
701/// Only the exact ID is removed, so unrelated components sharing the thread-local registry
702/// are untouched.
703pub fn deregister_component(id: &Ustr) {
704    with_component_registry(|registry| registry.remove(id));
705}
706
707#[cfg(test)]
708/// Clears the component registry (for test isolation).
709pub fn clear_component_registry() {
710    with_component_registry(|registry| {
711        registry.components.borrow_mut().clear();
712        registry.borrows.borrow_mut().clear();
713    });
714}
715
716#[cfg(test)]
717mod tests {
718    use std::{
719        any::Any,
720        sync::atomic::{AtomicBool, Ordering},
721    };
722
723    use rstest::rstest;
724
725    use super::*;
726
727    #[derive(Debug)]
728    struct TestComponent {
729        id: ComponentId,
730        state: ComponentState,
731        should_panic: &'static AtomicBool,
732    }
733
734    impl TestComponent {
735        fn new(name: &str, should_panic: &'static AtomicBool) -> Self {
736            Self {
737                id: ComponentId::new(name),
738                state: ComponentState::Ready,
739                should_panic,
740            }
741        }
742    }
743
744    impl Actor for TestComponent {
745        fn id(&self) -> Ustr {
746            self.id.inner()
747        }
748
749        fn handle(&mut self, _msg: &dyn Any) {}
750
751        fn as_any(&self) -> &dyn Any {
752            self
753        }
754    }
755
756    impl Component for TestComponent {
757        fn component_id(&self) -> ComponentId {
758            self.id
759        }
760
761        fn state(&self) -> ComponentState {
762            self.state
763        }
764
765        fn transition_state(&mut self, trigger: ComponentTrigger) -> anyhow::Result<()> {
766            self.state = self.state.transition(&trigger)?;
767            Ok(())
768        }
769
770        fn register(
771            &mut self,
772            _trader_id: TraderId,
773            _clock: Rc<RefCell<dyn Clock>>,
774            _cache: Rc<RefCell<Cache>>,
775        ) -> anyhow::Result<()> {
776            Ok(())
777        }
778
779        #[expect(clippy::panic_in_result_fn)] // Intentional panic for testing
780        fn on_start(&mut self) -> anyhow::Result<()> {
781            assert!(
782                !self.should_panic.load(Ordering::SeqCst),
783                "Intentional panic for testing"
784            );
785            Ok(())
786        }
787    }
788
789    static NO_PANIC: AtomicBool = AtomicBool::new(false);
790    static DO_PANIC: AtomicBool = AtomicBool::new(true);
791
792    #[rstest]
793    fn test_component_borrow_tracking_prevents_double_borrow() {
794        clear_component_registry();
795
796        let id = Ustr::from("test-component-1");
797        let component = TestComponent::new("test-component-1", &NO_PANIC);
798        let component_id = component.id.inner();
799
800        let component_ref = Rc::new(UnsafeCell::new(component));
801        with_component_registry(|registry| registry.insert(component_id, component_ref));
802
803        // First borrow via start_component should succeed
804        let result1 = start_component(&id);
805        assert!(result1.is_ok());
806
807        // Component should now be borrowable again (guard released)
808        let result2 = stop_component(&id);
809        assert!(result2.is_ok());
810    }
811
812    #[rstest]
813    fn test_component_borrow_released_after_lifecycle_call() {
814        clear_component_registry();
815
816        let id = Ustr::from("test-component-2");
817        let component = TestComponent::new("test-component-2", &NO_PANIC);
818        let component_id = component.id.inner();
819
820        let component_ref = Rc::new(UnsafeCell::new(component));
821        with_component_registry(|registry| registry.insert(component_id, component_ref));
822
823        // Call start - borrow should be released after
824        let _ = start_component(&id);
825
826        // Verify not marked as borrowed
827        assert!(!with_component_registry(
828            |registry| registry.is_borrowed(&id)
829        ));
830    }
831
832    #[rstest]
833    fn test_component_borrow_released_on_panic() {
834        clear_component_registry();
835
836        let id = Ustr::from("test-component-panic");
837        let component = TestComponent::new("test-component-panic", &DO_PANIC);
838        let component_id = component.id.inner();
839
840        let component_ref = Rc::new(UnsafeCell::new(component));
841        with_component_registry(|registry| registry.insert(component_id, component_ref));
842
843        // Call start which will panic - catch the panic
844        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
845            let _ = start_component(&id);
846        }));
847        assert!(result.is_err(), "Expected panic from on_start");
848
849        // Borrow should still be released due to BorrowGuard drop
850        assert!(
851            !with_component_registry(|registry| registry.is_borrowed(&id)),
852            "Borrow was not released after panic"
853        );
854    }
855}