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}