ferrous_di/provider/mod.rs
1//! Service provider module for dependency injection.
2//!
3//! This module contains the ServiceProvider type and related functionality
4//! for resolving registered services from the DI container.
5
6use std::collections::HashMap;
7use std::sync::{Arc, Mutex};
8
9use crate::{DiResult, DiError, Key, Lifetime};
10use crate::registration::{Registry, AnyArc};
11use crate::internal::{DisposeBag, BoxFutureUnit, with_circular_catch};
12use crate::observer::{Observers, ObservationContext};
13use crate::capabilities::{CapabilityRegistry, ToolSelectionCriteria, ToolDiscoveryResult, ToolInfo};
14use crate::fast_singletons::FastSingletonCache;
15use crate::traits::{Resolver, ResolverCore, Dispose, AsyncDispose};
16
17// Re-export Scope and ResolverContext
18pub mod scope;
19pub mod context;
20pub use scope::*;
21pub use context::ResolverContext;
22use context::ResolverContext as LocalResolverContext;
23
24/// Service provider for resolving dependencies from the DI container.
25///
26/// The `ServiceProvider` is the heart of the dependency injection system. It resolves
27/// services according to their registered lifetimes (Singleton, Scoped, Transient) and
28/// manages the lifecycle of singleton services including disposal.
29///
30/// # Performance Optimizations
31///
32/// ServiceProvider includes world-class performance optimizations:
33/// - **Singleton caching**: Embedded OnceCell provides 31ns resolution (~31.5M ops/sec)
34/// - **Scoped caching**: Slot-based resolution with O(1) access times
35/// - **Hybrid registry**: Vec for small collections, HashMap for large ones
36/// - **Lock-free reads**: After initialization, singleton access requires no locks
37///
38/// # Thread Safety
39///
40/// ServiceProvider is fully thread-safe and can be shared across multiple threads.
41/// Singleton services are cached with proper synchronization, and the provider
42/// can be cloned cheaply (it uses `Arc` internally).
43///
44/// # Examples
45///
46/// ```
47/// use ferrous_di::{ServiceCollection, Resolver};
48/// use std::sync::Arc;
49///
50/// struct Database { url: String }
51/// struct UserService { db: Arc<Database> }
52///
53/// let mut collection = ServiceCollection::new();
54/// collection.add_singleton(Database { url: "postgres://localhost".to_string() });
55/// collection.add_transient_factory::<UserService, _>(|resolver| {
56/// UserService { db: resolver.get_required::<Database>() }
57/// });
58///
59/// let provider = collection.build();
60/// let user_service = provider.get_required::<UserService>();
61/// assert_eq!(user_service.db.url, "postgres://localhost");
62/// ```
63pub struct ServiceProvider {
64 inner: Arc<ProviderInner>,
65}
66
67pub(crate) struct ProviderInner {
68 pub registry: Registry,
69 pub singletons: Mutex<HashMap<Key, AnyArc>>, // Legacy cache for multi-bindings
70 pub fast_cache: FastSingletonCache, // High-performance singleton cache
71 pub root_disposers: Mutex<DisposeBag>,
72 pub observers: Observers,
73 pub capabilities: CapabilityRegistry,
74}
75
76impl ServiceProvider {
77 /// Convenience accessor for the inner provider
78 #[inline]
79 pub(crate) fn inner(&self) -> &ProviderInner {
80 &self.inner
81 }
82
83 /// Creates a new scope for resolving scoped services.
84 ///
85 /// Scoped services are cached per scope and are ideal for request-scoped
86 /// dependencies in web applications. Each scope maintains its own cache
87 /// of scoped services while still accessing singleton services from the
88 /// root provider.
89 ///
90 /// # Returns
91 ///
92 /// A new `Scope` that can resolve both scoped and singleton services.
93 /// The scope maintains its own cache for scoped services.
94 ///
95 /// # Examples
96 ///
97 /// ```
98 /// use ferrous_di::{ServiceCollection, Resolver};
99 /// use std::sync::{Arc, Mutex};
100 ///
101 /// #[derive(Debug)]
102 /// struct RequestId(String);
103 ///
104 /// let mut collection = ServiceCollection::new();
105 /// let counter = Arc::new(Mutex::new(0));
106 /// let counter_clone = counter.clone();
107 ///
108 /// collection.add_scoped_factory::<RequestId, _>(move |_| {
109 /// let mut c = counter_clone.lock().unwrap();
110 /// *c += 1;
111 /// RequestId(format!("req-{}", *c))
112 /// });
113 ///
114 /// let provider = collection.build();
115 ///
116 /// // Create separate scopes
117 /// let scope1 = provider.create_scope();
118 /// let scope2 = provider.create_scope();
119 ///
120 /// let req1a = scope1.get_required::<RequestId>();
121 /// let req1b = scope1.get_required::<RequestId>(); // Same instance
122 /// let req2 = scope2.get_required::<RequestId>(); // Different instance
123 ///
124 /// assert!(Arc::ptr_eq(&req1a, &req1b)); // Same scope, same instance
125 /// assert!(!Arc::ptr_eq(&req1a, &req2)); // Different scopes, different instances
126 /// ```
127 pub fn create_scope(&self) -> Scope {
128 #[cfg(feature = "once-cell")]
129 {
130 use once_cell::sync::OnceCell;
131
132 let scoped_count = self.inner().registry.scoped_count;
133 let scoped_cells: Box<[OnceCell<AnyArc>]> = (0..scoped_count)
134 .map(|_| OnceCell::new())
135 .collect::<Vec<_>>()
136 .into_boxed_slice();
137
138 Scope {
139 root: self.clone(),
140 scoped_cells,
141 scoped_disposers: Mutex::new(DisposeBag::default()),
142 }
143 }
144
145 #[cfg(not(feature = "once-cell"))]
146 {
147 Scope {
148 root: self.clone(),
149 scoped: Mutex::new(HashMap::new()),
150 scoped_disposers: Mutex::new(DisposeBag::default()),
151 }
152 }
153 }
154
155 /// Disposes all registered disposal hooks in LIFO order.
156 ///
157 /// This method runs all asynchronous disposal hooks first (in reverse order),
158 /// followed by all synchronous disposal hooks (in reverse order). This ensures
159 /// proper cleanup of singleton services.
160 ///
161 /// # Examples
162 ///
163 /// ```
164 /// use ferrous_di::{ServiceCollection, Dispose, AsyncDispose, Resolver};
165 /// use async_trait::async_trait;
166 /// use std::sync::Arc;
167 ///
168 /// struct Cache;
169 /// impl Dispose for Cache {
170 /// fn dispose(&self) {
171 /// println!("Cache disposed");
172 /// }
173 /// }
174 ///
175 /// struct Client;
176 /// #[async_trait]
177 /// impl AsyncDispose for Client {
178 /// async fn dispose(&self) {
179 /// println!("Client disposed");
180 /// }
181 /// }
182 ///
183 /// # async fn example() {
184 /// let mut services = ServiceCollection::new();
185 /// services.add_singleton_factory::<Cache, _>(|r| {
186 /// let cache = Arc::new(Cache);
187 /// r.register_disposer(cache.clone());
188 /// Cache // Return concrete type
189 /// });
190 /// services.add_singleton_factory::<Client, _>(|r| {
191 /// let client = Arc::new(Client);
192 /// r.register_async_disposer(client.clone());
193 /// Client // Return concrete type
194 /// });
195 ///
196 /// let provider = services.build();
197 /// // ... use services ...
198 /// provider.dispose_all().await;
199 /// # }
200 /// ```
201 pub async fn dispose_all(&self) {
202 // First run async disposers in reverse order
203 self.inner().root_disposers.lock().unwrap().run_all_async_reverse().await;
204 // Then run sync disposers in reverse order
205 self.inner().root_disposers.lock().unwrap().run_all_sync_reverse();
206 }
207
208 #[cfg(feature = "diagnostics")]
209 pub fn to_debug_string(&self) -> String {
210 let mut s = String::new();
211 s.push_str("=== Service Provider Debug ===\n");
212 s.push_str("Single Bindings:\n");
213 for (k, r) in self.inner().registry.iter() {
214 s.push_str(&format!(" {:?}: {:?}\n", k, r.lifetime));
215 }
216 s.push_str("Multi Bindings:\n");
217 for (k, rs) in &self.inner().registry.many {
218 for (i, r) in rs.iter().enumerate() {
219 s.push_str(&format!(" MultiTrait({} @ {}): {:?}\n", k, i, r.lifetime));
220 }
221 }
222 s
223 }
224}
225
226impl Clone for ServiceProvider {
227 fn clone(&self) -> Self {
228 Self {
229 inner: self.inner.clone(),
230 }
231 }
232}
233
234impl Drop for ServiceProvider {
235 fn drop(&mut self) {
236 // Check if this is the last reference to the inner provider
237 if Arc::strong_count(&self.inner) == 1 {
238 // Check if there are undisposed resources and warn
239 if let Ok(bag) = self.inner.root_disposers.try_lock() {
240 if !bag.is_empty() {
241 eprintln!("[ferrous-di] ServiceProvider dropped with undisposed resources. Call dispose_all().await before dropping.");
242 }
243 }
244 }
245 }
246}
247
248impl ResolverCore for ServiceProvider {
249 fn resolve_any(&self, key: &Key) -> DiResult<AnyArc> {
250 let name = key.display_name();
251 with_circular_catch(name, || self.resolve_any_impl(key))
252 }
253
254 fn resolve_many(&self, key: &Key) -> DiResult<Vec<AnyArc>> {
255 if let Key::Trait(_trait_name) = key {
256 let name = key.display_name();
257 with_circular_catch(name, || self.resolve_many_impl(key))
258 } else {
259 Ok(Vec::new())
260 }
261 }
262
263 fn push_sync_disposer(&self, f: Box<dyn FnOnce() + Send>) {
264 self.inner().root_disposers.lock().unwrap().push_sync(f);
265 }
266
267 fn push_async_disposer(&self, f: Box<dyn FnOnce() -> BoxFutureUnit + Send>) {
268 self.inner().root_disposers.lock().unwrap().push_async(move || (f)());
269 }
270}
271
272impl ServiceProvider {
273 /// Alternative high-performance singleton resolution using FastSingletonCache
274 /// This provides an alternative to the embedded OnceCell approach for scenarios
275 /// where maximum throughput is needed and error handling can be simplified
276 #[inline(always)]
277 pub fn resolve_singleton_fast_cache(&self, key: &Key) -> Option<AnyArc> {
278 // Check if we already have it in the fast cache
279 if let Some(cached) = self.inner().fast_cache.get(key) {
280 return Some(cached);
281 }
282
283 // If not cached and it's a singleton, try to initialize it
284 if let Some(reg) = self.inner().registry.get(key) {
285 if reg.lifetime == Lifetime::Singleton {
286 // Use the fast cache for ultra-high performance
287 // Note: This version doesn't propagate errors for maximum performance
288 let result = self.inner().fast_cache.get_or_init(key, || {
289 let ctx = LocalResolverContext::new(self);
290 (reg.ctor)(&ctx).unwrap_or_else(|_| Arc::new(()) as AnyArc)
291 });
292 return Some(result);
293 }
294 }
295 None
296 }
297
298 /// Ultra-optimized singleton resolution using embedded OnceCell
299 #[inline(always)]
300 pub(crate) fn resolve_singleton(&self, reg: &crate::registration::Registration, _key: &Key) -> DiResult<AnyArc> {
301 #[cfg(feature = "once-cell")]
302 {
303 if let Some(cell) = ®.single_runtime {
304 // Ultra-fast path: check if already initialized
305 if let Some(value) = cell.get() {
306 return Ok(value.clone());
307 }
308
309 // Slow path: initialize with factory (unlikely after first access)
310 // TODO: Add std::hint::unlikely when stable
311 {
312 let ctx = LocalResolverContext::new(self);
313 let v = (reg.ctor)(&ctx)?;
314 let stored = cell.get_or_init(|| v.clone()).clone();
315 return Ok(stored);
316 }
317 }
318 }
319
320 #[cfg(not(feature = "once-cell"))]
321 {
322 if let Some(mutex) = ®.single_runtime {
323 let mut guard = mutex.lock().unwrap();
324 if let Some(value) = guard.as_ref() {
325 return Ok(value.clone());
326 }
327
328 let ctx = LocalResolverContext::new(self);
329 let value = (reg.ctor)(&ctx)?;
330 *guard = Some(value.clone());
331 return Ok(value);
332 }
333 }
334
335 // Fallback to old behavior if no single_runtime (shouldn't happen)
336 let ctx = LocalResolverContext::new(self);
337 (reg.ctor)(&ctx)
338 }
339
340 /// Creates observation context from available scope-local data.
341 fn create_observation_context(&self) -> ObservationContext {
342 // Try to extract workflow context from scope-local storage
343 // This allows rich observation when workflow context is available
344 use crate::scope_local::{ScopeLocal, WorkflowContext};
345
346 // Attempt to get workflow context if available
347 if let Ok(workflow_ctx) = self.get::<ScopeLocal<WorkflowContext>>() {
348 ObservationContext::workflow(
349 workflow_ctx.run_id(),
350 workflow_ctx.workflow_name(),
351 None::<String>
352 )
353 } else {
354 // Fallback to basic context
355 ObservationContext::new()
356 }
357 }
358
359 fn resolve_any_impl(&self, key: &Key) -> DiResult<AnyArc> {
360 let name = key.display_name();
361
362 if let Some(reg) = self.inner().registry.get(key) {
363 match reg.lifetime {
364 Lifetime::Singleton => {
365 // Observer support with optimized path
366 if self.inner().observers.has_observers() {
367 let start = std::time::Instant::now();
368 let context = self.create_observation_context();
369 self.inner().observers.resolving_with_context(key, &context);
370
371 let result = self.resolve_singleton(reg, key);
372
373 let duration = start.elapsed();
374 self.inner().observers.resolved_with_context(key, duration, &context);
375 result
376 } else {
377 // Ultra-fast path: no observer overhead
378 self.resolve_singleton(reg, key)
379 }
380 }
381 Lifetime::Scoped => {
382 Err(DiError::WrongLifetime("Cannot resolve scoped service from root provider"))
383 }
384 Lifetime::Transient => {
385 if self.inner().observers.has_observers() {
386 let start = std::time::Instant::now();
387 let context = self.create_observation_context();
388 self.inner().observers.resolving_with_context(key, &context);
389
390 let ctx = LocalResolverContext::new(self);
391 let result = (reg.ctor)(&ctx);
392
393 match &result {
394 Ok(_) => {
395 let duration = start.elapsed();
396 self.inner().observers.resolved_with_context(key, duration, &context);
397 }
398 Err(_) => {
399 let duration = start.elapsed();
400 self.inner().observers.resolved_with_context(key, duration, &context);
401 }
402 }
403 result
404 } else {
405 let ctx = LocalResolverContext::new(self);
406 (reg.ctor)(&ctx)
407 }
408 }
409 }
410 } else if let Key::Trait(trait_name) = key {
411 // Fallback: if trait has multi-bindings, return last as single
412 if let Some(regs) = self.inner().registry.many.get(trait_name) {
413 if let Some(last) = regs.last() {
414 if self.inner().observers.has_observers() {
415 let start = std::time::Instant::now();
416 let context = self.create_observation_context();
417 self.inner().observers.resolving_with_context(key, &context);
418
419 let ctx = LocalResolverContext::new(self);
420 let result = (last.ctor)(&ctx);
421
422 match &result {
423 Ok(_) => {
424 let duration = start.elapsed();
425 self.inner().observers.resolved_with_context(key, duration, &context);
426 }
427 Err(_) => {
428 let duration = start.elapsed();
429 self.inner().observers.resolved_with_context(key, duration, &context);
430 }
431 }
432 result
433 } else {
434 let ctx = LocalResolverContext::new(self);
435 (last.ctor)(&ctx)
436 }
437 } else {
438 Err(DiError::NotFound(name))
439 }
440 } else {
441 Err(DiError::NotFound(name))
442 }
443 } else {
444 Err(DiError::NotFound(name))
445 }
446 }
447
448 fn resolve_many_impl(&self, key: &Key) -> DiResult<Vec<AnyArc>> {
449 if let Key::Trait(trait_name) = key {
450 if let Some(regs) = self.inner().registry.many.get(trait_name) {
451 let mut results = Vec::with_capacity(regs.len());
452
453 for (i, reg) in regs.iter().enumerate() {
454 let multi_key = Key::MultiTrait(trait_name, i);
455
456 let value = match reg.lifetime {
457 Lifetime::Singleton => {
458 // Expert fix: Double-checked locking - never hold lock while invoking factory
459 {
460 let cache = self.inner().singletons.lock().unwrap();
461 if let Some(cached) = cache.get(&multi_key) {
462 results.push(cached.clone());
463 continue;
464 }
465 } // Lock released here
466
467 // Create without holding lock
468 let ctx = ResolverContext::new(self);
469 let value = (reg.ctor)(&ctx)?;
470
471 // Double-checked insert
472 {
473 let mut cache = self.inner().singletons.lock().unwrap();
474 if let Some(cached) = cache.get(&multi_key) {
475 cached.clone() // Another thread beat us
476 } else {
477 cache.insert(multi_key, value.clone());
478 value
479 }
480 }
481 }
482 Lifetime::Scoped => {
483 return Err(DiError::WrongLifetime("Cannot resolve scoped service from root provider"));
484 }
485 Lifetime::Transient => {
486 let ctx = ResolverContext::new(self);
487 (reg.ctor)(&ctx)?
488 }
489 };
490
491 results.push(value);
492 }
493
494 Ok(results)
495 } else {
496 Ok(Vec::new())
497 }
498 } else {
499 Ok(Vec::new())
500 }
501 }
502
503 /// Create a new ServiceProvider with the given registry.
504 /// This is used internally by ServiceCollection.build().
505 #[allow(dead_code)]
506 pub(crate) fn new(registry: Registry) -> Self {
507 Self::new_with_observers_and_capabilities(registry, Observers::new(), CapabilityRegistry::new())
508 }
509
510 /// Create a new ServiceProvider with the given registry and observers.
511 /// This is used internally by ServiceCollection.build().
512 #[allow(dead_code)]
513 pub(crate) fn new_with_observers(registry: Registry, observers: Observers) -> Self {
514 Self::new_with_observers_and_capabilities(registry, observers, CapabilityRegistry::new())
515 }
516
517 /// Create a new ServiceProvider with the given registry, observers, and capabilities.
518 /// This is used internally by ServiceCollection.build().
519 pub(crate) fn new_with_observers_and_capabilities(
520 registry: Registry,
521 observers: Observers,
522 capabilities: CapabilityRegistry
523 ) -> Self {
524 Self {
525 inner: Arc::new(ProviderInner {
526 registry,
527 singletons: Mutex::new(HashMap::new()), // Legacy cache for multi-bindings
528 fast_cache: FastSingletonCache::new(), // High-performance singleton cache
529 root_disposers: Mutex::new(DisposeBag::default()),
530 observers,
531 capabilities,
532 }),
533 }
534 }
535
536 /// Discovers available tools based on capability requirements.
537 ///
538 /// This is the main entry point for agent planners to find suitable tools
539 /// for their tasks. Returns matching tools along with partial matches and
540 /// any unsatisfied requirements.
541 ///
542 /// # Examples
543 ///
544 /// ```
545 /// use ferrous_di::{ServiceCollection, ToolSelectionCriteria, CapabilityRequirement};
546 ///
547 /// // ... after registering tools with capabilities ...
548 /// let mut services = ServiceCollection::new();
549 /// let provider = services.build();
550 ///
551 /// // Find tools that can search the web
552 /// let criteria = ToolSelectionCriteria::new()
553 /// .require("web_search")
554 /// .exclude_tag("experimental")
555 /// .max_cost(0.01);
556 ///
557 /// let result = provider.discover_tools(&criteria);
558 ///
559 /// println!("Found {} matching tools", result.matching_tools.len());
560 /// for tool in &result.matching_tools {
561 /// println!(" - {}: {}", tool.name, tool.description);
562 /// }
563 ///
564 /// if !result.unsatisfied_requirements.is_empty() {
565 /// println!("Missing capabilities: {:?}", result.unsatisfied_requirements);
566 /// }
567 /// ```
568 pub fn discover_tools(&self, criteria: &ToolSelectionCriteria) -> ToolDiscoveryResult {
569 self.inner.capabilities.discover(criteria)
570 }
571
572 /// Gets all registered tools with their capability information.
573 ///
574 /// Useful for debugging or building tool catalogs.
575 pub fn list_all_tools(&self) -> Vec<&ToolInfo> {
576 self.inner.capabilities.all_tools()
577 }
578
579 /// Gets capability information for a specific tool.
580 pub fn get_tool_info(&self, key: &Key) -> Option<&ToolInfo> {
581 self.inner.capabilities.get_tool(key)
582 }
583}
584
585impl Resolver for ServiceProvider {
586 fn register_disposer<T>(&self, service: Arc<T>)
587 where
588 T: Dispose + 'static,
589 {
590 self.push_sync_disposer(Box::new(move || service.dispose()));
591 }
592
593 fn register_async_disposer<T>(&self, service: Arc<T>)
594 where
595 T: AsyncDispose + 'static,
596 {
597 self.push_async_disposer(Box::new(move || {
598 let service = service.clone();
599 Box::pin(async move { service.dispose().await })
600 }));
601 }
602}