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    // =========================================================================
747    // Lifecycle Methods
748    // =========================================================================
749
750    /// Lock the container to prevent further registrations.
751    ///
752    /// Useful for ensuring no services are registered after app initialization.
753    #[inline]
754    pub fn lock(&self) {
755        self.locked.store(true, Ordering::Release);
756
757        #[cfg(feature = "logging")]
758        debug!(
759            target: "dependency_injector",
760            depth = self.depth,
761            service_count = self.storage.len(),
762            "Container locked - no further registrations allowed"
763        );
764    }
765
766    /// Check if the container is locked.
767    #[inline]
768    pub fn is_locked(&self) -> bool {
769        self.locked.load(Ordering::Acquire)
770    }
771
772    /// Freeze the container into an immutable, perfectly-hashed storage.
773    ///
774    /// This creates a `FrozenStorage` that uses minimal perfect hashing for
775    /// O(1) lookups without hash collisions, providing ~5ns faster resolution.
776    ///
777    /// Note: This also locks the container to prevent further registrations.
778    ///
779    /// # Example
780    ///
781    /// ```rust,ignore
782    /// use dependency_injector::Container;
783    ///
784    /// let container = Container::new();
785    /// container.singleton(MyService { ... });
786    ///
787    /// let frozen = container.freeze();
788    /// // Use frozen.resolve(&type_id) for faster lookups
789    /// ```
790    #[cfg(feature = "perfect-hash")]
791    #[inline]
792    pub fn freeze(&self) -> crate::storage::FrozenStorage {
793        self.lock();
794        crate::storage::FrozenStorage::from_storage(&self.storage)
795    }
796
797    /// Clear all services from this scope.
798    ///
799    /// Does not affect parent scopes.
800    #[inline]
801    pub fn clear(&self) {
802        #[cfg(feature = "logging")]
803        let count = self.storage.len();
804        self.storage.clear();
805
806        #[cfg(feature = "logging")]
807        debug!(
808            target: "dependency_injector",
809            depth = self.depth,
810            services_removed = count,
811            "Container cleared - all services removed from this scope"
812        );
813    }
814
815    /// Remove a service registration from this scope.
816    ///
817    /// Returns `true` if a service of type `T` was registered in this scope
818    /// and has been removed, `false` otherwise. Does not affect parent scopes.
819    /// Like [`clear`](Self::clear), removal is permitted on a locked
820    /// container (locking prevents new registrations, not removal).
821    ///
822    /// # Examples
823    ///
824    /// ```rust
825    /// use dependency_injector::Container;
826    ///
827    /// #[derive(Clone)]
828    /// struct MyService;
829    ///
830    /// let container = Container::new();
831    /// container.singleton(MyService);
832    ///
833    /// assert!(container.remove::<MyService>());
834    /// assert!(!container.contains::<MyService>());
835    /// assert!(!container.remove::<MyService>()); // Already removed
836    /// ```
837    #[inline]
838    pub fn remove<T: Injectable>(&self) -> bool {
839        let removed = self.storage.remove(&TypeId::of::<T>());
840        if removed {
841            #[cfg(feature = "logging")]
842            debug!(
843                target: "dependency_injector",
844                service = std::any::type_name::<T>(),
845                depth = self.depth,
846                "Service registration removed from this scope"
847            );
848        }
849        removed
850    }
851
852    /// Panic if locked (internal helper).
853    /// Uses relaxed ordering for fast path - we only need eventual consistency
854    /// since registration is not a hot path and locking is rare.
855    #[inline]
856    fn check_not_locked(&self) {
857        if self.locked.load(Ordering::Relaxed) {
858            panic!("Cannot register services: container is locked");
859        }
860    }
861
862    // =========================================================================
863    // Batch Registration (Phase 3)
864    // =========================================================================
865
866    /// Register multiple services in a single batch operation.
867    ///
868    /// This is more efficient than individual registrations when registering
869    /// many services at once, as it:
870    /// - Performs a single lock check at the start
871    /// - Minimizes per-call overhead
872    ///
873    /// # Examples
874    ///
875    /// ```rust
876    /// use dependency_injector::Container;
877    ///
878    /// #[derive(Clone)]
879    /// struct Database { url: String }
880    /// #[derive(Clone)]
881    /// struct Cache { size: usize }
882    /// #[derive(Clone)]
883    /// struct Logger { level: String }
884    ///
885    /// let container = Container::new();
886    /// container.batch(|batch| {
887    ///     batch.singleton(Database { url: "postgres://localhost".into() });
888    ///     batch.singleton(Cache { size: 1024 });
889    ///     batch.singleton(Logger { level: "info".into() });
890    /// });
891    ///
892    /// assert!(container.contains::<Database>());
893    /// assert!(container.contains::<Cache>());
894    /// assert!(container.contains::<Logger>());
895    /// ```
896    ///
897    /// Note: For maximum performance with many services, prefer the builder API:
898    /// ```rust
899    /// use dependency_injector::Container;
900    ///
901    /// #[derive(Clone)]
902    /// struct A;
903    /// #[derive(Clone)]
904    /// struct B;
905    ///
906    /// let container = Container::new();
907    /// container.register_batch()
908    ///     .singleton(A)
909    ///     .singleton(B)
910    ///     .done();
911    /// ```
912    #[inline]
913    pub fn batch<F>(&self, f: F)
914    where
915        F: FnOnce(BatchRegistrar<'_>),
916    {
917        self.check_not_locked();
918
919        #[cfg(feature = "logging")]
920        let start_count = self.storage.len();
921
922        // Create a zero-cost batch registrar that wraps the storage
923        f(BatchRegistrar {
924            storage: &self.storage,
925        });
926
927        #[cfg(feature = "logging")]
928        {
929            let end_count = self.storage.len();
930            debug!(
931                target: "dependency_injector",
932                depth = self.depth,
933                services_registered = end_count - start_count,
934                "Batch registration completed"
935            );
936        }
937    }
938
939    /// Start a fluent batch registration.
940    ///
941    /// This is faster than the closure-based `batch()` for many services
942    /// because it avoids closure overhead.
943    ///
944    /// # Example
945    ///
946    /// ```rust
947    /// use dependency_injector::Container;
948    ///
949    /// #[derive(Clone)]
950    /// struct Database { url: String }
951    /// #[derive(Clone)]
952    /// struct Cache { size: usize }
953    ///
954    /// let container = Container::new();
955    /// container.register_batch()
956    ///     .singleton(Database { url: "postgres://localhost".into() })
957    ///     .singleton(Cache { size: 1024 })
958    ///     .done();
959    ///
960    /// assert!(container.contains::<Database>());
961    /// assert!(container.contains::<Cache>());
962    /// ```
963    #[inline]
964    pub fn register_batch(&self) -> BatchBuilder<'_> {
965        self.check_not_locked();
966        BatchBuilder {
967            storage: &self.storage,
968            #[cfg(feature = "logging")]
969            count: 0,
970        }
971    }
972}
973
974/// Fluent batch registration builder.
975///
976/// Provides a chainable API for registering multiple services without closure overhead.
977pub struct BatchBuilder<'a> {
978    storage: &'a ServiceStorage,
979    #[cfg(feature = "logging")]
980    count: usize,
981}
982
983impl<'a> BatchBuilder<'a> {
984    /// Register a singleton and continue the chain
985    #[inline]
986    pub fn singleton<T: Injectable>(self, instance: T) -> Self {
987        self.storage
988            .insert(TypeId::of::<T>(), AnyFactory::singleton(instance));
989        Self {
990            storage: self.storage,
991            #[cfg(feature = "logging")]
992            count: self.count + 1,
993        }
994    }
995
996    /// Register a lazy singleton and continue the chain
997    #[inline]
998    pub fn lazy<T: Injectable, F>(self, factory: F) -> Self
999    where
1000        F: Fn() -> T + Send + Sync + 'static,
1001    {
1002        self.storage
1003            .insert(TypeId::of::<T>(), AnyFactory::lazy(factory));
1004        Self {
1005            storage: self.storage,
1006            #[cfg(feature = "logging")]
1007            count: self.count + 1,
1008        }
1009    }
1010
1011    /// Register a transient and continue the chain
1012    #[inline]
1013    pub fn transient<T: Injectable, F>(self, factory: F) -> Self
1014    where
1015        F: Fn() -> T + Send + Sync + 'static,
1016    {
1017        self.storage
1018            .insert(TypeId::of::<T>(), AnyFactory::transient(factory));
1019        Self {
1020            storage: self.storage,
1021            #[cfg(feature = "logging")]
1022            count: self.count + 1,
1023        }
1024    }
1025
1026    /// Finish the batch registration
1027    #[inline]
1028    pub fn done(self) {
1029        #[cfg(feature = "logging")]
1030        debug!(
1031            target: "dependency_injector",
1032            services_registered = self.count,
1033            "Batch registration completed"
1034        );
1035    }
1036}
1037
1038/// Batch registrar for closure-based bulk registration.
1039///
1040/// A zero-cost wrapper that provides direct storage access.
1041/// The lock check is done once in `Container::batch()`.
1042#[repr(transparent)]
1043pub struct BatchRegistrar<'a> {
1044    storage: &'a ServiceStorage,
1045}
1046
1047impl<'a> BatchRegistrar<'a> {
1048    /// Register a singleton service (inserted immediately)
1049    #[inline]
1050    pub fn singleton<T: Injectable>(&self, instance: T) {
1051        self.storage
1052            .insert(TypeId::of::<T>(), AnyFactory::singleton(instance));
1053    }
1054
1055    /// Register a lazy singleton service (inserted immediately)
1056    #[inline]
1057    pub fn lazy<T: Injectable, F>(&self, factory: F)
1058    where
1059        F: Fn() -> T + Send + Sync + 'static,
1060    {
1061        self.storage
1062            .insert(TypeId::of::<T>(), AnyFactory::lazy(factory));
1063    }
1064
1065    /// Register a transient service (inserted immediately)
1066    #[inline]
1067    pub fn transient<T: Injectable, F>(&self, factory: F)
1068    where
1069        F: Fn() -> T + Send + Sync + 'static,
1070    {
1071        self.storage
1072            .insert(TypeId::of::<T>(), AnyFactory::transient(factory));
1073    }
1074}
1075
1076// =============================================================================
1077// Scope Pooling (Phase 6 optimization)
1078// =============================================================================
1079
1080use std::sync::Mutex;
1081
1082/// A pool of pre-allocated scopes for high-throughput scenarios.
1083///
1084/// Creating a scope involves allocating a DashMap (~134ns). For web servers
1085/// handling thousands of requests per second, this adds up. ScopePool pre-allocates
1086/// scopes and reuses them, reducing per-request overhead to near-zero.
1087///
1088/// # Example
1089///
1090/// ```rust
1091/// use dependency_injector::{Container, ScopePool};
1092///
1093/// #[derive(Clone)]
1094/// struct AppConfig { name: String }
1095///
1096/// #[derive(Clone)]
1097/// struct RequestId(String);
1098///
1099/// // Create root container with app-wide services
1100/// let root = Container::new();
1101/// root.singleton(AppConfig { name: "MyApp".into() });
1102///
1103/// // Create a pool of reusable scopes (pre-allocates 4 scopes)
1104/// let pool = ScopePool::new(&root, 4);
1105///
1106/// // In request handler: acquire a pooled scope
1107/// {
1108///     let scope = pool.acquire();
1109///     scope.singleton(RequestId("req-123".into()));
1110///
1111///     // Can access parent services
1112///     assert!(scope.contains::<AppConfig>());
1113///     assert!(scope.contains::<RequestId>());
1114///
1115///     // Scope automatically released when dropped
1116/// }
1117///
1118/// // Next request reuses the same scope allocation
1119/// {
1120///     let scope = pool.acquire();
1121///     // Previous RequestId is cleared, fresh scope
1122///     assert!(!scope.contains::<RequestId>());
1123/// }
1124/// ```
1125///
1126/// # Performance
1127///
1128/// - First acquisition: ~134ns (creates new scope if pool is empty)
1129/// - Subsequent acquisitions: ~20ns (reuses pooled scope)
1130/// - Release: ~10ns (clears and returns to pool)
1131pub struct ScopePool {
1132    /// Parent storage to create scopes from
1133    parent_storage: Arc<ServiceStorage>,
1134    /// Pool of available scopes (storage + lock state pairs)
1135    available: Mutex<Vec<ScopeSlot>>,
1136    /// Parent depth for child scope depth calculation
1137    parent_depth: u32,
1138}
1139
1140/// A reusable scope slot containing pre-allocated storage and lock state
1141struct ScopeSlot {
1142    /// Pre-allocated storage with parent reference
1143    storage: Arc<ServiceStorage>,
1144    locked: Arc<AtomicBool>,
1145}
1146
1147impl ScopePool {
1148    /// Create a new scope pool with pre-allocated capacity.
1149    ///
1150    /// # Arguments
1151    ///
1152    /// * `parent` - The parent container that scopes will inherit from
1153    /// * `capacity` - Number of scopes to pre-allocate
1154    ///
1155    /// # Example
1156    ///
1157    /// ```rust
1158    /// use dependency_injector::{Container, ScopePool};
1159    ///
1160    /// let root = Container::new();
1161    /// // Pre-allocate 8 scopes for concurrent request handling
1162    /// let pool = ScopePool::new(&root, 8);
1163    /// ```
1164    pub fn new(parent: &Container, capacity: usize) -> Self {
1165        let mut available = Vec::with_capacity(capacity);
1166
1167        // Pre-allocate storage with parent reference and lock states
1168        for _ in 0..capacity {
1169            available.push(ScopeSlot {
1170                storage: Arc::new(ServiceStorage::with_parent(Arc::clone(&parent.storage))),
1171                locked: Arc::new(AtomicBool::new(false)),
1172            });
1173        }
1174
1175        #[cfg(feature = "logging")]
1176        debug!(
1177            target: "dependency_injector",
1178            capacity = capacity,
1179            parent_depth = parent.depth,
1180            "Created scope pool with pre-allocated scopes"
1181        );
1182
1183        Self {
1184            parent_storage: Arc::clone(&parent.storage),
1185            available: Mutex::new(available),
1186            parent_depth: parent.depth,
1187        }
1188    }
1189
1190    /// Acquire a scope from the pool.
1191    ///
1192    /// Returns a `PooledScope` that automatically returns to the pool when dropped.
1193    /// If the pool is empty, creates a new scope.
1194    ///
1195    /// # Example
1196    ///
1197    /// ```rust
1198    /// use dependency_injector::{Container, ScopePool};
1199    ///
1200    /// #[derive(Clone)]
1201    /// struct RequestData { id: u64 }
1202    ///
1203    /// let root = Container::new();
1204    /// let pool = ScopePool::new(&root, 4);
1205    ///
1206    /// let scope = pool.acquire();
1207    /// scope.singleton(RequestData { id: 123 });
1208    /// let data = scope.get::<RequestData>().unwrap();
1209    /// assert_eq!(data.id, 123);
1210    /// ```
1211    #[inline]
1212    pub fn acquire(&self) -> PooledScope<'_> {
1213        let slot = self.available.lock().unwrap().pop();
1214
1215        let (storage, locked) = match slot {
1216            Some(slot) => {
1217                #[cfg(feature = "logging")]
1218                trace!(
1219                    target: "dependency_injector",
1220                    "Acquired scope from pool (reusing storage)"
1221                );
1222                (slot.storage, slot.locked)
1223            }
1224            None => {
1225                #[cfg(feature = "logging")]
1226                trace!(
1227                    target: "dependency_injector",
1228                    "Pool empty, creating new scope"
1229                );
1230                (
1231                    Arc::new(ServiceStorage::with_parent(Arc::clone(
1232                        &self.parent_storage,
1233                    ))),
1234                    Arc::new(AtomicBool::new(false)),
1235                )
1236            }
1237        };
1238
1239        let container = Container {
1240            storage,
1241            parent_storage: Some(Arc::clone(&self.parent_storage)),
1242            locked,
1243            depth: self.parent_depth + 1,
1244        };
1245
1246        PooledScope {
1247            container: Some(container),
1248            pool: self,
1249        }
1250    }
1251
1252    /// Return a scope to the pool (internal use).
1253    #[inline]
1254    fn release(&self, container: Container) {
1255        // Clear storage for reuse (parent reference is preserved). The clear
1256        // stamps a fresh generation, invalidating hot cache entries for this
1257        // scope (including lingering clones of the container).
1258        container.storage.clear();
1259        // Reset lock state
1260        container.locked.store(false, Ordering::Relaxed);
1261
1262        // Return to pool
1263        self.available.lock().unwrap().push(ScopeSlot {
1264            storage: container.storage,
1265            locked: container.locked,
1266        });
1267
1268        #[cfg(feature = "logging")]
1269        trace!(
1270            target: "dependency_injector",
1271            "Released scope back to pool"
1272        );
1273    }
1274
1275    /// Get the current number of available scopes in the pool.
1276    #[inline]
1277    pub fn available_count(&self) -> usize {
1278        self.available.lock().unwrap().len()
1279    }
1280}
1281
1282/// A scope acquired from a pool that automatically returns when dropped.
1283///
1284/// This provides RAII-style management of pooled scopes, ensuring they're
1285/// always returned to the pool even if the code panics.
1286pub struct PooledScope<'a> {
1287    container: Option<Container>,
1288    pool: &'a ScopePool,
1289}
1290
1291impl PooledScope<'_> {
1292    /// Get a reference to the underlying container.
1293    #[inline]
1294    pub fn container(&self) -> &Container {
1295        self.container.as_ref().unwrap()
1296    }
1297}
1298
1299impl std::ops::Deref for PooledScope<'_> {
1300    type Target = Container;
1301
1302    #[inline]
1303    fn deref(&self) -> &Self::Target {
1304        self.container.as_ref().unwrap()
1305    }
1306}
1307
1308impl Drop for PooledScope<'_> {
1309    fn drop(&mut self) {
1310        if let Some(container) = self.container.take() {
1311            self.pool.release(container);
1312        }
1313    }
1314}
1315
1316impl Default for Container {
1317    fn default() -> Self {
1318        Self::new()
1319    }
1320}
1321
1322impl std::fmt::Debug for Container {
1323    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1324        f.debug_struct("Container")
1325            .field("service_count", &self.len())
1326            .field("depth", &self.depth)
1327            .field("has_parent", &self.parent_storage.is_some())
1328            .field("locked", &self.is_locked())
1329            .finish_non_exhaustive()
1330    }
1331}
1332
1333// =========================================================================
1334// Thread Safety
1335// =========================================================================
1336
1337// Container is Send + Sync because:
1338// - storage is Arc<ServiceStorage>, which uses DashMap (thread-safe)
1339// - parent_storage is Option<Arc<ServiceStorage>> (Send + Sync)
1340// - locked is Arc<AtomicBool> (Send + Sync)
1341// - depth is a plain u32 (trivially Send + Sync)
1342unsafe impl Send for Container {}
1343unsafe impl Sync for Container {}
1344
1345#[cfg(test)]
1346mod tests {
1347    use super::*;
1348
1349    #[derive(Clone)]
1350    struct TestService {
1351        value: String,
1352    }
1353
1354    #[allow(dead_code)]
1355    #[derive(Clone)]
1356    struct AnotherService {
1357        name: String,
1358    }
1359
1360    #[test]
1361    fn test_singleton() {
1362        let container = Container::new();
1363        container.singleton(TestService {
1364            value: "test".into(),
1365        });
1366
1367        let s1 = container.get::<TestService>().unwrap();
1368        let s2 = container.get::<TestService>().unwrap();
1369
1370        assert_eq!(s1.value, "test");
1371        assert!(Arc::ptr_eq(&s1, &s2));
1372    }
1373
1374    #[test]
1375    fn test_lazy() {
1376        use std::sync::atomic::{AtomicBool, Ordering};
1377
1378        static CREATED: AtomicBool = AtomicBool::new(false);
1379
1380        let container = Container::new();
1381        container.lazy(|| {
1382            CREATED.store(true, Ordering::SeqCst);
1383            TestService {
1384                value: "lazy".into(),
1385            }
1386        });
1387
1388        assert!(!CREATED.load(Ordering::SeqCst));
1389
1390        let s = container.get::<TestService>().unwrap();
1391        assert!(CREATED.load(Ordering::SeqCst));
1392        assert_eq!(s.value, "lazy");
1393    }
1394
1395    #[test]
1396    fn test_transient() {
1397        use std::sync::atomic::{AtomicU32, Ordering};
1398
1399        static COUNTER: AtomicU32 = AtomicU32::new(0);
1400
1401        #[derive(Clone)]
1402        struct Counter(u32);
1403
1404        let container = Container::new();
1405        container.transient(|| Counter(COUNTER.fetch_add(1, Ordering::SeqCst)));
1406
1407        let c1 = container.get::<Counter>().unwrap();
1408        let c2 = container.get::<Counter>().unwrap();
1409
1410        assert_ne!(c1.0, c2.0);
1411    }
1412
1413    #[test]
1414    fn test_scope_inheritance() {
1415        let root = Container::new();
1416        root.singleton(TestService {
1417            value: "root".into(),
1418        });
1419
1420        let child = root.scope();
1421        child.singleton(AnotherService {
1422            name: "child".into(),
1423        });
1424
1425        // Child sees both
1426        assert!(child.contains::<TestService>());
1427        assert!(child.contains::<AnotherService>());
1428
1429        // Root only sees its own
1430        assert!(root.contains::<TestService>());
1431        assert!(!root.contains::<AnotherService>());
1432    }
1433
1434    #[test]
1435    fn test_scope_override() {
1436        let root = Container::new();
1437        root.singleton(TestService {
1438            value: "root".into(),
1439        });
1440
1441        let child = root.scope();
1442        child.singleton(TestService {
1443            value: "child".into(),
1444        });
1445
1446        let root_service = root.get::<TestService>().unwrap();
1447        let child_service = child.get::<TestService>().unwrap();
1448
1449        assert_eq!(root_service.value, "root");
1450        assert_eq!(child_service.value, "child");
1451    }
1452
1453    #[test]
1454    fn test_not_found() {
1455        let container = Container::new();
1456        let result = container.get::<TestService>();
1457        assert!(result.is_err());
1458    }
1459
1460    #[test]
1461    fn test_lock() {
1462        let container = Container::new();
1463        assert!(!container.is_locked());
1464
1465        container.lock();
1466        assert!(container.is_locked());
1467    }
1468
1469    #[test]
1470    #[should_panic(expected = "Cannot register services: container is locked")]
1471    fn test_register_after_lock() {
1472        let container = Container::new();
1473        container.lock();
1474        container.singleton(TestService {
1475            value: "fail".into(),
1476        });
1477    }
1478
1479    #[test]
1480    fn test_batch_registration() {
1481        #[derive(Clone)]
1482        struct ServiceA(i32);
1483        #[allow(dead_code)]
1484        #[derive(Clone)]
1485        struct ServiceB(String);
1486
1487        let container = Container::new();
1488        container.batch(|batch| {
1489            batch.singleton(ServiceA(42));
1490            batch.singleton(ServiceB("test".into()));
1491            batch.lazy(|| TestService {
1492                value: "lazy".into(),
1493            });
1494        });
1495
1496        assert!(container.contains::<ServiceA>());
1497        assert!(container.contains::<ServiceB>());
1498        assert!(container.contains::<TestService>());
1499
1500        let a = container.get::<ServiceA>().unwrap();
1501        assert_eq!(a.0, 42);
1502    }
1503
1504    #[test]
1505    fn test_scope_pool_basic() {
1506        #[derive(Clone)]
1507        struct RequestId(u64);
1508
1509        let root = Container::new();
1510        root.singleton(TestService {
1511            value: "root".into(),
1512        });
1513
1514        // Create pool with 2 pre-allocated scopes
1515        let pool = ScopePool::new(&root, 2);
1516        assert_eq!(pool.available_count(), 2);
1517
1518        // Acquire a scope
1519        {
1520            let scope = pool.acquire();
1521            assert_eq!(pool.available_count(), 1);
1522
1523            // Can access parent services
1524            assert!(scope.contains::<TestService>());
1525
1526            // Register request-specific service
1527            scope.singleton(RequestId(123));
1528            assert!(scope.contains::<RequestId>());
1529
1530            let id = scope.get::<RequestId>().unwrap();
1531            assert_eq!(id.0, 123);
1532        }
1533        // Scope released back to pool
1534        assert_eq!(pool.available_count(), 2);
1535    }
1536
1537    #[test]
1538    fn test_scope_pool_reuse() {
1539        #[derive(Clone)]
1540        struct RequestId(u64);
1541
1542        let root = Container::new();
1543        let pool = ScopePool::new(&root, 1);
1544
1545        // First request
1546        {
1547            let scope = pool.acquire();
1548            scope.singleton(RequestId(1));
1549            assert!(scope.contains::<RequestId>());
1550        }
1551
1552        // Second request - should reuse the same scope (cleared)
1553        {
1554            let scope = pool.acquire();
1555            // Previous RequestId should be cleared
1556            assert!(!scope.contains::<RequestId>());
1557
1558            scope.singleton(RequestId(2));
1559            let id = scope.get::<RequestId>().unwrap();
1560            assert_eq!(id.0, 2);
1561        }
1562    }
1563
1564    #[test]
1565    fn test_scope_pool_expansion() {
1566        let root = Container::new();
1567        let pool = ScopePool::new(&root, 1);
1568
1569        // Acquire more scopes than pre-allocated
1570        let _s1 = pool.acquire();
1571        let _s2 = pool.acquire(); // Creates new scope
1572
1573        assert_eq!(pool.available_count(), 0);
1574
1575        // Both should work
1576        drop(_s1);
1577        drop(_s2);
1578
1579        // Both return to pool
1580        assert_eq!(pool.available_count(), 2);
1581    }
1582
1583    #[test]
1584    fn test_deep_parent_chain() {
1585        // Test that services can be resolved from grandparent and beyond
1586        #[derive(Clone)]
1587        struct RootService(i32);
1588        #[derive(Clone)]
1589        struct MiddleService(i32);
1590        #[derive(Clone)]
1591        struct LeafService(i32);
1592
1593        // Create 4-level hierarchy: root -> middle1 -> middle2 -> leaf
1594        let root = Container::new();
1595        root.singleton(RootService(1));
1596
1597        let middle1 = root.scope();
1598        middle1.singleton(MiddleService(2));
1599
1600        let middle2 = middle1.scope();
1601        // No service in middle2
1602
1603        let leaf = middle2.scope();
1604        leaf.singleton(LeafService(4));
1605
1606        // Leaf should be able to access all ancestor services
1607        assert!(
1608            leaf.contains::<RootService>(),
1609            "Should find root service in leaf"
1610        );
1611        assert!(
1612            leaf.contains::<MiddleService>(),
1613            "Should find middle service in leaf"
1614        );
1615        assert!(
1616            leaf.contains::<LeafService>(),
1617            "Should find leaf service in leaf"
1618        );
1619
1620        // Verify resolution works
1621        let root_svc = leaf.get::<RootService>().unwrap();
1622        assert_eq!(root_svc.0, 1);
1623
1624        let middle_svc = leaf.get::<MiddleService>().unwrap();
1625        assert_eq!(middle_svc.0, 2);
1626
1627        let leaf_svc = leaf.get::<LeafService>().unwrap();
1628        assert_eq!(leaf_svc.0, 4);
1629
1630        // Middle2 should also access ancestor services
1631        assert!(middle2.contains::<RootService>());
1632        assert!(middle2.contains::<MiddleService>());
1633        assert!(!middle2.contains::<LeafService>()); // Leaf service not in parent
1634    }
1635
1636    #[test]
1637    fn test_cache_invalidated_on_clear() {
1638        let container = Container::new();
1639        container.singleton(TestService { value: "v1".into() });
1640
1641        // Populate the thread-local hot cache
1642        assert_eq!(container.get::<TestService>().unwrap().value, "v1");
1643
1644        // After clear(), the cached entry must not be served
1645        container.clear();
1646        assert!(container.get::<TestService>().is_err());
1647    }
1648
1649    #[test]
1650    fn test_cache_invalidated_on_reregistration() {
1651        let container = Container::new();
1652        container.singleton(TestService { value: "v1".into() });
1653
1654        // Populate the thread-local hot cache
1655        assert_eq!(container.get::<TestService>().unwrap().value, "v1");
1656
1657        // Re-registering the same type overwrites the previous instance
1658        container.singleton(TestService { value: "v2".into() });
1659        assert_eq!(container.get::<TestService>().unwrap().value, "v2");
1660    }
1661
1662    #[test]
1663    fn test_parent_mutation_visible_through_child_scope() {
1664        // Parent-resolved services are deliberately not hot-cached (see
1665        // resolve_from_parents), so mutations of an ancestor scope must be
1666        // visible through child scopes on the same thread.
1667        let root = Container::new();
1668        root.singleton(TestService { value: "v1".into() });
1669
1670        let scope = root.scope();
1671        assert_eq!(scope.get::<TestService>().unwrap().value, "v1");
1672
1673        // Re-register in the parent: the child must observe the new value
1674        root.singleton(TestService { value: "v2".into() });
1675        assert_eq!(scope.get::<TestService>().unwrap().value, "v2");
1676
1677        // Clear the parent: the child must observe the removal
1678        root.clear();
1679        assert!(scope.get::<TestService>().is_err());
1680    }
1681
1682    #[test]
1683    fn test_parent_singleton_identity_through_child() {
1684        // Parent-resolved services are not hot-cached; repeated resolution
1685        // through a child scope must still return the same Arc instance.
1686        let root = Container::new();
1687        root.singleton(TestService {
1688            value: "shared".into(),
1689        });
1690
1691        let child = root.scope();
1692        let a = child.get::<TestService>().unwrap();
1693        let b = child.get::<TestService>().unwrap();
1694        assert!(Arc::ptr_eq(&a, &b));
1695    }
1696
1697    #[test]
1698    fn test_remove_when_locked() {
1699        // Locking prevents new registrations; like clear(), remove is
1700        // still permitted (pinned behavior — see the remove() docs).
1701        let container = Container::new();
1702        container.singleton(TestService { value: "v".into() });
1703        container.lock();
1704
1705        assert!(container.remove::<TestService>());
1706        assert!(!container.contains::<TestService>());
1707    }
1708
1709    #[test]
1710    fn test_remove_in_child_does_not_affect_parent() {
1711        let root = Container::new();
1712        root.singleton(TestService {
1713            value: "root".into(),
1714        });
1715
1716        let child = root.scope();
1717        // Removing a parent-only registration from the child is a no-op
1718        assert!(!child.remove::<TestService>());
1719        assert!(root.contains::<TestService>());
1720
1721        // A child override can be removed without touching the parent
1722        child.singleton(TestService {
1723            value: "child".into(),
1724        });
1725        assert!(child.remove::<TestService>());
1726        assert!(root.contains::<TestService>());
1727        assert_eq!(root.get::<TestService>().unwrap().value, "root");
1728        // Child falls back to the parent's registration again
1729        assert_eq!(child.get::<TestService>().unwrap().value, "root");
1730    }
1731
1732    #[test]
1733    fn test_pool_reuse_invalidates_hot_cache() {
1734        #[derive(Clone)]
1735        struct RequestId(u64);
1736
1737        let root = Container::new();
1738        let pool = ScopePool::new(&root, 1);
1739
1740        {
1741            let scope = pool.acquire();
1742            scope.singleton(RequestId(1));
1743            // Populate the thread-local hot cache for this storage
1744            assert_eq!(scope.get::<RequestId>().unwrap().0, 1);
1745        }
1746
1747        // Same storage is reused; the release-path clear() must have
1748        // stamped a fresh generation so the cached RequestId(1) cannot hit.
1749        {
1750            let scope = pool.acquire();
1751            assert!(scope.get::<RequestId>().is_err());
1752            scope.singleton(RequestId(2));
1753            assert_eq!(scope.get::<RequestId>().unwrap().0, 2);
1754        }
1755    }
1756
1757    #[test]
1758    fn test_cross_thread_invalidation_monotonic() {
1759        // A reader must never observe an older value after a newer one:
1760        // the Release stores in ServiceStorage's mutators paired with the
1761        // Acquire load in generation() make re-registrations publish to
1762        // readers' hot caches. (Single values are compared, so this test
1763        // is deterministic-safe: it can miss a regression but never
1764        // false-positives.)
1765        use std::sync::atomic::AtomicBool as StopFlag;
1766
1767        #[derive(Clone)]
1768        struct Counter(u64);
1769
1770        let container = Container::new();
1771        container.singleton(Counter(0));
1772        let done = Arc::new(StopFlag::new(false));
1773
1774        let writer = {
1775            let c = container.clone();
1776            let done = Arc::clone(&done);
1777            std::thread::spawn(move || {
1778                for i in 1..=2_000u64 {
1779                    c.singleton(Counter(i));
1780                }
1781                done.store(true, Ordering::Release);
1782            })
1783        };
1784
1785        let reader = {
1786            let c = container.clone();
1787            let done = Arc::clone(&done);
1788            std::thread::spawn(move || {
1789                let mut last = 0u64;
1790                while !done.load(Ordering::Acquire) {
1791                    let v = c.get::<Counter>().unwrap().0;
1792                    assert!(v >= last, "stale read: observed {v} after {last}");
1793                    last = v;
1794                }
1795            })
1796        };
1797
1798        writer.join().unwrap();
1799        reader.join().unwrap();
1800    }
1801
1802    #[test]
1803    fn test_mutation_does_not_affect_other_container_cache() {
1804        let a = Container::new();
1805        let b = Container::new();
1806        a.singleton(TestService { value: "a".into() });
1807        b.singleton(TestService { value: "b".into() });
1808
1809        // Populate the hot cache for both containers
1810        assert_eq!(a.get::<TestService>().unwrap().value, "a");
1811        let b1 = b.get::<TestService>().unwrap();
1812        assert_eq!(b1.value, "b");
1813
1814        // Mutating `a` must not disturb `b`'s cached resolution
1815        a.clear();
1816        assert!(a.get::<TestService>().is_err());
1817        let b2 = b.get::<TestService>().unwrap();
1818        assert_eq!(b2.value, "b");
1819        assert!(Arc::ptr_eq(&b1, &b2));
1820    }
1821
1822    #[test]
1823    fn test_remove() {
1824        let container = Container::new();
1825        container.singleton(TestService {
1826            value: "test".into(),
1827        });
1828        assert!(container.contains::<TestService>());
1829
1830        assert!(container.remove::<TestService>());
1831        assert!(!container.contains::<TestService>());
1832        assert!(container.get::<TestService>().is_err());
1833    }
1834
1835    #[test]
1836    fn test_remove_unregistered() {
1837        let container = Container::new();
1838        assert!(!container.remove::<TestService>());
1839    }
1840
1841    #[test]
1842    fn test_remove_invalidates_cache() {
1843        let container = Container::new();
1844        container.singleton(TestService {
1845            value: "cached".into(),
1846        });
1847
1848        // Populate the thread-local hot cache
1849        assert_eq!(container.get::<TestService>().unwrap().value, "cached");
1850
1851        // After remove(), the cached entry must not be served
1852        assert!(container.remove::<TestService>());
1853        assert!(container.get::<TestService>().is_err());
1854    }
1855
1856    #[cfg(feature = "perfect-hash")]
1857    #[test]
1858    fn test_freeze() {
1859        let container = Container::new();
1860        container.singleton(TestService {
1861            value: "frozen".into(),
1862        });
1863
1864        let frozen = container.freeze();
1865
1866        // Freezing locks the container
1867        assert!(container.is_locked());
1868
1869        // The frozen storage resolves the registered service
1870        let service = frozen.resolve(&TypeId::of::<TestService>()).unwrap();
1871        // SAFETY: We resolved by TypeId::of::<TestService>(), so the Arc
1872        // contains a TestService.
1873        let typed: Arc<TestService> = unsafe { downcast_arc_unchecked(service) };
1874        assert_eq!(typed.value, "frozen");
1875        assert_eq!(frozen.len(), 1);
1876
1877        // The original container still resolves after freezing
1878        assert_eq!(container.get::<TestService>().unwrap().value, "frozen");
1879    }
1880
1881    #[test]
1882    fn test_warm_cache() {
1883        let container = Container::new();
1884        container.singleton(TestService {
1885            value: "warm".into(),
1886        });
1887
1888        // Pre-warm the cache, then resolve normally
1889        container.warm_cache::<TestService>();
1890        let service = container.get::<TestService>().unwrap();
1891        assert_eq!(service.value, "warm");
1892    }
1893
1894    #[test]
1895    fn test_clear_cache() {
1896        let container = Container::new();
1897        container.singleton(TestService {
1898            value: "cached".into(),
1899        });
1900
1901        // Populate the hot cache, then explicitly clear it
1902        let s1 = container.get::<TestService>().unwrap();
1903        container.clear_cache();
1904
1905        // Resolution still works, served from storage again
1906        let s2 = container.get::<TestService>().unwrap();
1907        assert_eq!(s2.value, "cached");
1908        assert!(Arc::ptr_eq(&s1, &s2));
1909    }
1910}