Skip to main content

dependency_injector/
container.rs

1//! High-performance dependency injection container
2//!
3//! The `Container` is the core of the DI system. It stores services and
4//! resolves dependencies with minimal overhead.
5
6use crate::factory::AnyFactory;
7use crate::storage::{ServiceStorage, downcast_arc_unchecked};
8use crate::{DiError, Injectable, Result};
9use std::any::{Any, TypeId};
10use std::cell::UnsafeCell;
11use std::sync::Arc;
12use std::sync::atomic::{AtomicBool, Ordering};
13
14#[cfg(feature = "logging")]
15use tracing::{debug, trace};
16
17// =============================================================================
18// Thread-Local Hot Cache (Phase 5 optimization)
19// =============================================================================
20
21/// Number of slots in the thread-local hot cache (power of 2 for fast indexing)
22/// 4 slots fits in a single cache line and provides good hit rates for typical apps.
23const HOT_CACHE_SLOTS: usize = 4;
24
25/// A cached service entry
26///
27/// Phase 13 optimization: Stores pre-computed u64 hash instead of TypeId
28/// to avoid transmute on every comparison.
29struct CacheEntry {
30    /// Pre-computed hash of TypeId (avoids transmute on lookup)
31    type_hash: u64,
32    /// Pointer to the storage this was resolved from (for scope identity)
33    storage_ptr: usize,
34    /// The cached service
35    service: Arc<dyn Any + Send + Sync>,
36}
37
38/// Thread-local cache for frequently accessed services.
39///
40/// This provides ~8-10ns speedup for hot services by avoiding DashMap lookups.
41/// Uses a simple direct-mapped cache with TypeId + storage pointer as key.
42struct HotCache {
43    entries: [Option<CacheEntry>; HOT_CACHE_SLOTS],
44}
45
46impl HotCache {
47    const fn new() -> Self {
48        Self {
49            entries: [const { None }; HOT_CACHE_SLOTS],
50        }
51    }
52
53    /// Get a cached service if present for a specific container
54    ///
55    /// Phase 12+13 optimization: Uses UnsafeCell (no RefCell borrow check)
56    /// and pre-computed type_hash (no transmute on lookup).
57    #[inline(always)]
58    fn get<T: Send + Sync + 'static>(&self, storage_ptr: usize) -> Option<Arc<T>> {
59        let type_hash = Self::type_hash::<T>();
60        let slot = Self::slot_for_hash(type_hash, storage_ptr);
61
62        if let Some(entry) = &self.entries[slot] {
63            // Phase 13: Compare u64 hash directly (faster than TypeId comparison)
64            if entry.type_hash == type_hash && entry.storage_ptr == storage_ptr {
65                // Cache hit - clone and downcast (unchecked since type_hash matches)
66                // SAFETY: We verified type_hash matches, so the Arc contains type T
67                let arc = entry.service.clone();
68                return Some(unsafe { downcast_arc_unchecked(arc) });
69            }
70        }
71        None
72    }
73
74    /// Insert a service into the cache for a specific container
75    #[inline]
76    fn insert<T: Injectable>(&mut self, storage_ptr: usize, service: Arc<T>) {
77        let type_hash = Self::type_hash::<T>();
78        let slot = Self::slot_for_hash(type_hash, storage_ptr);
79
80        self.entries[slot] = Some(CacheEntry {
81            type_hash,
82            storage_ptr,
83            service: service as Arc<dyn Any + Send + Sync>,
84        });
85    }
86
87    /// Clear the cache (call when container is modified)
88    #[inline]
89    fn clear(&mut self) {
90        self.entries = [const { None }; HOT_CACHE_SLOTS];
91    }
92
93    /// Extract u64 hash from TypeId (computed once per type at compile time via monomorphization)
94    #[inline(always)]
95    fn type_hash<T: 'static>() -> u64 {
96        let type_id = TypeId::of::<T>();
97        // SAFETY: TypeId is #[repr(transparent)] wrapper around u128
98        unsafe { std::mem::transmute_copy(&type_id) }
99    }
100
101    /// Calculate slot index from pre-computed type hash and storage pointer
102    #[inline(always)]
103    fn slot_for_hash(type_hash: u64, storage_ptr: usize) -> usize {
104        // Fast bit mixing: XOR with rotated storage_ptr for good distribution
105        let mixed = type_hash ^ (storage_ptr as u64).rotate_left(32);
106
107        // Use golden ratio multiplication for final mixing (fast & good distribution)
108        let slot = mixed.wrapping_mul(0x9e3779b97f4a7c15);
109
110        (slot as usize) & (HOT_CACHE_SLOTS - 1)
111    }
112}
113
114thread_local! {
115    /// Thread-local hot cache for frequently accessed services
116    ///
117    /// Phase 12 optimization: Uses UnsafeCell instead of RefCell to eliminate
118    /// borrow checking overhead. This is safe because thread_local! guarantees
119    /// single-threaded access.
120    static HOT_CACHE: UnsafeCell<HotCache> = const { UnsafeCell::new(HotCache::new()) };
121}
122
123/// Helper to access the hot cache without RefCell overhead
124///
125/// SAFETY: thread_local! guarantees single-threaded access, so we can use
126/// UnsafeCell without data races. We ensure no aliasing by limiting access
127/// to immutable borrows for reads and brief mutable borrows for writes.
128#[inline(always)]
129fn with_hot_cache<F, R>(f: F) -> R
130where
131    F: FnOnce(&HotCache) -> R,
132{
133    HOT_CACHE.with(|cell| {
134        // SAFETY: thread_local guarantees single-threaded access
135        let cache = unsafe { &*cell.get() };
136        f(cache)
137    })
138}
139
140/// Helper to mutably access the hot cache
141#[inline(always)]
142fn with_hot_cache_mut<F, R>(f: F) -> R
143where
144    F: FnOnce(&mut HotCache) -> R,
145{
146    HOT_CACHE.with(|cell| {
147        // SAFETY: thread_local guarantees single-threaded access
148        let cache = unsafe { &mut *cell.get() };
149        f(cache)
150    })
151}
152
153/// High-performance dependency injection container.
154///
155/// Uses lock-free data structures for maximum concurrent throughput.
156/// Supports hierarchical scopes with full parent chain resolution.
157///
158/// # Examples
159///
160/// ```rust
161/// use dependency_injector::Container;
162///
163/// #[derive(Clone)]
164/// struct MyService { name: String }
165///
166/// let container = Container::new();
167/// container.singleton(MyService { name: "test".into() });
168///
169/// let service = container.get::<MyService>().unwrap();
170/// assert_eq!(service.name, "test");
171/// ```
172#[derive(Clone)]
173pub struct Container {
174    /// Service storage (lock-free)
175    storage: Arc<ServiceStorage>,
176    /// Parent storage - strong reference for fast resolution (Phase 2 optimization)
177    /// This avoids Weak::upgrade() cost on every parent resolution
178    parent_storage: Option<Arc<ServiceStorage>>,
179    /// Lock state - uses AtomicBool for fast lock checking (no contention)
180    locked: Arc<AtomicBool>,
181    /// Scope depth for debugging
182    depth: u32,
183}
184
185impl Container {
186    /// Create a new root container.
187    ///
188    /// # Examples
189    ///
190    /// ```rust
191    /// use dependency_injector::Container;
192    /// let container = Container::new();
193    /// ```
194    #[inline]
195    pub fn new() -> Self {
196        #[cfg(feature = "logging")]
197        debug!(
198            target: "dependency_injector",
199            depth = 0,
200            "Creating new root DI container"
201        );
202
203        Self {
204            storage: Arc::new(ServiceStorage::new()),
205            parent_storage: None,
206            locked: Arc::new(AtomicBool::new(false)),
207            depth: 0,
208        }
209    }
210
211    /// Create a container with pre-allocated capacity.
212    ///
213    /// Use this when you know approximately how many services will be registered.
214    #[inline]
215    pub fn with_capacity(capacity: usize) -> Self {
216        Self {
217            storage: Arc::new(ServiceStorage::with_capacity(capacity)),
218            parent_storage: None,
219            locked: Arc::new(AtomicBool::new(false)),
220            depth: 0,
221        }
222    }
223
224    /// Create a child scope that inherits from this container.
225    ///
226    /// Child scopes can:
227    /// - Access all services from parent scopes
228    /// - Override parent services with local registrations
229    /// - Have their own transient/scoped services
230    ///
231    /// # Examples
232    ///
233    /// ```rust
234    /// use dependency_injector::Container;
235    ///
236    /// #[derive(Clone)]
237    /// struct AppConfig { debug: bool }
238    ///
239    /// #[derive(Clone)]
240    /// struct RequestId(String);
241    ///
242    /// let root = Container::new();
243    /// root.singleton(AppConfig { debug: true });
244    ///
245    /// let request = root.scope();
246    /// request.singleton(RequestId("req-123".into()));
247    ///
248    /// // Request scope can access root config
249    /// assert!(request.contains::<AppConfig>());
250    /// ```
251    #[inline]
252    pub fn scope(&self) -> Self {
253        let child_depth = self.depth + 1;
254
255        #[cfg(feature = "logging")]
256        debug!(
257            target: "dependency_injector",
258            parent_depth = self.depth,
259            child_depth = child_depth,
260            parent_services = self.storage.len(),
261            "Creating child scope from parent container"
262        );
263
264        Self {
265            // Phase 9: Storage now holds parent reference for deep chain resolution
266            storage: Arc::new(ServiceStorage::with_parent(Arc::clone(&self.storage))),
267            parent_storage: Some(Arc::clone(&self.storage)), // Keep for quick parent access
268            locked: Arc::new(AtomicBool::new(false)),
269            depth: child_depth,
270        }
271    }
272
273    /// Alias for `scope()` - creates a child container.
274    #[inline]
275    pub fn create_scope(&self) -> Self {
276        self.scope()
277    }
278
279    // =========================================================================
280    // Registration Methods
281    // =========================================================================
282
283    /// Register a singleton service (eager).
284    ///
285    /// The instance is stored immediately and shared across all resolves.
286    ///
287    /// # Examples
288    ///
289    /// ```rust
290    /// use dependency_injector::Container;
291    ///
292    /// #[derive(Clone)]
293    /// struct Database { url: String }
294    ///
295    /// let container = Container::new();
296    /// container.singleton(Database { url: "postgres://localhost".into() });
297    /// ```
298    #[inline]
299    pub fn singleton<T: Injectable>(&self, instance: T) {
300        self.check_not_locked();
301
302        let type_id = TypeId::of::<T>();
303        let type_name = std::any::type_name::<T>();
304
305        #[cfg(feature = "logging")]
306        debug!(
307            target: "dependency_injector",
308            service = type_name,
309            lifetime = "singleton",
310            depth = self.depth,
311            service_count = self.storage.len() + 1,
312            "Registering singleton service"
313        );
314
315        // Phase 2: Use enum-based AnyFactory directly
316        self.storage
317            .insert(type_id, AnyFactory::singleton(instance));
318    }
319
320    /// Register a lazy singleton service.
321    ///
322    /// The factory is called once on first access, then the instance is cached.
323    ///
324    /// # Examples
325    ///
326    /// ```rust
327    /// use dependency_injector::Container;
328    ///
329    /// #[derive(Clone)]
330    /// struct ExpensiveService { data: Vec<u8> }
331    ///
332    /// let container = Container::new();
333    /// container.lazy(|| ExpensiveService {
334    ///     data: vec![0; 1024 * 1024], // Only allocated on first use
335    /// });
336    /// ```
337    #[inline]
338    pub fn lazy<T: Injectable, F>(&self, factory: F)
339    where
340        F: Fn() -> T + Send + Sync + 'static,
341    {
342        self.check_not_locked();
343
344        let type_id = TypeId::of::<T>();
345        let type_name = std::any::type_name::<T>();
346
347        #[cfg(feature = "logging")]
348        debug!(
349            target: "dependency_injector",
350            service = type_name,
351            lifetime = "lazy_singleton",
352            depth = self.depth,
353            service_count = self.storage.len() + 1,
354            "Registering lazy singleton service (will be created on first access)"
355        );
356
357        // Phase 2: Use enum-based AnyFactory directly
358        self.storage.insert(type_id, AnyFactory::lazy(factory));
359    }
360
361    /// Register a transient service.
362    ///
363    /// A new instance is created on every resolve.
364    ///
365    /// # Examples
366    ///
367    /// ```rust
368    /// use dependency_injector::Container;
369    /// use std::sync::atomic::{AtomicU64, Ordering};
370    ///
371    /// static COUNTER: AtomicU64 = AtomicU64::new(0);
372    ///
373    /// #[derive(Clone)]
374    /// struct RequestId(u64);
375    ///
376    /// let container = Container::new();
377    /// container.transient(|| RequestId(COUNTER.fetch_add(1, Ordering::SeqCst)));
378    ///
379    /// let id1 = container.get::<RequestId>().unwrap();
380    /// let id2 = container.get::<RequestId>().unwrap();
381    /// assert_ne!(id1.0, id2.0); // Different instances
382    /// ```
383    #[inline]
384    pub fn transient<T: Injectable, F>(&self, factory: F)
385    where
386        F: Fn() -> T + Send + Sync + 'static,
387    {
388        self.check_not_locked();
389
390        let type_id = TypeId::of::<T>();
391        let type_name = std::any::type_name::<T>();
392
393        #[cfg(feature = "logging")]
394        debug!(
395            target: "dependency_injector",
396            service = type_name,
397            lifetime = "transient",
398            depth = self.depth,
399            service_count = self.storage.len() + 1,
400            "Registering transient service (new instance on every resolve)"
401        );
402
403        // Phase 2: Use enum-based AnyFactory directly
404        self.storage.insert(type_id, AnyFactory::transient(factory));
405    }
406
407    /// Register using a factory (alias for `lazy`).
408    #[inline]
409    pub fn register_factory<T: Injectable, F>(&self, factory: F)
410    where
411        F: Fn() -> T + Send + Sync + 'static,
412    {
413        self.lazy(factory);
414    }
415
416    /// Register an instance (alias for `singleton`).
417    #[inline]
418    pub fn register<T: Injectable>(&self, instance: T) {
419        self.singleton(instance);
420    }
421
422    /// Register a boxed instance.
423    #[inline]
424    #[allow(clippy::boxed_local)]
425    pub fn register_boxed<T: Injectable>(&self, instance: Box<T>) {
426        self.singleton(*instance);
427    }
428
429    /// Register by TypeId directly (advanced use).
430    #[inline]
431    pub fn register_by_id(&self, type_id: TypeId, instance: Arc<dyn Any + Send + Sync>) {
432        self.check_not_locked();
433
434        // Phase 2: Use the singleton factory with pre-erased Arc directly
435        self.storage.insert(
436            type_id,
437            AnyFactory::Singleton(crate::factory::SingletonFactory { instance }),
438        );
439    }
440
441    // =========================================================================
442    // Resolution Methods
443    // =========================================================================
444
445    /// Resolve a service by type.
446    ///
447    /// Returns `Arc<T>` for zero-copy sharing. Walks the parent chain if
448    /// not found in the current scope.
449    ///
450    /// # Performance
451    ///
452    /// Uses thread-local caching for frequently accessed services (~8ns vs ~19ns).
453    /// The cache is automatically populated on first access.
454    ///
455    /// # Examples
456    ///
457    /// ```rust
458    /// use dependency_injector::Container;
459    ///
460    /// #[derive(Clone)]
461    /// struct MyService;
462    ///
463    /// let container = Container::new();
464    /// container.singleton(MyService);
465    ///
466    /// let service = container.get::<MyService>().unwrap();
467    /// ```
468    #[inline]
469    pub fn get<T: Injectable>(&self) -> Result<Arc<T>> {
470        // Get storage pointer for cache key (unique per container scope)
471        let storage_ptr = Arc::as_ptr(&self.storage) as usize;
472
473        // Phase 5+12: Check thread-local hot cache first (UnsafeCell, no RefCell overhead)
474        // Note: Transients won't be in cache, so they'll fall through to get_and_cache
475        if let Some(cached) = with_hot_cache(|cache| cache.get::<T>(storage_ptr)) {
476            #[cfg(feature = "logging")]
477            trace!(
478                target: "dependency_injector",
479                service = std::any::type_name::<T>(),
480                depth = self.depth,
481                location = "hot_cache",
482                "Service resolved from thread-local cache"
483            );
484            return Ok(cached);
485        }
486
487        // Cache miss - resolve normally and cache the result (unless transient)
488        self.get_and_cache::<T>(storage_ptr)
489    }
490
491    /// Internal: Resolve and cache a service
492    ///
493    /// Phase 15 optimization: Fast path for root containers (depth == 0) avoids
494    /// function call overhead to resolve_from_parents when there are no parents.
495    #[inline]
496    fn get_and_cache<T: Injectable>(&self, storage_ptr: usize) -> Result<Arc<T>> {
497        let type_id = TypeId::of::<T>();
498
499        #[cfg(feature = "logging")]
500        let type_name = std::any::type_name::<T>();
501
502        #[cfg(feature = "logging")]
503        trace!(
504            target: "dependency_injector",
505            service = type_name,
506            depth = self.depth,
507            "Resolving service (cache miss)"
508        );
509
510        // Try local storage first (most common case)
511        // Use get_with_transient_flag to avoid second DashMap lookup for is_transient
512        if let Some((service, is_transient)) = self.storage.get_with_transient_flag::<T>() {
513            #[cfg(feature = "logging")]
514            trace!(
515                target: "dependency_injector",
516                service = type_name,
517                depth = self.depth,
518                location = "local",
519                "Service resolved from current scope"
520            );
521
522            // Cache non-transient services (transients create new instances each time)
523            if !is_transient {
524                with_hot_cache_mut(|cache| cache.insert(storage_ptr, Arc::clone(&service)));
525            }
526
527            return Ok(service);
528        }
529
530        // Phase 15: Fast path for root containers - no parents to walk
531        if self.depth == 0 {
532            #[cfg(feature = "logging")]
533            debug!(
534                target: "dependency_injector",
535                service = std::any::type_name::<T>(),
536                "Service not found in root container"
537            );
538            return Err(DiError::not_found::<T>());
539        }
540
541        // Walk parent chain (cold path)
542        self.resolve_from_parents::<T>(&type_id, storage_ptr)
543    }
544
545    /// Resolve from parent chain (internal)
546    ///
547    /// Phase 9 optimization: Walks the full parent chain via ServiceStorage.parent.
548    /// This allows services to be resolved from any ancestor scope.
549    ///
550    /// Phase 14 optimization: Marked as cold to improve branch prediction in the
551    /// hot path - most resolutions hit the cache and don't need parent traversal.
552    #[cold]
553    fn resolve_from_parents<T: Injectable>(
554        &self,
555        type_id: &TypeId,
556        storage_ptr: usize,
557    ) -> Result<Arc<T>> {
558        let type_name = std::any::type_name::<T>();
559
560        #[cfg(feature = "logging")]
561        trace!(
562            target: "dependency_injector",
563            service = type_name,
564            depth = self.depth,
565            "Service not in local scope, walking parent chain"
566        );
567
568        // Walk the full parent chain via storage's parent references
569        let mut current = self.storage.parent();
570        let mut ancestor_depth = self.depth.saturating_sub(1);
571
572        while let Some(storage) = current {
573            if let Some(arc) = storage.resolve(type_id) {
574                // SAFETY: We resolved by TypeId::of::<T>(), so the factory
575                // was registered with the same TypeId and stores type T.
576                let typed: Arc<T> = unsafe { downcast_arc_unchecked(arc) };
577
578                #[cfg(feature = "logging")]
579                trace!(
580                    target: "dependency_injector",
581                    service = type_name,
582                    depth = self.depth,
583                    ancestor_depth = ancestor_depth,
584                    location = "ancestor",
585                    "Service resolved from ancestor scope"
586                );
587
588                // Cache non-transient services from parent (using child's storage ptr as key)
589                if !storage.is_transient(type_id) {
590                    with_hot_cache_mut(|cache| cache.insert(storage_ptr, Arc::clone(&typed)));
591                }
592
593                return Ok(typed);
594            }
595            current = storage.parent();
596            ancestor_depth = ancestor_depth.saturating_sub(1);
597        }
598
599        #[cfg(feature = "logging")]
600        debug!(
601            target: "dependency_injector",
602            service = type_name,
603            depth = self.depth,
604            "Service not found in container or parent chain"
605        );
606
607        Err(DiError::not_found::<T>())
608    }
609
610    /// Clear the thread-local hot cache.
611    ///
612    /// Call this after modifying the container (registering/removing services)
613    /// if you want subsequent resolutions to see the changes immediately.
614    ///
615    /// Note: The cache is automatically invalidated when services are
616    /// re-registered, but this method can be used for explicit control.
617    #[inline]
618    pub fn clear_cache(&self) {
619        with_hot_cache_mut(|cache| cache.clear());
620    }
621
622    /// Pre-warm the thread-local cache with a specific service type.
623    ///
624    /// This can be useful at the start of request handling to ensure
625    /// hot services are already in the cache.
626    ///
627    /// # Example
628    ///
629    /// ```rust
630    /// use dependency_injector::Container;
631    ///
632    /// #[derive(Clone)]
633    /// struct Database;
634    ///
635    /// let container = Container::new();
636    /// container.singleton(Database);
637    ///
638    /// // Pre-warm cache for hot services
639    /// container.warm_cache::<Database>();
640    /// ```
641    #[inline]
642    pub fn warm_cache<T: Injectable>(&self) {
643        // Simply resolve the service to populate the cache
644        let _ = self.get::<T>();
645    }
646
647    /// Alias for `get` - resolve a service.
648    #[inline]
649    pub fn resolve<T: Injectable>(&self) -> Result<Arc<T>> {
650        self.get::<T>()
651    }
652
653    /// Try to resolve, returning None if not found.
654    ///
655    /// # Examples
656    ///
657    /// ```rust
658    /// use dependency_injector::Container;
659    ///
660    /// #[derive(Clone)]
661    /// struct OptionalService;
662    ///
663    /// let container = Container::new();
664    /// assert!(container.try_get::<OptionalService>().is_none());
665    /// ```
666    #[inline]
667    pub fn try_get<T: Injectable>(&self) -> Option<Arc<T>> {
668        self.get::<T>().ok()
669    }
670
671    /// Alias for `try_get`.
672    #[inline]
673    pub fn try_resolve<T: Injectable>(&self) -> Option<Arc<T>> {
674        self.try_get::<T>()
675    }
676
677    // =========================================================================
678    // Query Methods
679    // =========================================================================
680
681    /// Check if a service is registered.
682    ///
683    /// Checks both current scope and parent scopes.
684    #[inline]
685    pub fn contains<T: Injectable>(&self) -> bool {
686        let type_id = TypeId::of::<T>();
687        self.contains_type_id(&type_id)
688    }
689
690    /// Alias for `contains`.
691    #[inline]
692    pub fn has<T: Injectable>(&self) -> bool {
693        self.contains::<T>()
694    }
695
696    /// Check by TypeId
697    /// Phase 9 optimization: Uses storage's parent chain for deep hierarchy support
698    fn contains_type_id(&self, type_id: &TypeId) -> bool {
699        // Check local storage and full parent chain
700        self.storage.contains_in_chain(type_id)
701    }
702
703    /// Get the number of services in this scope (not including parents).
704    #[inline]
705    pub fn len(&self) -> usize {
706        self.storage.len()
707    }
708
709    /// Check if this scope is empty.
710    #[inline]
711    pub fn is_empty(&self) -> bool {
712        self.storage.is_empty()
713    }
714
715    /// Get all registered TypeIds in this scope.
716    pub fn registered_types(&self) -> Vec<TypeId> {
717        self.storage.type_ids()
718    }
719
720    /// Get the scope depth (0 = root).
721    #[inline]
722    pub fn depth(&self) -> u32 {
723        self.depth
724    }
725
726    // =========================================================================
727    // Lifecycle Methods
728    // =========================================================================
729
730    /// Lock the container to prevent further registrations.
731    ///
732    /// Useful for ensuring no services are registered after app initialization.
733    #[inline]
734    pub fn lock(&self) {
735        self.locked.store(true, Ordering::Release);
736
737        #[cfg(feature = "logging")]
738        debug!(
739            target: "dependency_injector",
740            depth = self.depth,
741            service_count = self.storage.len(),
742            "Container locked - no further registrations allowed"
743        );
744    }
745
746    /// Check if the container is locked.
747    #[inline]
748    pub fn is_locked(&self) -> bool {
749        self.locked.load(Ordering::Acquire)
750    }
751
752    /// Freeze the container into an immutable, perfectly-hashed storage.
753    ///
754    /// This creates a `FrozenStorage` that uses minimal perfect hashing for
755    /// O(1) lookups without hash collisions, providing ~5ns faster resolution.
756    ///
757    /// Note: This also locks the container to prevent further registrations.
758    ///
759    /// # Example
760    ///
761    /// ```rust,ignore
762    /// use dependency_injector::Container;
763    ///
764    /// let container = Container::new();
765    /// container.singleton(MyService { ... });
766    ///
767    /// let frozen = container.freeze();
768    /// // Use frozen.resolve(&type_id) for faster lookups
769    /// ```
770    #[cfg(feature = "perfect-hash")]
771    #[inline]
772    pub fn freeze(&self) -> crate::storage::FrozenStorage {
773        self.lock();
774        crate::storage::FrozenStorage::from_storage(&self.storage)
775    }
776
777    /// Clear all services from this scope.
778    ///
779    /// Does not affect parent scopes.
780    #[inline]
781    pub fn clear(&self) {
782        let count = self.storage.len();
783        self.storage.clear();
784
785        #[cfg(feature = "logging")]
786        debug!(
787            target: "dependency_injector",
788            depth = self.depth,
789            services_removed = count,
790            "Container cleared - all services removed from this scope"
791        );
792    }
793
794    /// Panic if locked (internal helper).
795    /// Uses relaxed ordering for fast path - we only need eventual consistency
796    /// since registration is not a hot path and locking is rare.
797    #[inline]
798    fn check_not_locked(&self) {
799        if self.locked.load(Ordering::Relaxed) {
800            panic!("Cannot register services: container is locked");
801        }
802    }
803
804    // =========================================================================
805    // Batch Registration (Phase 3)
806    // =========================================================================
807
808    /// Register multiple services in a single batch operation.
809    ///
810    /// This is more efficient than individual registrations when registering
811    /// many services at once, as it:
812    /// - Performs a single lock check at the start
813    /// - Minimizes per-call overhead
814    ///
815    /// # Examples
816    ///
817    /// ```rust
818    /// use dependency_injector::Container;
819    ///
820    /// #[derive(Clone)]
821    /// struct Database { url: String }
822    /// #[derive(Clone)]
823    /// struct Cache { size: usize }
824    /// #[derive(Clone)]
825    /// struct Logger { level: String }
826    ///
827    /// let container = Container::new();
828    /// container.batch(|batch| {
829    ///     batch.singleton(Database { url: "postgres://localhost".into() });
830    ///     batch.singleton(Cache { size: 1024 });
831    ///     batch.singleton(Logger { level: "info".into() });
832    /// });
833    ///
834    /// assert!(container.contains::<Database>());
835    /// assert!(container.contains::<Cache>());
836    /// assert!(container.contains::<Logger>());
837    /// ```
838    ///
839    /// Note: For maximum performance with many services, prefer the builder API:
840    /// ```rust
841    /// use dependency_injector::Container;
842    ///
843    /// #[derive(Clone)]
844    /// struct A;
845    /// #[derive(Clone)]
846    /// struct B;
847    ///
848    /// let container = Container::new();
849    /// container.register_batch()
850    ///     .singleton(A)
851    ///     .singleton(B)
852    ///     .done();
853    /// ```
854    #[inline]
855    pub fn batch<F>(&self, f: F)
856    where
857        F: FnOnce(BatchRegistrar<'_>),
858    {
859        self.check_not_locked();
860
861        #[cfg(feature = "logging")]
862        let start_count = self.storage.len();
863
864        // Create a zero-cost batch registrar that wraps the storage
865        f(BatchRegistrar {
866            storage: &self.storage,
867        });
868
869        #[cfg(feature = "logging")]
870        {
871            let end_count = self.storage.len();
872            debug!(
873                target: "dependency_injector",
874                depth = self.depth,
875                services_registered = end_count - start_count,
876                "Batch registration completed"
877            );
878        }
879    }
880
881    /// Start a fluent batch registration.
882    ///
883    /// This is faster than the closure-based `batch()` for many services
884    /// because it avoids closure overhead.
885    ///
886    /// # Example
887    ///
888    /// ```rust
889    /// use dependency_injector::Container;
890    ///
891    /// #[derive(Clone)]
892    /// struct Database { url: String }
893    /// #[derive(Clone)]
894    /// struct Cache { size: usize }
895    ///
896    /// let container = Container::new();
897    /// container.register_batch()
898    ///     .singleton(Database { url: "postgres://localhost".into() })
899    ///     .singleton(Cache { size: 1024 })
900    ///     .done();
901    ///
902    /// assert!(container.contains::<Database>());
903    /// assert!(container.contains::<Cache>());
904    /// ```
905    #[inline]
906    pub fn register_batch(&self) -> BatchBuilder<'_> {
907        self.check_not_locked();
908        BatchBuilder {
909            storage: &self.storage,
910            #[cfg(feature = "logging")]
911            count: 0,
912        }
913    }
914}
915
916/// Fluent batch registration builder.
917///
918/// Provides a chainable API for registering multiple services without closure overhead.
919pub struct BatchBuilder<'a> {
920    storage: &'a ServiceStorage,
921    #[cfg(feature = "logging")]
922    count: usize,
923}
924
925impl<'a> BatchBuilder<'a> {
926    /// Register a singleton and continue the chain
927    #[inline]
928    pub fn singleton<T: Injectable>(self, instance: T) -> Self {
929        self.storage
930            .insert(TypeId::of::<T>(), AnyFactory::singleton(instance));
931        Self {
932            storage: self.storage,
933            #[cfg(feature = "logging")]
934            count: self.count + 1,
935        }
936    }
937
938    /// Register a lazy singleton and continue the chain
939    #[inline]
940    pub fn lazy<T: Injectable, F>(self, factory: F) -> Self
941    where
942        F: Fn() -> T + Send + Sync + 'static,
943    {
944        self.storage
945            .insert(TypeId::of::<T>(), AnyFactory::lazy(factory));
946        Self {
947            storage: self.storage,
948            #[cfg(feature = "logging")]
949            count: self.count + 1,
950        }
951    }
952
953    /// Register a transient and continue the chain
954    #[inline]
955    pub fn transient<T: Injectable, F>(self, factory: F) -> Self
956    where
957        F: Fn() -> T + Send + Sync + 'static,
958    {
959        self.storage
960            .insert(TypeId::of::<T>(), AnyFactory::transient(factory));
961        Self {
962            storage: self.storage,
963            #[cfg(feature = "logging")]
964            count: self.count + 1,
965        }
966    }
967
968    /// Finish the batch registration
969    #[inline]
970    pub fn done(self) {
971        #[cfg(feature = "logging")]
972        debug!(
973            target: "dependency_injector",
974            services_registered = self.count,
975            "Batch registration completed"
976        );
977    }
978}
979
980/// Batch registrar for closure-based bulk registration.
981///
982/// A zero-cost wrapper that provides direct storage access.
983/// The lock check is done once in `Container::batch()`.
984#[repr(transparent)]
985pub struct BatchRegistrar<'a> {
986    storage: &'a ServiceStorage,
987}
988
989impl<'a> BatchRegistrar<'a> {
990    /// Register a singleton service (inserted immediately)
991    #[inline]
992    pub fn singleton<T: Injectable>(&self, instance: T) {
993        self.storage
994            .insert(TypeId::of::<T>(), AnyFactory::singleton(instance));
995    }
996
997    /// Register a lazy singleton service (inserted immediately)
998    #[inline]
999    pub fn lazy<T: Injectable, F>(&self, factory: F)
1000    where
1001        F: Fn() -> T + Send + Sync + 'static,
1002    {
1003        self.storage
1004            .insert(TypeId::of::<T>(), AnyFactory::lazy(factory));
1005    }
1006
1007    /// Register a transient service (inserted immediately)
1008    #[inline]
1009    pub fn transient<T: Injectable, F>(&self, factory: F)
1010    where
1011        F: Fn() -> T + Send + Sync + 'static,
1012    {
1013        self.storage
1014            .insert(TypeId::of::<T>(), AnyFactory::transient(factory));
1015    }
1016}
1017
1018// =============================================================================
1019// Scope Pooling (Phase 6 optimization)
1020// =============================================================================
1021
1022use std::sync::Mutex;
1023
1024/// A pool of pre-allocated scopes for high-throughput scenarios.
1025///
1026/// Creating a scope involves allocating a DashMap (~134ns). For web servers
1027/// handling thousands of requests per second, this adds up. ScopePool pre-allocates
1028/// scopes and reuses them, reducing per-request overhead to near-zero.
1029///
1030/// # Example
1031///
1032/// ```rust
1033/// use dependency_injector::{Container, ScopePool};
1034///
1035/// #[derive(Clone)]
1036/// struct AppConfig { name: String }
1037///
1038/// #[derive(Clone)]
1039/// struct RequestId(String);
1040///
1041/// // Create root container with app-wide services
1042/// let root = Container::new();
1043/// root.singleton(AppConfig { name: "MyApp".into() });
1044///
1045/// // Create a pool of reusable scopes (pre-allocates 4 scopes)
1046/// let pool = ScopePool::new(&root, 4);
1047///
1048/// // In request handler: acquire a pooled scope
1049/// {
1050///     let scope = pool.acquire();
1051///     scope.singleton(RequestId("req-123".into()));
1052///
1053///     // Can access parent services
1054///     assert!(scope.contains::<AppConfig>());
1055///     assert!(scope.contains::<RequestId>());
1056///
1057///     // Scope automatically released when dropped
1058/// }
1059///
1060/// // Next request reuses the same scope allocation
1061/// {
1062///     let scope = pool.acquire();
1063///     // Previous RequestId is cleared, fresh scope
1064///     assert!(!scope.contains::<RequestId>());
1065/// }
1066/// ```
1067///
1068/// # Performance
1069///
1070/// - First acquisition: ~134ns (creates new scope if pool is empty)
1071/// - Subsequent acquisitions: ~20ns (reuses pooled scope)
1072/// - Release: ~10ns (clears and returns to pool)
1073pub struct ScopePool {
1074    /// Parent storage to create scopes from
1075    parent_storage: Arc<ServiceStorage>,
1076    /// Pool of available scopes (storage + lock state pairs)
1077    available: Mutex<Vec<ScopeSlot>>,
1078    /// Parent depth for child scope depth calculation
1079    parent_depth: u32,
1080}
1081
1082/// A reusable scope slot containing pre-allocated storage and lock state
1083struct ScopeSlot {
1084    /// Pre-allocated storage with parent reference
1085    storage: Arc<ServiceStorage>,
1086    locked: Arc<AtomicBool>,
1087}
1088
1089impl ScopePool {
1090    /// Create a new scope pool with pre-allocated capacity.
1091    ///
1092    /// # Arguments
1093    ///
1094    /// * `parent` - The parent container that scopes will inherit from
1095    /// * `capacity` - Number of scopes to pre-allocate
1096    ///
1097    /// # Example
1098    ///
1099    /// ```rust
1100    /// use dependency_injector::{Container, ScopePool};
1101    ///
1102    /// let root = Container::new();
1103    /// // Pre-allocate 8 scopes for concurrent request handling
1104    /// let pool = ScopePool::new(&root, 8);
1105    /// ```
1106    pub fn new(parent: &Container, capacity: usize) -> Self {
1107        let mut available = Vec::with_capacity(capacity);
1108
1109        // Pre-allocate storage with parent reference and lock states
1110        for _ in 0..capacity {
1111            available.push(ScopeSlot {
1112                storage: Arc::new(ServiceStorage::with_parent(Arc::clone(&parent.storage))),
1113                locked: Arc::new(AtomicBool::new(false)),
1114            });
1115        }
1116
1117        #[cfg(feature = "logging")]
1118        debug!(
1119            target: "dependency_injector",
1120            capacity = capacity,
1121            parent_depth = parent.depth,
1122            "Created scope pool with pre-allocated scopes"
1123        );
1124
1125        Self {
1126            parent_storage: Arc::clone(&parent.storage),
1127            available: Mutex::new(available),
1128            parent_depth: parent.depth,
1129        }
1130    }
1131
1132    /// Acquire a scope from the pool.
1133    ///
1134    /// Returns a `PooledScope` that automatically returns to the pool when dropped.
1135    /// If the pool is empty, creates a new scope.
1136    ///
1137    /// # Example
1138    ///
1139    /// ```rust
1140    /// use dependency_injector::{Container, ScopePool};
1141    ///
1142    /// #[derive(Clone)]
1143    /// struct RequestData { id: u64 }
1144    ///
1145    /// let root = Container::new();
1146    /// let pool = ScopePool::new(&root, 4);
1147    ///
1148    /// let scope = pool.acquire();
1149    /// scope.singleton(RequestData { id: 123 });
1150    /// let data = scope.get::<RequestData>().unwrap();
1151    /// assert_eq!(data.id, 123);
1152    /// ```
1153    #[inline]
1154    pub fn acquire(&self) -> PooledScope<'_> {
1155        let slot = self.available.lock().unwrap().pop();
1156
1157        let (storage, locked) = match slot {
1158            Some(slot) => {
1159                #[cfg(feature = "logging")]
1160                trace!(
1161                    target: "dependency_injector",
1162                    "Acquired scope from pool (reusing storage)"
1163                );
1164                (slot.storage, slot.locked)
1165            }
1166            None => {
1167                #[cfg(feature = "logging")]
1168                trace!(
1169                    target: "dependency_injector",
1170                    "Pool empty, creating new scope"
1171                );
1172                (
1173                    Arc::new(ServiceStorage::with_parent(Arc::clone(
1174                        &self.parent_storage,
1175                    ))),
1176                    Arc::new(AtomicBool::new(false)),
1177                )
1178            }
1179        };
1180
1181        let container = Container {
1182            storage,
1183            parent_storage: Some(Arc::clone(&self.parent_storage)),
1184            locked,
1185            depth: self.parent_depth + 1,
1186        };
1187
1188        PooledScope {
1189            container: Some(container),
1190            pool: self,
1191        }
1192    }
1193
1194    /// Return a scope to the pool (internal use).
1195    #[inline]
1196    fn release(&self, container: Container) {
1197        // Clear storage for reuse (parent reference is preserved)
1198        container.storage.clear();
1199        // Reset lock state
1200        container.locked.store(false, Ordering::Relaxed);
1201
1202        // Return to pool
1203        self.available.lock().unwrap().push(ScopeSlot {
1204            storage: container.storage,
1205            locked: container.locked,
1206        });
1207
1208        #[cfg(feature = "logging")]
1209        trace!(
1210            target: "dependency_injector",
1211            "Released scope back to pool"
1212        );
1213    }
1214
1215    /// Get the current number of available scopes in the pool.
1216    #[inline]
1217    pub fn available_count(&self) -> usize {
1218        self.available.lock().unwrap().len()
1219    }
1220}
1221
1222/// A scope acquired from a pool that automatically returns when dropped.
1223///
1224/// This provides RAII-style management of pooled scopes, ensuring they're
1225/// always returned to the pool even if the code panics.
1226pub struct PooledScope<'a> {
1227    container: Option<Container>,
1228    pool: &'a ScopePool,
1229}
1230
1231impl PooledScope<'_> {
1232    /// Get a reference to the underlying container.
1233    #[inline]
1234    pub fn container(&self) -> &Container {
1235        self.container.as_ref().unwrap()
1236    }
1237}
1238
1239impl std::ops::Deref for PooledScope<'_> {
1240    type Target = Container;
1241
1242    #[inline]
1243    fn deref(&self) -> &Self::Target {
1244        self.container.as_ref().unwrap()
1245    }
1246}
1247
1248impl Drop for PooledScope<'_> {
1249    fn drop(&mut self) {
1250        if let Some(container) = self.container.take() {
1251            self.pool.release(container);
1252        }
1253    }
1254}
1255
1256impl Default for Container {
1257    fn default() -> Self {
1258        Self::new()
1259    }
1260}
1261
1262impl std::fmt::Debug for Container {
1263    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1264        f.debug_struct("Container")
1265            .field("service_count", &self.len())
1266            .field("depth", &self.depth)
1267            .field("has_parent", &self.parent_storage.is_some())
1268            .field("locked", &self.is_locked())
1269            .finish_non_exhaustive()
1270    }
1271}
1272
1273// =========================================================================
1274// Thread Safety
1275// =========================================================================
1276
1277// Container is Send + Sync because:
1278// - ServiceStorage uses DashMap (thread-safe)
1279// - parent is Weak<...> which is Send + Sync
1280// - locked uses AtomicBool (Send + Sync)
1281unsafe impl Send for Container {}
1282unsafe impl Sync for Container {}
1283
1284#[cfg(test)]
1285mod tests {
1286    use super::*;
1287
1288    #[derive(Clone)]
1289    struct TestService {
1290        value: String,
1291    }
1292
1293    #[allow(dead_code)]
1294    #[derive(Clone)]
1295    struct AnotherService {
1296        name: String,
1297    }
1298
1299    #[test]
1300    fn test_singleton() {
1301        let container = Container::new();
1302        container.singleton(TestService {
1303            value: "test".into(),
1304        });
1305
1306        let s1 = container.get::<TestService>().unwrap();
1307        let s2 = container.get::<TestService>().unwrap();
1308
1309        assert_eq!(s1.value, "test");
1310        assert!(Arc::ptr_eq(&s1, &s2));
1311    }
1312
1313    #[test]
1314    fn test_lazy() {
1315        use std::sync::atomic::{AtomicBool, Ordering};
1316
1317        static CREATED: AtomicBool = AtomicBool::new(false);
1318
1319        let container = Container::new();
1320        container.lazy(|| {
1321            CREATED.store(true, Ordering::SeqCst);
1322            TestService {
1323                value: "lazy".into(),
1324            }
1325        });
1326
1327        assert!(!CREATED.load(Ordering::SeqCst));
1328
1329        let s = container.get::<TestService>().unwrap();
1330        assert!(CREATED.load(Ordering::SeqCst));
1331        assert_eq!(s.value, "lazy");
1332    }
1333
1334    #[test]
1335    fn test_transient() {
1336        use std::sync::atomic::{AtomicU32, Ordering};
1337
1338        static COUNTER: AtomicU32 = AtomicU32::new(0);
1339
1340        #[derive(Clone)]
1341        struct Counter(u32);
1342
1343        let container = Container::new();
1344        container.transient(|| Counter(COUNTER.fetch_add(1, Ordering::SeqCst)));
1345
1346        let c1 = container.get::<Counter>().unwrap();
1347        let c2 = container.get::<Counter>().unwrap();
1348
1349        assert_ne!(c1.0, c2.0);
1350    }
1351
1352    #[test]
1353    fn test_scope_inheritance() {
1354        let root = Container::new();
1355        root.singleton(TestService {
1356            value: "root".into(),
1357        });
1358
1359        let child = root.scope();
1360        child.singleton(AnotherService {
1361            name: "child".into(),
1362        });
1363
1364        // Child sees both
1365        assert!(child.contains::<TestService>());
1366        assert!(child.contains::<AnotherService>());
1367
1368        // Root only sees its own
1369        assert!(root.contains::<TestService>());
1370        assert!(!root.contains::<AnotherService>());
1371    }
1372
1373    #[test]
1374    fn test_scope_override() {
1375        let root = Container::new();
1376        root.singleton(TestService {
1377            value: "root".into(),
1378        });
1379
1380        let child = root.scope();
1381        child.singleton(TestService {
1382            value: "child".into(),
1383        });
1384
1385        let root_service = root.get::<TestService>().unwrap();
1386        let child_service = child.get::<TestService>().unwrap();
1387
1388        assert_eq!(root_service.value, "root");
1389        assert_eq!(child_service.value, "child");
1390    }
1391
1392    #[test]
1393    fn test_not_found() {
1394        let container = Container::new();
1395        let result = container.get::<TestService>();
1396        assert!(result.is_err());
1397    }
1398
1399    #[test]
1400    fn test_lock() {
1401        let container = Container::new();
1402        assert!(!container.is_locked());
1403
1404        container.lock();
1405        assert!(container.is_locked());
1406    }
1407
1408    #[test]
1409    #[should_panic(expected = "Cannot register services: container is locked")]
1410    fn test_register_after_lock() {
1411        let container = Container::new();
1412        container.lock();
1413        container.singleton(TestService {
1414            value: "fail".into(),
1415        });
1416    }
1417
1418    #[test]
1419    fn test_batch_registration() {
1420        #[derive(Clone)]
1421        struct ServiceA(i32);
1422        #[allow(dead_code)]
1423        #[derive(Clone)]
1424        struct ServiceB(String);
1425
1426        let container = Container::new();
1427        container.batch(|batch| {
1428            batch.singleton(ServiceA(42));
1429            batch.singleton(ServiceB("test".into()));
1430            batch.lazy(|| TestService {
1431                value: "lazy".into(),
1432            });
1433        });
1434
1435        assert!(container.contains::<ServiceA>());
1436        assert!(container.contains::<ServiceB>());
1437        assert!(container.contains::<TestService>());
1438
1439        let a = container.get::<ServiceA>().unwrap();
1440        assert_eq!(a.0, 42);
1441    }
1442
1443    #[test]
1444    fn test_scope_pool_basic() {
1445        #[derive(Clone)]
1446        struct RequestId(u64);
1447
1448        let root = Container::new();
1449        root.singleton(TestService {
1450            value: "root".into(),
1451        });
1452
1453        // Create pool with 2 pre-allocated scopes
1454        let pool = ScopePool::new(&root, 2);
1455        assert_eq!(pool.available_count(), 2);
1456
1457        // Acquire a scope
1458        {
1459            let scope = pool.acquire();
1460            assert_eq!(pool.available_count(), 1);
1461
1462            // Can access parent services
1463            assert!(scope.contains::<TestService>());
1464
1465            // Register request-specific service
1466            scope.singleton(RequestId(123));
1467            assert!(scope.contains::<RequestId>());
1468
1469            let id = scope.get::<RequestId>().unwrap();
1470            assert_eq!(id.0, 123);
1471        }
1472        // Scope released back to pool
1473        assert_eq!(pool.available_count(), 2);
1474    }
1475
1476    #[test]
1477    fn test_scope_pool_reuse() {
1478        #[derive(Clone)]
1479        struct RequestId(u64);
1480
1481        let root = Container::new();
1482        let pool = ScopePool::new(&root, 1);
1483
1484        // First request
1485        {
1486            let scope = pool.acquire();
1487            scope.singleton(RequestId(1));
1488            assert!(scope.contains::<RequestId>());
1489        }
1490
1491        // Second request - should reuse the same scope (cleared)
1492        {
1493            let scope = pool.acquire();
1494            // Previous RequestId should be cleared
1495            assert!(!scope.contains::<RequestId>());
1496
1497            scope.singleton(RequestId(2));
1498            let id = scope.get::<RequestId>().unwrap();
1499            assert_eq!(id.0, 2);
1500        }
1501    }
1502
1503    #[test]
1504    fn test_scope_pool_expansion() {
1505        let root = Container::new();
1506        let pool = ScopePool::new(&root, 1);
1507
1508        // Acquire more scopes than pre-allocated
1509        let _s1 = pool.acquire();
1510        let _s2 = pool.acquire(); // Creates new scope
1511
1512        assert_eq!(pool.available_count(), 0);
1513
1514        // Both should work
1515        drop(_s1);
1516        drop(_s2);
1517
1518        // Both return to pool
1519        assert_eq!(pool.available_count(), 2);
1520    }
1521
1522    #[test]
1523    fn test_deep_parent_chain() {
1524        // Test that services can be resolved from grandparent and beyond
1525        #[derive(Clone)]
1526        struct RootService(i32);
1527        #[derive(Clone)]
1528        struct MiddleService(i32);
1529        #[derive(Clone)]
1530        struct LeafService(i32);
1531
1532        // Create 4-level hierarchy: root -> middle1 -> middle2 -> leaf
1533        let root = Container::new();
1534        root.singleton(RootService(1));
1535
1536        let middle1 = root.scope();
1537        middle1.singleton(MiddleService(2));
1538
1539        let middle2 = middle1.scope();
1540        // No service in middle2
1541
1542        let leaf = middle2.scope();
1543        leaf.singleton(LeafService(4));
1544
1545        // Leaf should be able to access all ancestor services
1546        assert!(
1547            leaf.contains::<RootService>(),
1548            "Should find root service in leaf"
1549        );
1550        assert!(
1551            leaf.contains::<MiddleService>(),
1552            "Should find middle service in leaf"
1553        );
1554        assert!(
1555            leaf.contains::<LeafService>(),
1556            "Should find leaf service in leaf"
1557        );
1558
1559        // Verify resolution works
1560        let root_svc = leaf.get::<RootService>().unwrap();
1561        assert_eq!(root_svc.0, 1);
1562
1563        let middle_svc = leaf.get::<MiddleService>().unwrap();
1564        assert_eq!(middle_svc.0, 2);
1565
1566        let leaf_svc = leaf.get::<LeafService>().unwrap();
1567        assert_eq!(leaf_svc.0, 4);
1568
1569        // Middle2 should also access ancestor services
1570        assert!(middle2.contains::<RootService>());
1571        assert!(middle2.contains::<MiddleService>());
1572        assert!(!middle2.contains::<LeafService>()); // Leaf service not in parent
1573    }
1574}