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 keeps the cache compact (a few cache lines) and provides good hit
23/// rates for typical apps.
24const HOT_CACHE_SLOTS: usize = 4;
25
26/// A cached service entry
27///
28/// Phase 13 optimization: Stores pre-computed u64 hash instead of TypeId
29/// to avoid transmute on every comparison.
30struct CacheEntry {
31    /// Pre-computed hash of TypeId (avoids transmute on lookup)
32    type_hash: u64,
33    /// Pointer to the storage this was resolved from (for scope identity)
34    storage_ptr: usize,
35    /// Storage generation at the time of caching (for staleness detection).
36    /// Generations are stamped by `ServiceStorage` on every mutation and are
37    /// globally unique, which also defeats `Arc::as_ptr` ABA reuse.
38    generation: u64,
39    /// The cached service
40    service: Arc<dyn Any + Send + Sync>,
41}
42
43/// Thread-local cache for frequently accessed services.
44///
45/// This provides ~8-10ns speedup for hot services by avoiding DashMap lookups.
46/// Uses a simple direct-mapped cache with TypeId + storage pointer as key.
47struct HotCache {
48    entries: [Option<CacheEntry>; HOT_CACHE_SLOTS],
49}
50
51impl HotCache {
52    const fn new() -> Self {
53        Self {
54            entries: [const { None }; HOT_CACHE_SLOTS],
55        }
56    }
57
58    /// Get a cached service if present for a specific container
59    ///
60    /// Phase 12+13 optimization: Uses UnsafeCell (no RefCell borrow check)
61    /// and pre-computed type_hash (no transmute on lookup).
62    ///
63    /// The entry only hits if its generation matches the storage's current
64    /// generation, so entries cached before a mutation are never returned.
65    #[inline(always)]
66    fn get<T: Send + Sync + 'static>(&self, storage_ptr: usize, generation: u64) -> Option<Arc<T>> {
67        let type_hash = Self::type_hash::<T>();
68        let slot = Self::slot_for_hash(type_hash, storage_ptr);
69
70        if let Some(entry) = &self.entries[slot] {
71            // Phase 13: Compare u64 hash directly (faster than TypeId comparison)
72            if entry.type_hash == type_hash
73                && entry.storage_ptr == storage_ptr
74                && entry.generation == generation
75            {
76                // Cache hit - clone and downcast (unchecked since type_hash matches)
77                // SAFETY: We verified type_hash matches, so the Arc contains type T
78                let arc = entry.service.clone();
79                return Some(unsafe { downcast_arc_unchecked(arc) });
80            }
81        }
82        None
83    }
84
85    /// Insert a service into the cache for a specific container
86    #[inline]
87    fn insert<T: Injectable>(&mut self, storage_ptr: usize, generation: u64, service: Arc<T>) {
88        let type_hash = Self::type_hash::<T>();
89        let slot = Self::slot_for_hash(type_hash, storage_ptr);
90
91        self.entries[slot] = Some(CacheEntry {
92            type_hash,
93            storage_ptr,
94            generation,
95            service: service as Arc<dyn Any + Send + Sync>,
96        });
97    }
98
99    /// Clear the cache (call when container is modified)
100    #[inline]
101    fn clear(&mut self) {
102        self.entries = [const { None }; HOT_CACHE_SLOTS];
103    }
104
105    /// Extract u64 hash from TypeId (computed once per type at compile time via monomorphization)
106    #[inline(always)]
107    fn type_hash<T: 'static>() -> u64 {
108        let type_id = TypeId::of::<T>();
109        // SAFETY: TypeId is #[repr(transparent)] wrapper around u128
110        unsafe { std::mem::transmute_copy(&type_id) }
111    }
112
113    /// Calculate slot index from pre-computed type hash and storage pointer
114    #[inline(always)]
115    fn slot_for_hash(type_hash: u64, storage_ptr: usize) -> usize {
116        // Fast bit mixing: XOR with rotated storage_ptr for good distribution
117        let mixed = type_hash ^ (storage_ptr as u64).rotate_left(32);
118
119        // Use golden ratio multiplication for final mixing (fast & good distribution)
120        let slot = mixed.wrapping_mul(0x9e3779b97f4a7c15);
121
122        (slot as usize) & (HOT_CACHE_SLOTS - 1)
123    }
124}
125
126thread_local! {
127    /// Thread-local hot cache for frequently accessed services
128    ///
129    /// Phase 12 optimization: Uses UnsafeCell instead of RefCell to eliminate
130    /// borrow checking overhead. This is safe because thread_local! guarantees
131    /// single-threaded access.
132    static HOT_CACHE: UnsafeCell<HotCache> = const { UnsafeCell::new(HotCache::new()) };
133}
134
135/// Helper to access the hot cache without RefCell overhead
136///
137/// SAFETY: thread_local! guarantees single-threaded access, so we can use
138/// UnsafeCell without data races. We ensure no aliasing by limiting access
139/// to immutable borrows for reads and brief mutable borrows for writes.
140#[inline(always)]
141fn with_hot_cache<F, R>(f: F) -> R
142where
143    F: FnOnce(&HotCache) -> R,
144{
145    HOT_CACHE.with(|cell| {
146        // SAFETY: thread_local guarantees single-threaded access
147        let cache = unsafe { &*cell.get() };
148        f(cache)
149    })
150}
151
152/// Helper to mutably access the hot cache
153#[inline(always)]
154fn with_hot_cache_mut<F, R>(f: F) -> R
155where
156    F: FnOnce(&mut HotCache) -> R,
157{
158    HOT_CACHE.with(|cell| {
159        // SAFETY: thread_local guarantees single-threaded access
160        let cache = unsafe { &mut *cell.get() };
161        f(cache)
162    })
163}
164
165/// High-performance dependency injection container.
166///
167/// Uses lock-free data structures for maximum concurrent throughput.
168/// Supports hierarchical scopes with full parent chain resolution.
169///
170/// # Examples
171///
172/// ```rust
173/// use dependency_injector::Container;
174///
175/// #[derive(Clone)]
176/// struct MyService { name: String }
177///
178/// let container = Container::new();
179/// container.singleton(MyService { name: "test".into() });
180///
181/// let service = container.get::<MyService>().unwrap();
182/// assert_eq!(service.name, "test");
183/// ```
184#[derive(Clone)]
185pub struct Container {
186    /// Service storage (lock-free)
187    storage: Arc<ServiceStorage>,
188    /// Parent storage - strong reference for fast resolution (Phase 2 optimization)
189    /// This avoids Weak::upgrade() cost on every parent resolution
190    parent_storage: Option<Arc<ServiceStorage>>,
191    /// Lock state - uses AtomicBool for fast lock checking (no contention)
192    locked: Arc<AtomicBool>,
193    /// Scope depth for debugging
194    depth: u32,
195}
196
197impl Container {
198    /// Create a new root container.
199    ///
200    /// # Examples
201    ///
202    /// ```rust
203    /// use dependency_injector::Container;
204    /// let container = Container::new();
205    /// ```
206    #[inline]
207    pub fn new() -> Self {
208        #[cfg(feature = "logging")]
209        debug!(
210            target: "dependency_injector",
211            depth = 0,
212            "Creating new root DI container"
213        );
214
215        Self {
216            storage: Arc::new(ServiceStorage::new()),
217            parent_storage: None,
218            locked: Arc::new(AtomicBool::new(false)),
219            depth: 0,
220        }
221    }
222
223    /// Create a container with pre-allocated capacity.
224    ///
225    /// Use this when you know approximately how many services will be registered.
226    #[inline]
227    pub fn with_capacity(capacity: usize) -> Self {
228        Self {
229            storage: Arc::new(ServiceStorage::with_capacity(capacity)),
230            parent_storage: None,
231            locked: Arc::new(AtomicBool::new(false)),
232            depth: 0,
233        }
234    }
235
236    /// Create a child scope that inherits from this container.
237    ///
238    /// Child scopes can:
239    /// - Access all services from parent scopes
240    /// - Override parent services with local registrations
241    /// - Have their own transient/scoped services
242    ///
243    /// # Examples
244    ///
245    /// ```rust
246    /// use dependency_injector::Container;
247    ///
248    /// #[derive(Clone)]
249    /// struct AppConfig { debug: bool }
250    ///
251    /// #[derive(Clone)]
252    /// struct RequestId(String);
253    ///
254    /// let root = Container::new();
255    /// root.singleton(AppConfig { debug: true });
256    ///
257    /// let request = root.scope();
258    /// request.singleton(RequestId("req-123".into()));
259    ///
260    /// // Request scope can access root config
261    /// assert!(request.contains::<AppConfig>());
262    /// ```
263    #[inline]
264    pub fn scope(&self) -> Self {
265        let child_depth = self.depth + 1;
266
267        #[cfg(feature = "logging")]
268        debug!(
269            target: "dependency_injector",
270            parent_depth = self.depth,
271            child_depth = child_depth,
272            parent_services = self.storage.len(),
273            "Creating child scope from parent container"
274        );
275
276        Self {
277            // Phase 9: Storage now holds parent reference for deep chain resolution
278            storage: Arc::new(ServiceStorage::with_parent(Arc::clone(&self.storage))),
279            parent_storage: Some(Arc::clone(&self.storage)), // Keep for quick parent access
280            locked: Arc::new(AtomicBool::new(false)),
281            depth: child_depth,
282        }
283    }
284
285    /// Alias for `scope()` - creates a child container.
286    #[inline]
287    pub fn create_scope(&self) -> Self {
288        self.scope()
289    }
290
291    // =========================================================================
292    // Registration Methods
293    // =========================================================================
294
295    /// Register a singleton service (eager).
296    ///
297    /// The instance is stored immediately and shared across all resolves.
298    ///
299    /// # Examples
300    ///
301    /// ```rust
302    /// use dependency_injector::Container;
303    ///
304    /// #[derive(Clone)]
305    /// struct Database { url: String }
306    ///
307    /// let container = Container::new();
308    /// container.singleton(Database { url: "postgres://localhost".into() });
309    /// ```
310    #[inline]
311    pub fn singleton<T: Injectable>(&self, instance: T) {
312        self.check_not_locked();
313
314        let type_id = TypeId::of::<T>();
315        #[cfg(feature = "logging")]
316        let type_name = std::any::type_name::<T>();
317
318        #[cfg(feature = "logging")]
319        debug!(
320            target: "dependency_injector",
321            service = type_name,
322            lifetime = "singleton",
323            depth = self.depth,
324            service_count = self.storage.len() + 1,
325            "Registering singleton service"
326        );
327
328        // Phase 2: Use enum-based AnyFactory directly
329        self.storage
330            .insert(type_id, AnyFactory::singleton(instance));
331    }
332
333    /// Register a lazy singleton service.
334    ///
335    /// The factory is called once on first access, then the instance is cached.
336    ///
337    /// # Examples
338    ///
339    /// ```rust
340    /// use dependency_injector::Container;
341    ///
342    /// #[derive(Clone)]
343    /// struct ExpensiveService { data: Vec<u8> }
344    ///
345    /// let container = Container::new();
346    /// container.lazy(|| ExpensiveService {
347    ///     data: vec![0; 1024 * 1024], // Only allocated on first use
348    /// });
349    /// ```
350    #[inline]
351    pub fn lazy<T: Injectable, F>(&self, factory: F)
352    where
353        F: Fn() -> T + Send + Sync + 'static,
354    {
355        self.check_not_locked();
356
357        let type_id = TypeId::of::<T>();
358        #[cfg(feature = "logging")]
359        let type_name = std::any::type_name::<T>();
360
361        #[cfg(feature = "logging")]
362        debug!(
363            target: "dependency_injector",
364            service = type_name,
365            lifetime = "lazy_singleton",
366            depth = self.depth,
367            service_count = self.storage.len() + 1,
368            "Registering lazy singleton service (will be created on first access)"
369        );
370
371        // Phase 2: Use enum-based AnyFactory directly
372        self.storage.insert(type_id, AnyFactory::lazy(factory));
373    }
374
375    /// Register a transient service.
376    ///
377    /// A new instance is created on every resolve.
378    ///
379    /// # Examples
380    ///
381    /// ```rust
382    /// use dependency_injector::Container;
383    /// use std::sync::atomic::{AtomicU64, Ordering};
384    ///
385    /// static COUNTER: AtomicU64 = AtomicU64::new(0);
386    ///
387    /// #[derive(Clone)]
388    /// struct RequestId(u64);
389    ///
390    /// let container = Container::new();
391    /// container.transient(|| RequestId(COUNTER.fetch_add(1, Ordering::SeqCst)));
392    ///
393    /// let id1 = container.get::<RequestId>().unwrap();
394    /// let id2 = container.get::<RequestId>().unwrap();
395    /// assert_ne!(id1.0, id2.0); // Different instances
396    /// ```
397    #[inline]
398    pub fn transient<T: Injectable, F>(&self, factory: F)
399    where
400        F: Fn() -> T + Send + Sync + 'static,
401    {
402        self.check_not_locked();
403
404        let type_id = TypeId::of::<T>();
405        #[cfg(feature = "logging")]
406        let type_name = std::any::type_name::<T>();
407
408        #[cfg(feature = "logging")]
409        debug!(
410            target: "dependency_injector",
411            service = type_name,
412            lifetime = "transient",
413            depth = self.depth,
414            service_count = self.storage.len() + 1,
415            "Registering transient service (new instance on every resolve)"
416        );
417
418        // Phase 2: Use enum-based AnyFactory directly
419        self.storage.insert(type_id, AnyFactory::transient(factory));
420    }
421
422    /// Register using a factory (alias for `lazy`).
423    #[inline]
424    pub fn register_factory<T: Injectable, F>(&self, factory: F)
425    where
426        F: Fn() -> T + Send + Sync + 'static,
427    {
428        self.lazy(factory);
429    }
430
431    /// Register an instance (alias for `singleton`).
432    #[inline]
433    pub fn register<T: Injectable>(&self, instance: T) {
434        self.singleton(instance);
435    }
436
437    /// Register a boxed instance.
438    #[inline]
439    #[allow(clippy::boxed_local)]
440    pub fn register_boxed<T: Injectable>(&self, instance: Box<T>) {
441        self.singleton(*instance);
442    }
443
444    /// Register by TypeId directly (advanced use).
445    #[inline]
446    pub fn register_by_id(&self, type_id: TypeId, instance: Arc<dyn Any + Send + Sync>) {
447        self.check_not_locked();
448
449        // Phase 2: Use the singleton factory with pre-erased Arc directly
450        self.storage.insert(
451            type_id,
452            AnyFactory::Singleton(crate::factory::SingletonFactory { instance }),
453        );
454    }
455
456    // =========================================================================
457    // Resolution Methods
458    // =========================================================================
459
460    /// Resolve a service by type.
461    ///
462    /// Returns `Arc<T>` for zero-copy sharing. Walks the parent chain if
463    /// not found in the current scope.
464    ///
465    /// # Performance
466    ///
467    /// Uses thread-local caching for frequently accessed services (~8ns vs ~19ns).
468    /// The cache is automatically populated on first access.
469    ///
470    /// # Examples
471    ///
472    /// ```rust
473    /// use dependency_injector::Container;
474    ///
475    /// #[derive(Clone)]
476    /// struct MyService;
477    ///
478    /// let container = Container::new();
479    /// container.singleton(MyService);
480    ///
481    /// let service = container.get::<MyService>().unwrap();
482    /// ```
483    #[inline]
484    pub fn get<T: Injectable>(&self) -> Result<Arc<T>> {
485        // Get storage pointer for cache key (unique per container scope)
486        let storage_ptr = Arc::as_ptr(&self.storage) as usize;
487
488        // Current storage generation - entries cached before a mutation won't match it
489        let generation = self.storage.generation();
490
491        // Phase 5+12: Check thread-local hot cache first (UnsafeCell, no RefCell overhead)
492        // Note: Transients won't be in cache, so they'll fall through to get_and_cache
493        if let Some(cached) = with_hot_cache(|cache| cache.get::<T>(storage_ptr, generation)) {
494            #[cfg(feature = "logging")]
495            trace!(
496                target: "dependency_injector",
497                service = std::any::type_name::<T>(),
498                depth = self.depth,
499                location = "hot_cache",
500                "Service resolved from thread-local cache"
501            );
502            return Ok(cached);
503        }
504
505        // Cache miss - resolve normally and cache the result (unless transient)
506        self.get_and_cache::<T>(storage_ptr, generation)
507    }
508
509    /// Internal: Resolve and cache a service
510    ///
511    /// Phase 15 optimization: Fast path for root containers (depth == 0) avoids
512    /// function call overhead to resolve_from_parents when there are no parents.
513    #[inline]
514    fn get_and_cache<T: Injectable>(&self, storage_ptr: usize, generation: u64) -> Result<Arc<T>> {
515        let type_id = TypeId::of::<T>();
516
517        #[cfg(feature = "logging")]
518        let type_name = std::any::type_name::<T>();
519
520        #[cfg(feature = "logging")]
521        trace!(
522            target: "dependency_injector",
523            service = type_name,
524            depth = self.depth,
525            "Resolving service (cache miss)"
526        );
527
528        // Try local storage first (most common case)
529        // Use get_with_transient_flag to avoid second DashMap lookup for is_transient
530        if let Some((service, is_transient)) = self.storage.get_with_transient_flag::<T>() {
531            #[cfg(feature = "logging")]
532            trace!(
533                target: "dependency_injector",
534                service = type_name,
535                depth = self.depth,
536                location = "local",
537                "Service resolved from current scope"
538            );
539
540            // Cache non-transient services (transients create new instances each time)
541            if !is_transient {
542                with_hot_cache_mut(|cache| {
543                    cache.insert(storage_ptr, generation, Arc::clone(&service));
544                });
545            }
546
547            return Ok(service);
548        }
549
550        // Phase 15: Fast path for root containers - no parents to walk
551        if self.depth == 0 {
552            #[cfg(feature = "logging")]
553            debug!(
554                target: "dependency_injector",
555                service = std::any::type_name::<T>(),
556                "Service not found in root container"
557            );
558            return Err(DiError::not_found::<T>());
559        }
560
561        // Walk parent chain (cold path)
562        self.resolve_from_parents::<T>(&type_id)
563    }
564
565    /// Resolve from parent chain (internal)
566    ///
567    /// Phase 9 optimization: Walks the full parent chain via ServiceStorage.parent.
568    /// This allows services to be resolved from any ancestor scope.
569    ///
570    /// Phase 14 optimization: Marked as cold to improve branch prediction in the
571    /// hot path - most resolutions hit the cache and don't need parent traversal.
572    #[cold]
573    fn resolve_from_parents<T: Injectable>(&self, type_id: &TypeId) -> Result<Arc<T>> {
574        #[cfg(feature = "logging")]
575        let type_name = std::any::type_name::<T>();
576
577        #[cfg(feature = "logging")]
578        trace!(
579            target: "dependency_injector",
580            service = type_name,
581            depth = self.depth,
582            "Service not in local scope, walking parent chain"
583        );
584
585        // Walk the full parent chain via storage's parent references
586        let mut current = self.storage.parent();
587        let mut ancestor_depth = self.depth.saturating_sub(1);
588
589        while let Some(storage) = current {
590            if let Some(arc) = storage.resolve(type_id) {
591                // SAFETY: We resolved by TypeId::of::<T>(), so the factory
592                // was registered with the same TypeId and stores type T.
593                let typed: Arc<T> = unsafe { downcast_arc_unchecked(arc) };
594
595                #[cfg(feature = "logging")]
596                trace!(
597                    target: "dependency_injector",
598                    service = type_name,
599                    depth = self.depth,
600                    ancestor_depth = ancestor_depth,
601                    location = "ancestor",
602                    "Service resolved from ancestor scope"
603                );
604
605                // Deliberately NOT hot-cached: an entry would be stamped with
606                // the child's generation, which ancestor mutations never bump,
607                // so a re-registration or clear() in the parent could serve a
608                // stale value from this thread's cache indefinitely. Only
609                // same-storage resolutions are cached (see `get_and_cache`);
610                // this path is #[cold] and parent-chain walks stay correct.
611                return Ok(typed);
612            }
613            current = storage.parent();
614            ancestor_depth = ancestor_depth.saturating_sub(1);
615        }
616
617        #[cfg(feature = "logging")]
618        debug!(
619            target: "dependency_injector",
620            service = type_name,
621            depth = self.depth,
622            "Service not found in container or parent chain"
623        );
624
625        Err(DiError::not_found::<T>())
626    }
627
628    /// Clear the thread-local hot cache.
629    ///
630    /// The cache is automatically invalidated whenever this container is
631    /// mutated (services registered, removed, or cleared), so calling this
632    /// manually is rarely necessary. It remains available for explicit
633    /// control, e.g. to release the cached `Arc`s held by the current thread.
634    ///
635    /// Note: This clears the calling thread's entire hot cache, including
636    /// entries cached for other containers.
637    #[inline]
638    pub fn clear_cache(&self) {
639        with_hot_cache_mut(HotCache::clear);
640    }
641
642    /// Pre-warm the thread-local cache with a specific service type.
643    ///
644    /// This can be useful at the start of request handling to ensure
645    /// hot services are already in the cache.
646    ///
647    /// # Example
648    ///
649    /// ```rust
650    /// use dependency_injector::Container;
651    ///
652    /// #[derive(Clone)]
653    /// struct Database;
654    ///
655    /// let container = Container::new();
656    /// container.singleton(Database);
657    ///
658    /// // Pre-warm cache for hot services
659    /// container.warm_cache::<Database>();
660    /// ```
661    #[inline]
662    pub fn warm_cache<T: Injectable>(&self) {
663        // Simply resolve the service to populate the cache
664        let _ = self.get::<T>();
665    }
666
667    /// Alias for `get` - resolve a service.
668    #[inline]
669    pub fn resolve<T: Injectable>(&self) -> Result<Arc<T>> {
670        self.get::<T>()
671    }
672
673    /// Try to resolve, returning None if not found.
674    ///
675    /// # Examples
676    ///
677    /// ```rust
678    /// use dependency_injector::Container;
679    ///
680    /// #[derive(Clone)]
681    /// struct OptionalService;
682    ///
683    /// let container = Container::new();
684    /// assert!(container.try_get::<OptionalService>().is_none());
685    /// ```
686    #[inline]
687    pub fn try_get<T: Injectable>(&self) -> Option<Arc<T>> {
688        self.get::<T>().ok()
689    }
690
691    /// Alias for `try_get`.
692    #[inline]
693    pub fn try_resolve<T: Injectable>(&self) -> Option<Arc<T>> {
694        self.try_get::<T>()
695    }
696
697    // =========================================================================
698    // Query Methods
699    // =========================================================================
700
701    /// Check if a service is registered.
702    ///
703    /// Checks both current scope and parent scopes.
704    #[inline]
705    pub fn contains<T: Injectable>(&self) -> bool {
706        let type_id = TypeId::of::<T>();
707        self.contains_type_id(&type_id)
708    }
709
710    /// Alias for `contains`.
711    #[inline]
712    pub fn has<T: Injectable>(&self) -> bool {
713        self.contains::<T>()
714    }
715
716    /// Check by TypeId
717    /// Phase 9 optimization: Uses storage's parent chain for deep hierarchy support
718    fn contains_type_id(&self, type_id: &TypeId) -> bool {
719        // Check local storage and full parent chain
720        self.storage.contains_in_chain(type_id)
721    }
722
723    /// Get the number of services in this scope (not including parents).
724    #[inline]
725    pub fn len(&self) -> usize {
726        self.storage.len()
727    }
728
729    /// Check if this scope is empty.
730    #[inline]
731    pub fn is_empty(&self) -> bool {
732        self.storage.is_empty()
733    }
734
735    /// Get all registered TypeIds in this scope.
736    pub fn registered_types(&self) -> Vec<TypeId> {
737        self.storage.type_ids()
738    }
739
740    /// Get the scope depth (0 = root).
741    #[inline]
742    pub fn depth(&self) -> u32 {
743        self.depth
744    }
745
746    /// Format a human-readable summary of every registration visible to this
747    /// container, for logging when a resolution unexpectedly fails.
748    ///
749    /// The summary lists the scope depth, the number of services registered
750    /// in each scope of the parent chain, and the [`TypeId`] of every
751    /// registration. The container stores services by `TypeId` only (type
752    /// names are not retained), so pair this output with the `type_name`
753    /// carried by [`DiError::NotFound`] when diagnosing a failed resolve.
754    ///
755    /// # Examples
756    ///
757    /// ```rust
758    /// use dependency_injector::Container;
759    ///
760    /// #[derive(Clone)]
761    /// struct MyService;
762    ///
763    /// let root = Container::new();
764    /// root.singleton(MyService);
765    ///
766    /// let scope = root.scope();
767    /// if scope.get::<String>().is_err() {
768    ///     eprintln!("{}", scope.debug_registrations());
769    /// }
770    /// ```
771    pub fn debug_registrations(&self) -> String {
772        use std::fmt::Write;
773
774        let mut out = format!("Container registrations (scope depth {}):\n", self.depth);
775        let mut total = 0usize;
776        let mut current = Some(&self.storage);
777        let mut depth = self.depth;
778        let mut is_local = true;
779
780        while let Some(storage) = current {
781            let type_ids = storage.type_ids();
782            total += type_ids.len();
783            let location = if is_local {
784                "current scope"
785            } else {
786                "ancestor"
787            };
788            let _ = writeln!(
789                out,
790                "  {location} (depth {depth}): {} registered: {type_ids:?}",
791                type_ids.len()
792            );
793            current = storage.parent();
794            depth = depth.saturating_sub(1);
795            is_local = false;
796        }
797
798        let _ = write!(out, "  total: {total} service(s) in scope chain");
799        out
800    }
801
802    // =========================================================================
803    // Lifecycle Methods
804    // =========================================================================
805
806    /// Lock the container to prevent further registrations.
807    ///
808    /// Useful for ensuring no services are registered after app initialization.
809    #[inline]
810    pub fn lock(&self) {
811        self.locked.store(true, Ordering::Release);
812
813        #[cfg(feature = "logging")]
814        debug!(
815            target: "dependency_injector",
816            depth = self.depth,
817            service_count = self.storage.len(),
818            "Container locked - no further registrations allowed"
819        );
820    }
821
822    /// Check if the container is locked.
823    #[inline]
824    pub fn is_locked(&self) -> bool {
825        self.locked.load(Ordering::Acquire)
826    }
827
828    /// Freeze the container into an immutable, perfectly-hashed storage.
829    ///
830    /// This creates a `FrozenStorage` that uses minimal perfect hashing for
831    /// O(1) lookups without hash collisions, providing ~5ns faster resolution.
832    ///
833    /// Note: This also locks the container to prevent further registrations.
834    ///
835    /// # Example
836    ///
837    /// ```rust,ignore
838    /// use dependency_injector::Container;
839    ///
840    /// let container = Container::new();
841    /// container.singleton(MyService { ... });
842    ///
843    /// let frozen = container.freeze();
844    /// // Use frozen.resolve(&type_id) for faster lookups
845    /// ```
846    #[cfg(feature = "perfect-hash")]
847    #[inline]
848    pub fn freeze(&self) -> crate::storage::FrozenStorage {
849        self.lock();
850        crate::storage::FrozenStorage::from_storage(&self.storage)
851    }
852
853    /// Clear all services from this scope.
854    ///
855    /// Does not affect parent scopes.
856    #[inline]
857    pub fn clear(&self) {
858        #[cfg(feature = "logging")]
859        let count = self.storage.len();
860        self.storage.clear();
861
862        #[cfg(feature = "logging")]
863        debug!(
864            target: "dependency_injector",
865            depth = self.depth,
866            services_removed = count,
867            "Container cleared - all services removed from this scope"
868        );
869    }
870
871    /// Remove a service registration from this scope.
872    ///
873    /// Returns `true` if a service of type `T` was registered in this scope
874    /// and has been removed, `false` otherwise. Does not affect parent scopes.
875    /// Like [`clear`](Self::clear), removal is permitted on a locked
876    /// container (locking prevents new registrations, not removal).
877    ///
878    /// # Examples
879    ///
880    /// ```rust
881    /// use dependency_injector::Container;
882    ///
883    /// #[derive(Clone)]
884    /// struct MyService;
885    ///
886    /// let container = Container::new();
887    /// container.singleton(MyService);
888    ///
889    /// assert!(container.remove::<MyService>());
890    /// assert!(!container.contains::<MyService>());
891    /// assert!(!container.remove::<MyService>()); // Already removed
892    /// ```
893    #[inline]
894    pub fn remove<T: Injectable>(&self) -> bool {
895        let removed = self.storage.remove(&TypeId::of::<T>());
896        if removed {
897            #[cfg(feature = "logging")]
898            debug!(
899                target: "dependency_injector",
900                service = std::any::type_name::<T>(),
901                depth = self.depth,
902                "Service registration removed from this scope"
903            );
904        }
905        removed
906    }
907
908    /// Panic if locked (internal helper).
909    /// Uses relaxed ordering for fast path - we only need eventual consistency
910    /// since registration is not a hot path and locking is rare.
911    #[inline]
912    fn check_not_locked(&self) {
913        if self.locked.load(Ordering::Relaxed) {
914            panic!("Cannot register services: container is locked");
915        }
916    }
917
918    // =========================================================================
919    // Batch Registration (Phase 3)
920    // =========================================================================
921
922    /// Register multiple services in a single batch operation.
923    ///
924    /// This is more efficient than individual registrations when registering
925    /// many services at once, as it:
926    /// - Performs a single lock check at the start
927    /// - Minimizes per-call overhead
928    ///
929    /// # Examples
930    ///
931    /// ```rust
932    /// use dependency_injector::Container;
933    ///
934    /// #[derive(Clone)]
935    /// struct Database { url: String }
936    /// #[derive(Clone)]
937    /// struct Cache { size: usize }
938    /// #[derive(Clone)]
939    /// struct Logger { level: String }
940    ///
941    /// let container = Container::new();
942    /// container.batch(|batch| {
943    ///     batch.singleton(Database { url: "postgres://localhost".into() });
944    ///     batch.singleton(Cache { size: 1024 });
945    ///     batch.singleton(Logger { level: "info".into() });
946    /// });
947    ///
948    /// assert!(container.contains::<Database>());
949    /// assert!(container.contains::<Cache>());
950    /// assert!(container.contains::<Logger>());
951    /// ```
952    ///
953    /// Note: For maximum performance with many services, prefer the builder API:
954    /// ```rust
955    /// use dependency_injector::Container;
956    ///
957    /// #[derive(Clone)]
958    /// struct A;
959    /// #[derive(Clone)]
960    /// struct B;
961    ///
962    /// let container = Container::new();
963    /// container.register_batch()
964    ///     .singleton(A)
965    ///     .singleton(B)
966    ///     .done();
967    /// ```
968    #[inline]
969    pub fn batch<F>(&self, f: F)
970    where
971        F: FnOnce(BatchRegistrar<'_>),
972    {
973        self.check_not_locked();
974
975        #[cfg(feature = "logging")]
976        let start_count = self.storage.len();
977
978        // Create a zero-cost batch registrar that wraps the storage
979        f(BatchRegistrar {
980            storage: &self.storage,
981        });
982
983        #[cfg(feature = "logging")]
984        {
985            let end_count = self.storage.len();
986            debug!(
987                target: "dependency_injector",
988                depth = self.depth,
989                services_registered = end_count - start_count,
990                "Batch registration completed"
991            );
992        }
993    }
994
995    /// Start a fluent batch registration.
996    ///
997    /// This is faster than the closure-based `batch()` for many services
998    /// because it avoids closure overhead.
999    ///
1000    /// # Example
1001    ///
1002    /// ```rust
1003    /// use dependency_injector::Container;
1004    ///
1005    /// #[derive(Clone)]
1006    /// struct Database { url: String }
1007    /// #[derive(Clone)]
1008    /// struct Cache { size: usize }
1009    ///
1010    /// let container = Container::new();
1011    /// container.register_batch()
1012    ///     .singleton(Database { url: "postgres://localhost".into() })
1013    ///     .singleton(Cache { size: 1024 })
1014    ///     .done();
1015    ///
1016    /// assert!(container.contains::<Database>());
1017    /// assert!(container.contains::<Cache>());
1018    /// ```
1019    #[inline]
1020    pub fn register_batch(&self) -> BatchBuilder<'_> {
1021        self.check_not_locked();
1022        BatchBuilder {
1023            storage: &self.storage,
1024            #[cfg(feature = "logging")]
1025            count: 0,
1026        }
1027    }
1028}
1029
1030/// Fluent batch registration builder.
1031///
1032/// Provides a chainable API for registering multiple services without closure overhead.
1033pub struct BatchBuilder<'a> {
1034    storage: &'a ServiceStorage,
1035    #[cfg(feature = "logging")]
1036    count: usize,
1037}
1038
1039impl<'a> BatchBuilder<'a> {
1040    /// Register a singleton and continue the chain
1041    #[inline]
1042    pub fn singleton<T: Injectable>(self, instance: T) -> Self {
1043        self.storage
1044            .insert(TypeId::of::<T>(), AnyFactory::singleton(instance));
1045        Self {
1046            storage: self.storage,
1047            #[cfg(feature = "logging")]
1048            count: self.count + 1,
1049        }
1050    }
1051
1052    /// Register a lazy singleton and continue the chain
1053    #[inline]
1054    pub fn lazy<T: Injectable, F>(self, factory: F) -> Self
1055    where
1056        F: Fn() -> T + Send + Sync + 'static,
1057    {
1058        self.storage
1059            .insert(TypeId::of::<T>(), AnyFactory::lazy(factory));
1060        Self {
1061            storage: self.storage,
1062            #[cfg(feature = "logging")]
1063            count: self.count + 1,
1064        }
1065    }
1066
1067    /// Register a transient and continue the chain
1068    #[inline]
1069    pub fn transient<T: Injectable, F>(self, factory: F) -> Self
1070    where
1071        F: Fn() -> T + Send + Sync + 'static,
1072    {
1073        self.storage
1074            .insert(TypeId::of::<T>(), AnyFactory::transient(factory));
1075        Self {
1076            storage: self.storage,
1077            #[cfg(feature = "logging")]
1078            count: self.count + 1,
1079        }
1080    }
1081
1082    /// Finish the batch registration
1083    #[inline]
1084    pub fn done(self) {
1085        #[cfg(feature = "logging")]
1086        debug!(
1087            target: "dependency_injector",
1088            services_registered = self.count,
1089            "Batch registration completed"
1090        );
1091    }
1092}
1093
1094/// Batch registrar for closure-based bulk registration.
1095///
1096/// A zero-cost wrapper that provides direct storage access.
1097/// The lock check is done once in `Container::batch()`.
1098#[repr(transparent)]
1099pub struct BatchRegistrar<'a> {
1100    storage: &'a ServiceStorage,
1101}
1102
1103impl<'a> BatchRegistrar<'a> {
1104    /// Register a singleton service (inserted immediately)
1105    #[inline]
1106    pub fn singleton<T: Injectable>(&self, instance: T) {
1107        self.storage
1108            .insert(TypeId::of::<T>(), AnyFactory::singleton(instance));
1109    }
1110
1111    /// Register a lazy singleton service (inserted immediately)
1112    #[inline]
1113    pub fn lazy<T: Injectable, F>(&self, factory: F)
1114    where
1115        F: Fn() -> T + Send + Sync + 'static,
1116    {
1117        self.storage
1118            .insert(TypeId::of::<T>(), AnyFactory::lazy(factory));
1119    }
1120
1121    /// Register a transient service (inserted immediately)
1122    #[inline]
1123    pub fn transient<T: Injectable, F>(&self, factory: F)
1124    where
1125        F: Fn() -> T + Send + Sync + 'static,
1126    {
1127        self.storage
1128            .insert(TypeId::of::<T>(), AnyFactory::transient(factory));
1129    }
1130}
1131
1132// =============================================================================
1133// Scope Pooling (Phase 6 optimization)
1134// =============================================================================
1135
1136use std::sync::Mutex;
1137
1138/// A pool of pre-allocated scopes for high-throughput scenarios.
1139///
1140/// Creating a scope involves allocating a DashMap (~134ns). For web servers
1141/// handling thousands of requests per second, this adds up. ScopePool pre-allocates
1142/// scopes and reuses them, reducing per-request overhead to near-zero.
1143///
1144/// # Example
1145///
1146/// ```rust
1147/// use dependency_injector::{Container, ScopePool};
1148///
1149/// #[derive(Clone)]
1150/// struct AppConfig { name: String }
1151///
1152/// #[derive(Clone)]
1153/// struct RequestId(String);
1154///
1155/// // Create root container with app-wide services
1156/// let root = Container::new();
1157/// root.singleton(AppConfig { name: "MyApp".into() });
1158///
1159/// // Create a pool of reusable scopes (pre-allocates 4 scopes)
1160/// let pool = ScopePool::new(&root, 4);
1161///
1162/// // In request handler: acquire a pooled scope
1163/// {
1164///     let scope = pool.acquire();
1165///     scope.singleton(RequestId("req-123".into()));
1166///
1167///     // Can access parent services
1168///     assert!(scope.contains::<AppConfig>());
1169///     assert!(scope.contains::<RequestId>());
1170///
1171///     // Scope automatically released when dropped
1172/// }
1173///
1174/// // Next request reuses the same scope allocation
1175/// {
1176///     let scope = pool.acquire();
1177///     // Previous RequestId is cleared, fresh scope
1178///     assert!(!scope.contains::<RequestId>());
1179/// }
1180/// ```
1181///
1182/// # Performance
1183///
1184/// - First acquisition: ~134ns (creates new scope if pool is empty)
1185/// - Subsequent acquisitions: ~20ns (reuses pooled scope)
1186/// - Release: ~10ns (clears and returns to pool)
1187pub struct ScopePool {
1188    /// Parent storage to create scopes from
1189    parent_storage: Arc<ServiceStorage>,
1190    /// Pool of available scopes (storage + lock state pairs)
1191    available: Mutex<Vec<ScopeSlot>>,
1192    /// Parent depth for child scope depth calculation
1193    parent_depth: u32,
1194}
1195
1196/// A reusable scope slot containing pre-allocated storage and lock state
1197struct ScopeSlot {
1198    /// Pre-allocated storage with parent reference
1199    storage: Arc<ServiceStorage>,
1200    locked: Arc<AtomicBool>,
1201}
1202
1203impl ScopePool {
1204    /// Create a new scope pool with pre-allocated capacity.
1205    ///
1206    /// # Arguments
1207    ///
1208    /// * `parent` - The parent container that scopes will inherit from
1209    /// * `capacity` - Number of scopes to pre-allocate
1210    ///
1211    /// # Example
1212    ///
1213    /// ```rust
1214    /// use dependency_injector::{Container, ScopePool};
1215    ///
1216    /// let root = Container::new();
1217    /// // Pre-allocate 8 scopes for concurrent request handling
1218    /// let pool = ScopePool::new(&root, 8);
1219    /// ```
1220    pub fn new(parent: &Container, capacity: usize) -> Self {
1221        let mut available = Vec::with_capacity(capacity);
1222
1223        // Pre-allocate storage with parent reference and lock states
1224        for _ in 0..capacity {
1225            available.push(ScopeSlot {
1226                storage: Arc::new(ServiceStorage::with_parent(Arc::clone(&parent.storage))),
1227                locked: Arc::new(AtomicBool::new(false)),
1228            });
1229        }
1230
1231        #[cfg(feature = "logging")]
1232        debug!(
1233            target: "dependency_injector",
1234            capacity = capacity,
1235            parent_depth = parent.depth,
1236            "Created scope pool with pre-allocated scopes"
1237        );
1238
1239        Self {
1240            parent_storage: Arc::clone(&parent.storage),
1241            available: Mutex::new(available),
1242            parent_depth: parent.depth,
1243        }
1244    }
1245
1246    /// Acquire a scope from the pool.
1247    ///
1248    /// Returns a `PooledScope` that automatically returns to the pool when dropped.
1249    /// If the pool is empty, creates a new scope.
1250    ///
1251    /// # Example
1252    ///
1253    /// ```rust
1254    /// use dependency_injector::{Container, ScopePool};
1255    ///
1256    /// #[derive(Clone)]
1257    /// struct RequestData { id: u64 }
1258    ///
1259    /// let root = Container::new();
1260    /// let pool = ScopePool::new(&root, 4);
1261    ///
1262    /// let scope = pool.acquire();
1263    /// scope.singleton(RequestData { id: 123 });
1264    /// let data = scope.get::<RequestData>().unwrap();
1265    /// assert_eq!(data.id, 123);
1266    /// ```
1267    #[inline]
1268    pub fn acquire(&self) -> PooledScope<'_> {
1269        let slot = self.available.lock().unwrap().pop();
1270
1271        let (storage, locked) = match slot {
1272            Some(slot) => {
1273                #[cfg(feature = "logging")]
1274                trace!(
1275                    target: "dependency_injector",
1276                    "Acquired scope from pool (reusing storage)"
1277                );
1278                (slot.storage, slot.locked)
1279            }
1280            None => {
1281                #[cfg(feature = "logging")]
1282                trace!(
1283                    target: "dependency_injector",
1284                    "Pool empty, creating new scope"
1285                );
1286                (
1287                    Arc::new(ServiceStorage::with_parent(Arc::clone(
1288                        &self.parent_storage,
1289                    ))),
1290                    Arc::new(AtomicBool::new(false)),
1291                )
1292            }
1293        };
1294
1295        let container = Container {
1296            storage,
1297            parent_storage: Some(Arc::clone(&self.parent_storage)),
1298            locked,
1299            depth: self.parent_depth + 1,
1300        };
1301
1302        PooledScope {
1303            container: Some(container),
1304            pool: self,
1305        }
1306    }
1307
1308    /// Return a scope to the pool (internal use).
1309    #[inline]
1310    fn release(&self, container: Container) {
1311        // Clear storage for reuse (parent reference is preserved). The clear
1312        // stamps a fresh generation, invalidating hot cache entries for this
1313        // scope (including lingering clones of the container).
1314        container.storage.clear();
1315        // Reset lock state
1316        container.locked.store(false, Ordering::Relaxed);
1317
1318        // Return to pool
1319        self.available.lock().unwrap().push(ScopeSlot {
1320            storage: container.storage,
1321            locked: container.locked,
1322        });
1323
1324        #[cfg(feature = "logging")]
1325        trace!(
1326            target: "dependency_injector",
1327            "Released scope back to pool"
1328        );
1329    }
1330
1331    /// Get the current number of available scopes in the pool.
1332    #[inline]
1333    pub fn available_count(&self) -> usize {
1334        self.available.lock().unwrap().len()
1335    }
1336}
1337
1338/// A scope acquired from a pool that automatically returns when dropped.
1339///
1340/// This provides RAII-style management of pooled scopes, ensuring they're
1341/// always returned to the pool even if the code panics.
1342pub struct PooledScope<'a> {
1343    container: Option<Container>,
1344    pool: &'a ScopePool,
1345}
1346
1347impl PooledScope<'_> {
1348    /// Get a reference to the underlying container.
1349    #[inline]
1350    pub fn container(&self) -> &Container {
1351        self.container.as_ref().unwrap()
1352    }
1353}
1354
1355impl std::ops::Deref for PooledScope<'_> {
1356    type Target = Container;
1357
1358    #[inline]
1359    fn deref(&self) -> &Self::Target {
1360        self.container.as_ref().unwrap()
1361    }
1362}
1363
1364impl Drop for PooledScope<'_> {
1365    fn drop(&mut self) {
1366        if let Some(container) = self.container.take() {
1367            self.pool.release(container);
1368        }
1369    }
1370}
1371
1372impl Default for Container {
1373    fn default() -> Self {
1374        Self::new()
1375    }
1376}
1377
1378impl std::fmt::Debug for Container {
1379    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1380        f.debug_struct("Container")
1381            .field("service_count", &self.len())
1382            .field("depth", &self.depth)
1383            .field("has_parent", &self.parent_storage.is_some())
1384            .field("locked", &self.is_locked())
1385            .finish_non_exhaustive()
1386    }
1387}
1388
1389// =========================================================================
1390// Thread Safety
1391// =========================================================================
1392
1393// Container is Send + Sync because:
1394// - storage is Arc<ServiceStorage>, which uses DashMap (thread-safe)
1395// - parent_storage is Option<Arc<ServiceStorage>> (Send + Sync)
1396// - locked is Arc<AtomicBool> (Send + Sync)
1397// - depth is a plain u32 (trivially Send + Sync)
1398unsafe impl Send for Container {}
1399unsafe impl Sync for Container {}
1400
1401#[cfg(test)]
1402mod tests {
1403    use super::*;
1404
1405    #[derive(Clone)]
1406    struct TestService {
1407        value: String,
1408    }
1409
1410    #[allow(dead_code)]
1411    #[derive(Clone)]
1412    struct AnotherService {
1413        name: String,
1414    }
1415
1416    #[test]
1417    fn test_singleton() {
1418        let container = Container::new();
1419        container.singleton(TestService {
1420            value: "test".into(),
1421        });
1422
1423        let s1 = container.get::<TestService>().unwrap();
1424        let s2 = container.get::<TestService>().unwrap();
1425
1426        assert_eq!(s1.value, "test");
1427        assert!(Arc::ptr_eq(&s1, &s2));
1428    }
1429
1430    #[test]
1431    fn test_lazy() {
1432        use std::sync::atomic::{AtomicBool, Ordering};
1433
1434        static CREATED: AtomicBool = AtomicBool::new(false);
1435
1436        let container = Container::new();
1437        container.lazy(|| {
1438            CREATED.store(true, Ordering::SeqCst);
1439            TestService {
1440                value: "lazy".into(),
1441            }
1442        });
1443
1444        assert!(!CREATED.load(Ordering::SeqCst));
1445
1446        let s = container.get::<TestService>().unwrap();
1447        assert!(CREATED.load(Ordering::SeqCst));
1448        assert_eq!(s.value, "lazy");
1449    }
1450
1451    #[test]
1452    fn test_transient() {
1453        use std::sync::atomic::{AtomicU32, Ordering};
1454
1455        static COUNTER: AtomicU32 = AtomicU32::new(0);
1456
1457        #[derive(Clone)]
1458        struct Counter(u32);
1459
1460        let container = Container::new();
1461        container.transient(|| Counter(COUNTER.fetch_add(1, Ordering::SeqCst)));
1462
1463        let c1 = container.get::<Counter>().unwrap();
1464        let c2 = container.get::<Counter>().unwrap();
1465
1466        assert_ne!(c1.0, c2.0);
1467    }
1468
1469    #[test]
1470    fn test_scope_inheritance() {
1471        let root = Container::new();
1472        root.singleton(TestService {
1473            value: "root".into(),
1474        });
1475
1476        let child = root.scope();
1477        child.singleton(AnotherService {
1478            name: "child".into(),
1479        });
1480
1481        // Child sees both
1482        assert!(child.contains::<TestService>());
1483        assert!(child.contains::<AnotherService>());
1484
1485        // Root only sees its own
1486        assert!(root.contains::<TestService>());
1487        assert!(!root.contains::<AnotherService>());
1488    }
1489
1490    #[test]
1491    fn test_scope_override() {
1492        let root = Container::new();
1493        root.singleton(TestService {
1494            value: "root".into(),
1495        });
1496
1497        let child = root.scope();
1498        child.singleton(TestService {
1499            value: "child".into(),
1500        });
1501
1502        let root_service = root.get::<TestService>().unwrap();
1503        let child_service = child.get::<TestService>().unwrap();
1504
1505        assert_eq!(root_service.value, "root");
1506        assert_eq!(child_service.value, "child");
1507    }
1508
1509    #[test]
1510    fn test_not_found() {
1511        let container = Container::new();
1512        let result = container.get::<TestService>();
1513        assert!(result.is_err());
1514    }
1515
1516    #[test]
1517    fn test_not_found_message_names_type_and_hints_at_diagnostics() {
1518        let container = Container::new();
1519
1520        // Root fast path (get_and_cache)
1521        let message = container.get::<TestService>().err().unwrap().to_string();
1522        assert!(message.contains("TestService"));
1523        assert!(message.contains("different scope"));
1524        assert!(message.contains("Container::debug_registrations()"));
1525
1526        // Parent-chain path (resolve_from_parents) produces the same message
1527        let scope = container.scope();
1528        let scoped_message = scope.get::<TestService>().err().unwrap().to_string();
1529        assert_eq!(message, scoped_message);
1530    }
1531
1532    #[test]
1533    fn test_debug_registrations_reflects_count_and_depth() {
1534        let root = Container::new();
1535        root.singleton(TestService {
1536            value: "root".into(),
1537        });
1538
1539        let child = root.scope();
1540        child.singleton(AnotherService {
1541            name: "child".into(),
1542        });
1543
1544        let summary = child.debug_registrations();
1545        assert!(summary.contains("scope depth 1"));
1546        assert!(summary.contains("current scope (depth 1): 1 registered"));
1547        assert!(summary.contains("ancestor (depth 0): 1 registered"));
1548        assert!(summary.contains("total: 2 service(s) in scope chain"));
1549
1550        // The root's summary only covers its own scope
1551        let root_summary = root.debug_registrations();
1552        assert!(root_summary.contains("scope depth 0"));
1553        assert!(root_summary.contains("current scope (depth 0): 1 registered"));
1554        assert!(!root_summary.contains("ancestor"));
1555        assert!(root_summary.contains("total: 1 service(s) in scope chain"));
1556    }
1557
1558    #[test]
1559    fn test_debug_registrations_empty_container() {
1560        let container = Container::new();
1561        let summary = container.debug_registrations();
1562        assert!(summary.contains("current scope (depth 0): 0 registered"));
1563        assert!(summary.contains("total: 0 service(s) in scope chain"));
1564    }
1565
1566    #[test]
1567    fn test_debug_registrations_lists_type_ids() {
1568        let container = Container::new();
1569        container.singleton(TestService {
1570            value: "listed".into(),
1571        });
1572
1573        // The TypeId debug representation of the registered service appears
1574        let expected = format!("{:?}", TypeId::of::<TestService>());
1575        let summary = container.debug_registrations();
1576        assert!(summary.contains(&expected));
1577    }
1578
1579    #[test]
1580    fn test_lock() {
1581        let container = Container::new();
1582        assert!(!container.is_locked());
1583
1584        container.lock();
1585        assert!(container.is_locked());
1586    }
1587
1588    #[test]
1589    #[should_panic(expected = "Cannot register services: container is locked")]
1590    fn test_register_after_lock() {
1591        let container = Container::new();
1592        container.lock();
1593        container.singleton(TestService {
1594            value: "fail".into(),
1595        });
1596    }
1597
1598    #[test]
1599    fn test_batch_registration() {
1600        #[derive(Clone)]
1601        struct ServiceA(i32);
1602        #[allow(dead_code)]
1603        #[derive(Clone)]
1604        struct ServiceB(String);
1605
1606        let container = Container::new();
1607        container.batch(|batch| {
1608            batch.singleton(ServiceA(42));
1609            batch.singleton(ServiceB("test".into()));
1610            batch.lazy(|| TestService {
1611                value: "lazy".into(),
1612            });
1613        });
1614
1615        assert!(container.contains::<ServiceA>());
1616        assert!(container.contains::<ServiceB>());
1617        assert!(container.contains::<TestService>());
1618
1619        let a = container.get::<ServiceA>().unwrap();
1620        assert_eq!(a.0, 42);
1621    }
1622
1623    #[test]
1624    fn test_scope_pool_basic() {
1625        #[derive(Clone)]
1626        struct RequestId(u64);
1627
1628        let root = Container::new();
1629        root.singleton(TestService {
1630            value: "root".into(),
1631        });
1632
1633        // Create pool with 2 pre-allocated scopes
1634        let pool = ScopePool::new(&root, 2);
1635        assert_eq!(pool.available_count(), 2);
1636
1637        // Acquire a scope
1638        {
1639            let scope = pool.acquire();
1640            assert_eq!(pool.available_count(), 1);
1641
1642            // Can access parent services
1643            assert!(scope.contains::<TestService>());
1644
1645            // Register request-specific service
1646            scope.singleton(RequestId(123));
1647            assert!(scope.contains::<RequestId>());
1648
1649            let id = scope.get::<RequestId>().unwrap();
1650            assert_eq!(id.0, 123);
1651        }
1652        // Scope released back to pool
1653        assert_eq!(pool.available_count(), 2);
1654    }
1655
1656    #[test]
1657    fn test_scope_pool_reuse() {
1658        #[derive(Clone)]
1659        struct RequestId(u64);
1660
1661        let root = Container::new();
1662        let pool = ScopePool::new(&root, 1);
1663
1664        // First request
1665        {
1666            let scope = pool.acquire();
1667            scope.singleton(RequestId(1));
1668            assert!(scope.contains::<RequestId>());
1669        }
1670
1671        // Second request - should reuse the same scope (cleared)
1672        {
1673            let scope = pool.acquire();
1674            // Previous RequestId should be cleared
1675            assert!(!scope.contains::<RequestId>());
1676
1677            scope.singleton(RequestId(2));
1678            let id = scope.get::<RequestId>().unwrap();
1679            assert_eq!(id.0, 2);
1680        }
1681    }
1682
1683    #[test]
1684    fn test_scope_pool_expansion() {
1685        let root = Container::new();
1686        let pool = ScopePool::new(&root, 1);
1687
1688        // Acquire more scopes than pre-allocated
1689        let _s1 = pool.acquire();
1690        let _s2 = pool.acquire(); // Creates new scope
1691
1692        assert_eq!(pool.available_count(), 0);
1693
1694        // Both should work
1695        drop(_s1);
1696        drop(_s2);
1697
1698        // Both return to pool
1699        assert_eq!(pool.available_count(), 2);
1700    }
1701
1702    #[test]
1703    fn test_deep_parent_chain() {
1704        // Test that services can be resolved from grandparent and beyond
1705        #[derive(Clone)]
1706        struct RootService(i32);
1707        #[derive(Clone)]
1708        struct MiddleService(i32);
1709        #[derive(Clone)]
1710        struct LeafService(i32);
1711
1712        // Create 4-level hierarchy: root -> middle1 -> middle2 -> leaf
1713        let root = Container::new();
1714        root.singleton(RootService(1));
1715
1716        let middle1 = root.scope();
1717        middle1.singleton(MiddleService(2));
1718
1719        let middle2 = middle1.scope();
1720        // No service in middle2
1721
1722        let leaf = middle2.scope();
1723        leaf.singleton(LeafService(4));
1724
1725        // Leaf should be able to access all ancestor services
1726        assert!(
1727            leaf.contains::<RootService>(),
1728            "Should find root service in leaf"
1729        );
1730        assert!(
1731            leaf.contains::<MiddleService>(),
1732            "Should find middle service in leaf"
1733        );
1734        assert!(
1735            leaf.contains::<LeafService>(),
1736            "Should find leaf service in leaf"
1737        );
1738
1739        // Verify resolution works
1740        let root_svc = leaf.get::<RootService>().unwrap();
1741        assert_eq!(root_svc.0, 1);
1742
1743        let middle_svc = leaf.get::<MiddleService>().unwrap();
1744        assert_eq!(middle_svc.0, 2);
1745
1746        let leaf_svc = leaf.get::<LeafService>().unwrap();
1747        assert_eq!(leaf_svc.0, 4);
1748
1749        // Middle2 should also access ancestor services
1750        assert!(middle2.contains::<RootService>());
1751        assert!(middle2.contains::<MiddleService>());
1752        assert!(!middle2.contains::<LeafService>()); // Leaf service not in parent
1753    }
1754
1755    #[test]
1756    fn test_cache_invalidated_on_clear() {
1757        let container = Container::new();
1758        container.singleton(TestService { value: "v1".into() });
1759
1760        // Populate the thread-local hot cache
1761        assert_eq!(container.get::<TestService>().unwrap().value, "v1");
1762
1763        // After clear(), the cached entry must not be served
1764        container.clear();
1765        assert!(container.get::<TestService>().is_err());
1766    }
1767
1768    #[test]
1769    fn test_cache_invalidated_on_reregistration() {
1770        let container = Container::new();
1771        container.singleton(TestService { value: "v1".into() });
1772
1773        // Populate the thread-local hot cache
1774        assert_eq!(container.get::<TestService>().unwrap().value, "v1");
1775
1776        // Re-registering the same type overwrites the previous instance
1777        container.singleton(TestService { value: "v2".into() });
1778        assert_eq!(container.get::<TestService>().unwrap().value, "v2");
1779    }
1780
1781    #[test]
1782    fn test_parent_mutation_visible_through_child_scope() {
1783        // Parent-resolved services are deliberately not hot-cached (see
1784        // resolve_from_parents), so mutations of an ancestor scope must be
1785        // visible through child scopes on the same thread.
1786        let root = Container::new();
1787        root.singleton(TestService { value: "v1".into() });
1788
1789        let scope = root.scope();
1790        assert_eq!(scope.get::<TestService>().unwrap().value, "v1");
1791
1792        // Re-register in the parent: the child must observe the new value
1793        root.singleton(TestService { value: "v2".into() });
1794        assert_eq!(scope.get::<TestService>().unwrap().value, "v2");
1795
1796        // Clear the parent: the child must observe the removal
1797        root.clear();
1798        assert!(scope.get::<TestService>().is_err());
1799    }
1800
1801    #[test]
1802    fn test_parent_singleton_identity_through_child() {
1803        // Parent-resolved services are not hot-cached; repeated resolution
1804        // through a child scope must still return the same Arc instance.
1805        let root = Container::new();
1806        root.singleton(TestService {
1807            value: "shared".into(),
1808        });
1809
1810        let child = root.scope();
1811        let a = child.get::<TestService>().unwrap();
1812        let b = child.get::<TestService>().unwrap();
1813        assert!(Arc::ptr_eq(&a, &b));
1814    }
1815
1816    #[test]
1817    fn test_remove_when_locked() {
1818        // Locking prevents new registrations; like clear(), remove is
1819        // still permitted (pinned behavior — see the remove() docs).
1820        let container = Container::new();
1821        container.singleton(TestService { value: "v".into() });
1822        container.lock();
1823
1824        assert!(container.remove::<TestService>());
1825        assert!(!container.contains::<TestService>());
1826    }
1827
1828    #[test]
1829    fn test_remove_in_child_does_not_affect_parent() {
1830        let root = Container::new();
1831        root.singleton(TestService {
1832            value: "root".into(),
1833        });
1834
1835        let child = root.scope();
1836        // Removing a parent-only registration from the child is a no-op
1837        assert!(!child.remove::<TestService>());
1838        assert!(root.contains::<TestService>());
1839
1840        // A child override can be removed without touching the parent
1841        child.singleton(TestService {
1842            value: "child".into(),
1843        });
1844        assert!(child.remove::<TestService>());
1845        assert!(root.contains::<TestService>());
1846        assert_eq!(root.get::<TestService>().unwrap().value, "root");
1847        // Child falls back to the parent's registration again
1848        assert_eq!(child.get::<TestService>().unwrap().value, "root");
1849    }
1850
1851    #[test]
1852    fn test_pool_reuse_invalidates_hot_cache() {
1853        #[derive(Clone)]
1854        struct RequestId(u64);
1855
1856        let root = Container::new();
1857        let pool = ScopePool::new(&root, 1);
1858
1859        {
1860            let scope = pool.acquire();
1861            scope.singleton(RequestId(1));
1862            // Populate the thread-local hot cache for this storage
1863            assert_eq!(scope.get::<RequestId>().unwrap().0, 1);
1864        }
1865
1866        // Same storage is reused; the release-path clear() must have
1867        // stamped a fresh generation so the cached RequestId(1) cannot hit.
1868        {
1869            let scope = pool.acquire();
1870            assert!(scope.get::<RequestId>().is_err());
1871            scope.singleton(RequestId(2));
1872            assert_eq!(scope.get::<RequestId>().unwrap().0, 2);
1873        }
1874    }
1875
1876    #[test]
1877    fn test_cross_thread_invalidation_monotonic() {
1878        // A reader must never observe an older value after a newer one:
1879        // the Release stores in ServiceStorage's mutators paired with the
1880        // Acquire load in generation() make re-registrations publish to
1881        // readers' hot caches. (Single values are compared, so this test
1882        // is deterministic-safe: it can miss a regression but never
1883        // false-positives.)
1884        use std::sync::atomic::AtomicBool as StopFlag;
1885
1886        #[derive(Clone)]
1887        struct Counter(u64);
1888
1889        let container = Container::new();
1890        container.singleton(Counter(0));
1891        let done = Arc::new(StopFlag::new(false));
1892
1893        let writer = {
1894            let c = container.clone();
1895            let done = Arc::clone(&done);
1896            std::thread::spawn(move || {
1897                for i in 1..=2_000u64 {
1898                    c.singleton(Counter(i));
1899                }
1900                done.store(true, Ordering::Release);
1901            })
1902        };
1903
1904        let reader = {
1905            let c = container.clone();
1906            let done = Arc::clone(&done);
1907            std::thread::spawn(move || {
1908                let mut last = 0u64;
1909                while !done.load(Ordering::Acquire) {
1910                    let v = c.get::<Counter>().unwrap().0;
1911                    assert!(v >= last, "stale read: observed {v} after {last}");
1912                    last = v;
1913                }
1914            })
1915        };
1916
1917        writer.join().unwrap();
1918        reader.join().unwrap();
1919    }
1920
1921    #[test]
1922    fn test_mutation_does_not_affect_other_container_cache() {
1923        let a = Container::new();
1924        let b = Container::new();
1925        a.singleton(TestService { value: "a".into() });
1926        b.singleton(TestService { value: "b".into() });
1927
1928        // Populate the hot cache for both containers
1929        assert_eq!(a.get::<TestService>().unwrap().value, "a");
1930        let b1 = b.get::<TestService>().unwrap();
1931        assert_eq!(b1.value, "b");
1932
1933        // Mutating `a` must not disturb `b`'s cached resolution
1934        a.clear();
1935        assert!(a.get::<TestService>().is_err());
1936        let b2 = b.get::<TestService>().unwrap();
1937        assert_eq!(b2.value, "b");
1938        assert!(Arc::ptr_eq(&b1, &b2));
1939    }
1940
1941    #[test]
1942    fn test_remove() {
1943        let container = Container::new();
1944        container.singleton(TestService {
1945            value: "test".into(),
1946        });
1947        assert!(container.contains::<TestService>());
1948
1949        assert!(container.remove::<TestService>());
1950        assert!(!container.contains::<TestService>());
1951        assert!(container.get::<TestService>().is_err());
1952    }
1953
1954    #[test]
1955    fn test_remove_unregistered() {
1956        let container = Container::new();
1957        assert!(!container.remove::<TestService>());
1958    }
1959
1960    #[test]
1961    fn test_remove_invalidates_cache() {
1962        let container = Container::new();
1963        container.singleton(TestService {
1964            value: "cached".into(),
1965        });
1966
1967        // Populate the thread-local hot cache
1968        assert_eq!(container.get::<TestService>().unwrap().value, "cached");
1969
1970        // After remove(), the cached entry must not be served
1971        assert!(container.remove::<TestService>());
1972        assert!(container.get::<TestService>().is_err());
1973    }
1974
1975    #[cfg(feature = "perfect-hash")]
1976    #[test]
1977    fn test_freeze() {
1978        let container = Container::new();
1979        container.singleton(TestService {
1980            value: "frozen".into(),
1981        });
1982
1983        let frozen = container.freeze();
1984
1985        // Freezing locks the container
1986        assert!(container.is_locked());
1987
1988        // The frozen storage resolves the registered service
1989        let service = frozen.resolve(&TypeId::of::<TestService>()).unwrap();
1990        // SAFETY: We resolved by TypeId::of::<TestService>(), so the Arc
1991        // contains a TestService.
1992        let typed: Arc<TestService> = unsafe { downcast_arc_unchecked(service) };
1993        assert_eq!(typed.value, "frozen");
1994        assert_eq!(frozen.len(), 1);
1995
1996        // The original container still resolves after freezing
1997        assert_eq!(container.get::<TestService>().unwrap().value, "frozen");
1998    }
1999
2000    #[test]
2001    fn test_warm_cache() {
2002        let container = Container::new();
2003        container.singleton(TestService {
2004            value: "warm".into(),
2005        });
2006
2007        // Pre-warm the cache, then resolve normally
2008        container.warm_cache::<TestService>();
2009        let service = container.get::<TestService>().unwrap();
2010        assert_eq!(service.value, "warm");
2011    }
2012
2013    #[test]
2014    fn test_clear_cache() {
2015        let container = Container::new();
2016        container.singleton(TestService {
2017            value: "cached".into(),
2018        });
2019
2020        // Populate the hot cache, then explicitly clear it
2021        let s1 = container.get::<TestService>().unwrap();
2022        container.clear_cache();
2023
2024        // Resolution still works, served from storage again
2025        let s2 = container.get::<TestService>().unwrap();
2026        assert_eq!(s2.value, "cached");
2027        assert!(Arc::ptr_eq(&s1, &s2));
2028    }
2029}