@@ -264,6 +264,12 @@ protected override async ValueTask DisposeAsync(bool disposing)
264264 // Do not call the base, otherwise the standard Dispose will fire.
265265 }
266266
267+ /// <summary>
268+ /// Filters registration sources to skip a single source.
269+ /// </summary>
270+ /// <param name="sources">The source sequence to scan.</param>
271+ /// <param name="exclude">The source to exclude.</param>
272+ /// <returns>Sources that are not the excluded instance.</returns>
267273 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
268274 private static IEnumerable < IRegistrationSource > ExcludeSource ( IEnumerable < IRegistrationSource > sources , IRegistrationSource exclude )
269275 {
@@ -276,6 +282,33 @@ private static IEnumerable<IRegistrationSource> ExcludeSource(IEnumerable<IRegis
276282 }
277283 }
278284
285+ /// <summary>
286+ /// Acquires the service info lock and records wait time when metrics are enabled.
287+ /// </summary>
288+ /// <param name="info">The service info lock target.</param>
289+ /// <param name="instrumentationService">Optional detail about the service for metrics.</param>
290+ /// <param name="lockTaken">Tracks whether the lock was acquired.</param>
291+ private static void EnterServiceInfoLock ( ServiceRegistrationInfo info , string ? instrumentationService , ref bool lockTaken )
292+ {
293+ if ( AutofacMetrics . MetricsEnabled )
294+ {
295+ var wait = ValueStopwatch . StartNew ( ) ;
296+ Monitor . Enter ( info , ref lockTaken ) ;
297+ AutofacMetrics . RecordLockContention ( "Service" , instrumentationService , wait . ElapsedTicks ) ;
298+ }
299+ else
300+ {
301+ Monitor . Enter ( info , ref lockTaken ) ;
302+ }
303+ }
304+
305+ /// <summary>
306+ /// Gets or creates an ephemeral service info entry used during pre-complete initialization.
307+ /// </summary>
308+ /// <param name="ephemeralSet">The ephemeral map for this initialization pass.</param>
309+ /// <param name="service">The service key.</param>
310+ /// <param name="info">The baseline service info to clone if needed.</param>
311+ /// <returns>An ephemeral service info entry for the service.</returns>
279312 private static ServiceRegistrationInfo GetEphemeralServiceInfo ( Dictionary < Service , ServiceRegistrationInfo > ephemeralSet , Service service , ServiceRegistrationInfo info )
280313 {
281314 if ( ephemeralSet . TryGetValue ( service , out var ephemeral ) )
@@ -290,112 +323,59 @@ private static ServiceRegistrationInfo GetEphemeralServiceInfo(Dictionary<Servic
290323 return newCopy ;
291324 }
292325
293- private ServiceRegistrationInfo GetInitializedServiceInfo ( Service service )
326+ /// <summary>
327+ /// Unwraps scope-isolated services and notes when isolation applies.
328+ /// </summary>
329+ /// <param name="service">The service to inspect.</param>
330+ /// <param name="isScopeIsolatedService">Set to <see langword="true"/> when the service is scope isolated.</param>
331+ /// <returns>The inner service to process.</returns>
332+ private static Service ResolveScopeIsolation ( Service service , ref bool isScopeIsolatedService )
294333 {
295- var createdEphemeralSet = false ;
296- var isScopeIsolatedService = false ;
297-
298334 if ( service is ScopeIsolatedService scopeIsolatedService )
299335 {
300336 // This is an isolated service query; use the wrapped service instead and
301337 // remember that fact for later.
302338 isScopeIsolatedService = true ;
303- service = scopeIsolatedService . Service ;
339+ return scopeIsolatedService . Service ;
304340 }
305341
342+ return service ;
343+ }
344+
345+ /// <summary>
346+ /// Ensures the service info is initialized and returns it.
347+ /// </summary>
348+ /// <param name="service">The service being queried.</param>
349+ /// <returns>The initialized service info.</returns>
350+ private ServiceRegistrationInfo GetInitializedServiceInfo ( Service service )
351+ {
352+ var createdEphemeralSet = false ;
353+ var isScopeIsolatedService = false ;
354+
355+ service = ResolveScopeIsolation ( service , ref isScopeIsolatedService ) ;
356+
306357 var info = GetServiceInfo ( service ) ;
307358 var instrumentationService = AutofacMetrics . MetricsEnabled ? service . ToString ( ) : null ;
308359 if ( info . IsInitialized )
309360 {
310361 return info ;
311362 }
312363
313- if ( ! _trackerPopulationComplete )
314- {
315- // We need an ephemeral set for this pre-complete initialization.
316- if ( _ephemeralServiceInfo is null )
317- {
318- _ephemeralServiceInfo = new Dictionary < Service , ServiceRegistrationInfo > ( ) ;
319- createdEphemeralSet = true ;
320- }
321-
322- info = GetEphemeralServiceInfo ( _ephemeralServiceInfo , service , info ) ;
323- }
364+ info = GetServiceInfoForInitialization ( service , info , ref createdEphemeralSet ) ;
324365
325366 var succeeded = false ;
326367 var lockTaken = false ;
327368 try
328369 {
329- if ( AutofacMetrics . MetricsEnabled )
330- {
331- var wait = ValueStopwatch . StartNew ( ) ;
332- Monitor . Enter ( info , ref lockTaken ) ;
333- AutofacMetrics . RecordLockContention ( "Service" , instrumentationService , wait . ElapsedTicks ) ;
334- }
335- else
336- {
337- Monitor . Enter ( info , ref lockTaken ) ;
338- }
370+ EnterServiceInfoLock ( info , instrumentationService , ref lockTaken ) ;
339371
340372 if ( info . IsInitialized )
341373 {
342374 return info ;
343375 }
344376
345- if ( ! info . IsInitializing )
346- {
347- BeginServiceInfoInitialization ( service , info , _dynamicRegistrationSources ) ;
348- }
349-
350- info . InitializationDepth ++ ;
351-
352- while ( info . HasSourcesToQuery )
353- {
354- var next = info . DequeueNextSource ( ) ;
355-
356- // Do not query per-scope registration sources
357- // for isolated services.
358- if ( isScopeIsolatedService && next is IPerScopeRegistrationSource )
359- {
360- continue ;
361- }
362-
363- foreach ( var provided in next . RegistrationsFor ( service , _registrationAccessor ) )
364- {
365- // This ensures that multiple services provided by the same
366- // component share a single component (we don't re-query for them)
367- foreach ( var additionalService in provided . Services )
368- {
369- var additionalInfo = GetServiceInfo ( additionalService ) ;
370- if ( additionalInfo . IsInitialized || additionalInfo == info )
371- {
372- continue ;
373- }
374-
375- if ( _ephemeralServiceInfo is not null )
376- {
377- // Use ephemeral info for additional services.
378- additionalInfo = GetEphemeralServiceInfo ( _ephemeralServiceInfo , service , info ) ;
379- }
380-
381- if ( ! additionalInfo . IsInitializing )
382- {
383- BeginServiceInfoInitialization ( additionalService , additionalInfo , ExcludeSource ( _dynamicRegistrationSources , next ) ) ;
384- }
385- else
386- {
387- additionalInfo . SkipSource ( next ) ;
388- }
389- }
390-
391- AddRegistration (
392- provided ,
393- preserveDefaults : true ,
394- originatedFromDynamicSource : true ) ;
395- }
396- }
397-
398- succeeded = true ;
377+ // PopulateServiceInfo increments InitializationDepth; the decrement is paired in finally.
378+ succeeded = PopulateServiceInfo ( service , info , isScopeIsolatedService ) ;
399379 }
400380 finally
401381 {
@@ -421,8 +401,8 @@ private ServiceRegistrationInfo GetInitializedServiceInfo(Service service)
421401 Monitor . Exit ( info ) ;
422402 }
423403
424- // This method was the entry point to an ephemeral service info initialization.
425- // We need to discard it, so the next set of ephemeral service info is done from scratch .
404+ // This method was the entry point to an ephemeral initialization pass .
405+ // Discard the temporary map so later calls start with a clean slate .
426406 if ( createdEphemeralSet )
427407 {
428408 _ephemeralServiceInfo ? . Clear ( ) ;
@@ -433,6 +413,102 @@ private ServiceRegistrationInfo GetInitializedServiceInfo(Service service)
433413 return info ;
434414 }
435415
416+ /// <summary>
417+ /// Returns the appropriate service info for initialization, swapping to an ephemeral copy when needed.
418+ /// </summary>
419+ /// <param name="service">The service being queried.</param>
420+ /// <param name="info">The current service info.</param>
421+ /// <param name="createdEphemeralSet">Set to <see langword="true"/> when a new ephemeral set is created.</param>
422+ /// <returns>The service info to use for initialization.</returns>
423+ private ServiceRegistrationInfo GetServiceInfoForInitialization ( Service service , ServiceRegistrationInfo info , ref bool createdEphemeralSet )
424+ {
425+ if ( ! _trackerPopulationComplete )
426+ {
427+ // We need an ephemeral set for this pre-complete initialization.
428+ if ( _ephemeralServiceInfo is null )
429+ {
430+ _ephemeralServiceInfo = new Dictionary < Service , ServiceRegistrationInfo > ( ) ;
431+ createdEphemeralSet = true ;
432+ }
433+
434+ info = GetEphemeralServiceInfo ( _ephemeralServiceInfo , service , info ) ;
435+ }
436+
437+ return info ;
438+ }
439+
440+ /// <summary>
441+ /// Populates service info by querying registration sources and adding derived registrations.
442+ /// </summary>
443+ /// <param name="service">The service being initialized.</param>
444+ /// <param name="info">The service info to populate.</param>
445+ /// <param name="isScopeIsolatedService"><see langword="true"/> when per-scope sources should be skipped.</param>
446+ /// <returns><see langword="true"/> when initialization completes.</returns>
447+ private bool PopulateServiceInfo ( Service service , ServiceRegistrationInfo info , bool isScopeIsolatedService )
448+ {
449+ if ( ! info . IsInitializing )
450+ {
451+ BeginServiceInfoInitialization ( service , info , _dynamicRegistrationSources ) ;
452+ }
453+
454+ info . InitializationDepth ++ ;
455+
456+ // Drain sources in-order; registrations can enqueue additional sources.
457+ while ( info . HasSourcesToQuery )
458+ {
459+ var next = info . DequeueNextSource ( ) ;
460+
461+ // Do not query per-scope registration sources
462+ // for isolated services.
463+ if ( isScopeIsolatedService && next is IPerScopeRegistrationSource )
464+ {
465+ continue ;
466+ }
467+
468+ foreach ( var provided in next . RegistrationsFor ( service , _registrationAccessor ) )
469+ {
470+ // This ensures that multiple services provided by the same
471+ // component share a single component (we don't re-query for them)
472+ foreach ( var additionalService in provided . Services )
473+ {
474+ var additionalInfo = GetServiceInfo ( additionalService ) ;
475+ if ( additionalInfo . IsInitialized || additionalInfo == info )
476+ {
477+ continue ;
478+ }
479+
480+ if ( _ephemeralServiceInfo is not null )
481+ {
482+ // Use ephemeral info for additional services.
483+ additionalInfo = GetEphemeralServiceInfo ( _ephemeralServiceInfo , service , info ) ;
484+ }
485+
486+ if ( ! additionalInfo . IsInitializing )
487+ {
488+ BeginServiceInfoInitialization ( additionalService , additionalInfo , ExcludeSource ( _dynamicRegistrationSources , next ) ) ;
489+ }
490+ else
491+ {
492+ additionalInfo . SkipSource ( next ) ;
493+ }
494+ }
495+
496+ AddRegistration (
497+ provided ,
498+ preserveDefaults : true ,
499+ originatedFromDynamicSource : true ) ;
500+ }
501+ }
502+
503+ return true ;
504+ }
505+
506+ /// <summary>
507+ /// Seeds service info with middleware and registration sources.
508+ /// </summary>
509+ /// <param name="service">The service being initialized.</param>
510+ /// <param name="info">The service info to update.</param>
511+ /// <param name="registrationSources">Sources to query for registrations.</param>
436512 private void BeginServiceInfoInitialization ( Service service , ServiceRegistrationInfo info , IEnumerable < IRegistrationSource > registrationSources )
437513 {
438514 // Add any additional service pipeline configuration from external sources.
@@ -444,6 +520,11 @@ private void BeginServiceInfoInitialization(Service service, ServiceRegistration
444520 info . BeginInitialization ( registrationSources ) ;
445521 }
446522
523+ /// <summary>
524+ /// Gets or creates the service info entry for a service key.
525+ /// </summary>
526+ /// <param name="service">The service key.</param>
527+ /// <returns>The service info entry.</returns>
447528 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
448529 private ServiceRegistrationInfo GetServiceInfo ( Service service )
449530 {
0 commit comments