11// Copyright (c) Autofac Project. All rights reserved.
22// Licensed under the MIT License. See LICENSE in the project root for license information.
33
4+ using System . Collections . Concurrent ;
45using System . Diagnostics ;
56using System . Globalization ;
67#if NET5_0_OR_GREATER
@@ -79,13 +80,12 @@ public class MultitenantContainer : Disposable, IContainer
7980 /// is <see cref="object"/>, value is <see cref="ILifetimeScope"/>.
8081 /// </summary>
8182 // Issue #280: Incorrect double-checked-lock pattern usage in MultitenantContainer.GetTenantScope
82- private readonly Dictionary < object , ILifetimeScope > _tenantLifetimeScopes = new ( ) ;
83+ private readonly ConcurrentDictionary < object , ILifetimeScope > _tenantLifetimeScopes = new ( ) ;
8384
8485 /// <summary>
85- /// Reader-writer lock for locking modifications and initializations
86- /// of tenant scopes.
86+ /// Flag that disallows creating a new scope while disposing or after that.
8787 /// </summary>
88- private readonly ReaderWriterLockSlim _readWriteLock = new ( ) ;
88+ private int _isDisposed ;
8989
9090 /// <summary>
9191 /// Initializes a new instance of the <see cref="MultitenantContainer"/> class.
@@ -329,28 +329,37 @@ public void ConfigureTenant(object tenantId, Action<ContainerBuilder> configurat
329329
330330 tenantId ??= _defaultTenantId ;
331331
332- _readWriteLock . EnterUpgradeableReadLock ( ) ;
333- try
332+ if ( _tenantLifetimeScopes . ContainsKey ( tenantId ) )
334333 {
335- if ( _tenantLifetimeScopes . ContainsKey ( tenantId ) )
336- {
337- throw new InvalidOperationException ( string . Format ( CultureInfo . CurrentCulture , Properties . Resources . MultitenantContainer_TenantAlreadyConfigured , tenantId ) ) ;
338- }
334+ throw new InvalidOperationException ( string . Format ( CultureInfo . CurrentCulture , Properties . Resources . MultitenantContainer_TenantAlreadyConfigured , tenantId ) ) ;
335+ }
339336
340- _readWriteLock . EnterWriteLock ( ) ;
341- try
342- {
343- _tenantLifetimeScopes [ tenantId ] = ApplicationContainer . BeginLifetimeScope ( TenantLifetimeScopeTag , configuration ) ;
344- }
345- finally
346- {
347- _readWriteLock . ExitWriteLock ( ) ;
348- }
337+ CreateTenantScope ( tenantId , configuration ) ;
338+ }
339+
340+ /// <summary>
341+ /// Creates new tenant scope without any locking. Uses optimistic approach - creates the scope and in case it fails to insert to the dictionary it's immediately disposed.
342+ /// This should happen very rarely, hopefully never.
343+ /// </summary>
344+ private ILifetimeScope CreateTenantScope ( object tenantId , Action < ContainerBuilder > configuration = null )
345+ {
346+ if ( _isDisposed == 1 )
347+ {
348+ throw new ObjectDisposedException ( nameof ( ApplicationContainer ) ) ;
349349 }
350- finally
350+
351+ var lifetimeScope = configuration != null
352+ ? ApplicationContainer . BeginLifetimeScope ( TenantLifetimeScopeTag , configuration )
353+ : ApplicationContainer . BeginLifetimeScope ( TenantLifetimeScopeTag ) ;
354+
355+ var setLifetimeScope = _tenantLifetimeScopes . GetOrAdd ( tenantId , lifetimeScope ) ;
356+
357+ if ( setLifetimeScope != lifetimeScope )
351358 {
352- _readWriteLock . ExitUpgradeableReadLock ( ) ;
359+ lifetimeScope . Dispose ( ) ;
353360 }
361+
362+ return setLifetimeScope ;
354363 }
355364
356365 /// <summary>
@@ -393,26 +402,16 @@ public bool ReconfigureTenant(object tenantId, Action<ContainerBuilder> configur
393402
394403 tenantId ??= _defaultTenantId ;
395404
396- // we're going to change the dictionary either way, dispense with the read-check
397- _readWriteLock . EnterWriteLock ( ) ;
398- try
405+ var removed = false ;
406+ if ( _tenantLifetimeScopes . TryRemove ( tenantId , out var tenantScope ) )
399407 {
400- var removed = false ;
401- if ( _tenantLifetimeScopes . TryGetValue ( tenantId , out var tenantScope ) && tenantScope != null )
402- {
403- tenantScope . Dispose ( ) ;
404-
405- removed = _tenantLifetimeScopes . Remove ( tenantId ) ;
406- }
408+ tenantScope . Dispose ( ) ;
409+ removed = true ;
410+ }
407411
408- _tenantLifetimeScopes [ tenantId ] = ApplicationContainer . BeginLifetimeScope ( TenantLifetimeScopeTag , configuration ) ;
412+ CreateTenantScope ( tenantId , configuration ) ;
409413
410- return removed ;
411- }
412- finally
413- {
414- _readWriteLock . ExitWriteLock ( ) ;
415- }
414+ return removed ;
416415 }
417416
418417 /// <summary>
@@ -449,57 +448,23 @@ public ILifetimeScope GetTenantScope(object tenantId)
449448 {
450449 tenantId ??= _defaultTenantId ;
451450
452- var tenantScope = ( ILifetimeScope ) null ;
453- _readWriteLock . EnterReadLock ( ) ;
454- try
451+ if ( _tenantLifetimeScopes . TryGetValue ( tenantId , out var tenantScope ) )
455452 {
456- _tenantLifetimeScopes . TryGetValue ( tenantId , out tenantScope ) ;
457- }
458- finally
459- {
460- _readWriteLock . ExitReadLock ( ) ;
461- }
462-
463- if ( tenantScope == null )
464- {
465- // just go straight to write-lock, chances of not needing it at this point would be low
466- _readWriteLock . EnterWriteLock ( ) ;
467-
468- try
469- {
470- // The check and [potential] scope creation are locked here to
471- // ensure atomicity. We don't want to check and then have another
472- // thread create the lifetime scope behind our backs.
473- if ( ! _tenantLifetimeScopes . TryGetValue ( tenantId , out tenantScope ) || tenantScope == null )
474- {
475- tenantScope = ApplicationContainer . BeginLifetimeScope ( TenantLifetimeScopeTag ) ;
476- _tenantLifetimeScopes [ tenantId ] = tenantScope ;
477- }
478- }
479- finally
480- {
481- _readWriteLock . ExitWriteLock ( ) ;
482- }
453+ return tenantScope ;
483454 }
484455
485- return tenantScope ;
456+ return CreateTenantScope ( tenantId ) ;
486457 }
487458
488459 /// <summary>
489460 /// Returns collection of all registered tenants IDs.
490461 /// </summary>
462+ #pragma warning disable CA1024
491463 public IEnumerable < object > GetTenants ( )
492464 {
493- _readWriteLock . EnterReadLock ( ) ;
494- try
495- {
496- return new List < object > ( _tenantLifetimeScopes . Keys ) ;
497- }
498- finally
499- {
500- _readWriteLock . ExitReadLock ( ) ;
501- }
465+ return new List < object > ( _tenantLifetimeScopes . Keys ) ;
502466 }
467+ #pragma warning restore CA1024
503468
504469 /// <summary>
505470 /// Returns whether the given tenant ID has been configured.
@@ -508,17 +473,9 @@ public IEnumerable<object> GetTenants()
508473 /// <returns>If configured, <c>true</c>; otherwise <c>false</c>.</returns>
509474 public bool TenantIsConfigured ( object tenantId )
510475 {
511- _readWriteLock . EnterReadLock ( ) ;
512- try
513- {
514- tenantId ??= _defaultTenantId ;
476+ tenantId ??= _defaultTenantId ;
515477
516- return _tenantLifetimeScopes . ContainsKey ( tenantId ) ;
517- }
518- finally
519- {
520- _readWriteLock . ExitReadLock ( ) ;
521- }
478+ return _tenantLifetimeScopes . ContainsKey ( tenantId ) ;
522479 }
523480
524481 /// <summary>
@@ -530,43 +487,27 @@ public bool RemoveTenant(object tenantId)
530487 {
531488 tenantId ??= _defaultTenantId ;
532489
533- // this should be a fairly rare operation, so we'll jump right to the write-lock
534- _readWriteLock . EnterWriteLock ( ) ;
535- try
490+ if ( _tenantLifetimeScopes . TryRemove ( tenantId , out var tenantScope ) )
536491 {
537- if ( _tenantLifetimeScopes . TryGetValue ( tenantId , out var tenantScope ) && tenantScope != null )
538- {
539- tenantScope . Dispose ( ) ;
492+ tenantScope . Dispose ( ) ;
540493
541- return _tenantLifetimeScopes . Remove ( tenantId ) ;
542- }
543-
544- return false ;
545- }
546- finally
547- {
548- _readWriteLock . ExitWriteLock ( ) ;
494+ return true ;
549495 }
496+
497+ return false ;
550498 }
551499
552500 /// <summary>
553501 /// Clears all tenants configurations and disposes the associated lifetime scopes.
554502 /// </summary>
555503 public void ClearTenants ( )
556504 {
557- _readWriteLock . EnterWriteLock ( ) ;
558- try
505+ foreach ( var tenantId in _tenantLifetimeScopes . Keys )
559506 {
560- foreach ( var tenantScope in _tenantLifetimeScopes . Values )
507+ if ( _tenantLifetimeScopes . TryRemove ( tenantId , out var tenantScope ) )
561508 {
562509 tenantScope . Dispose ( ) ;
563510 }
564-
565- _tenantLifetimeScopes . Clear ( ) ;
566- }
567- finally
568- {
569- _readWriteLock . ExitWriteLock ( ) ;
570511 }
571512 }
572513
@@ -598,26 +539,16 @@ public object ResolveComponent(ResolveRequest request)
598539 /// </param>
599540 protected override void Dispose ( bool disposing )
600541 {
542+ Interlocked . Exchange ( ref _isDisposed , 1 ) ;
543+
601544 if ( disposing )
602545 {
603- // Lock the lifetime scope table so no threads can add new lifetime
604- // scopes while we're disposing.
605- _readWriteLock . EnterWriteLock ( ) ;
606-
607- try
608- {
609- foreach ( var scope in _tenantLifetimeScopes . Values )
610- {
611- scope . Dispose ( ) ;
612- }
613-
614- ApplicationContainer . Dispose ( ) ;
615- }
616- finally
546+ foreach ( var scope in _tenantLifetimeScopes . Values )
617547 {
618- _readWriteLock . ExitWriteLock ( ) ;
619- _readWriteLock . Dispose ( ) ;
548+ scope . Dispose ( ) ;
620549 }
550+
551+ ApplicationContainer . Dispose ( ) ;
621552 }
622553
623554 base . Dispose ( disposing ) ;
@@ -632,24 +563,16 @@ protected override void Dispose(bool disposing)
632563 /// </param>
633564 protected override async ValueTask DisposeAsync ( bool disposing )
634565 {
566+ Interlocked . Exchange ( ref _isDisposed , 1 ) ;
567+
635568 if ( disposing )
636569 {
637- _readWriteLock . EnterWriteLock ( ) ;
638-
639- try
570+ foreach ( var scope in _tenantLifetimeScopes . Values )
640571 {
641- foreach ( var scope in _tenantLifetimeScopes . Values )
642- {
643- await scope . DisposeAsync ( ) . ConfigureAwait ( false ) ;
644- }
645-
646- await ApplicationContainer . DisposeAsync ( ) . ConfigureAwait ( false ) ;
647- }
648- finally
649- {
650- _readWriteLock . ExitWriteLock ( ) ;
651- _readWriteLock . Dispose ( ) ;
572+ await scope . DisposeAsync ( ) . ConfigureAwait ( false ) ;
652573 }
574+
575+ await ApplicationContainer . DisposeAsync ( ) . ConfigureAwait ( false ) ;
653576 }
654577
655578 // Do not call the base, otherwise the standard Dispose will fire.
0 commit comments