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