diff --git a/package.props b/package.props index fd43fc3..0f20fe3 100644 --- a/package.props +++ b/package.props @@ -1,7 +1,7 @@ - 1.0.0 + 2.0.0 This package is distributed as .NET Standard 1.0 package. It is compatible with Microsoft.Extensions.DependencyInjection.Abstractions specification. diff --git a/src/Configuration.cs b/src/Configuration.cs index 09b7108..2e6db8e 100644 --- a/src/Configuration.cs +++ b/src/Configuration.cs @@ -80,7 +80,7 @@ internal static LifetimeManager GetLifetime(this ServiceDescriptor serviceDescri case ServiceLifetime.Scoped: return new HierarchicalLifetimeManager(); case ServiceLifetime.Singleton: - return new ContainerControlledLifetimeManager(); + return new InjectionSingletonLifetimeManager(lifetime); case ServiceLifetime.Transient: return new InjectionTransientLifetimeManager(); default: diff --git a/src/Lifetime/InjectionSingletonLifetimeManager.cs b/src/Lifetime/InjectionSingletonLifetimeManager.cs index 4e12918..6dd33d9 100644 --- a/src/Lifetime/InjectionSingletonLifetimeManager.cs +++ b/src/Lifetime/InjectionSingletonLifetimeManager.cs @@ -1,35 +1,123 @@ using System; +using System.Threading; +using Unity.Exceptions; using Unity.Lifetime; namespace Unity.Microsoft.DependencyInjection.Lifetime { - public class InjectionSingletonLifetimeManager : ContainerControlledLifetimeManager + public class InjectionSingletonLifetimeManager : LifetimeManager, IRequiresRecovery + { #region Fields - private ILifetimeContainer _lifetime; - + private readonly object _lockObj = new object(); + private readonly ILifetimeContainer _container; + private object _value; + #endregion - public InjectionSingletonLifetimeManager(ILifetimeContainer lifetime) + #region Constructors + + public InjectionSingletonLifetimeManager(ILifetimeContainer container) { - _lifetime = lifetime; + _container = container ?? throw new ArgumentNullException(nameof(container)); } + #endregion + - protected override void SynchronizedSetValue(object newValue, ILifetimeContainer container = null) + #region LifetimeManager + + /// + /// Remove the given object from backing store. + /// + /// Instance of container + public override void RemoveValue(ILifetimeContainer container = null) { - base.SynchronizedSetValue(newValue, container); - _lifetime.Add(new DisposableAction(() => RemoveValue(_lifetime))); + if (_value == null) return; + if (_value is IDisposable disposable) + { + disposable.Dispose(); + } + _value = null; } protected override LifetimeManager OnCreateLifetimeManager() { - return new InjectionSingletonLifetimeManager(_lifetime); + return new InjectionSingletonLifetimeManager(_container); + } + + + /// + /// Retrieve a value from the backing store associated with this Lifetime policy. + /// + /// the object desired, or null if no such object is currently stored. + /// Calls to this method acquire a lock which is released only if a non-null value + /// has been set for the lifetime manager. + public override object GetValue(ILifetimeContainer container = null) + { + Monitor.Enter(_lockObj); + if (_value != null) + { + Monitor.Exit(_lockObj); + } + return _value; } + /// + /// Stores the given value into backing store for retrieval later. + /// + /// The object being stored. + /// The container this value belongs to. + /// Setting a value will attempt to release the lock acquired by + /// . + public override void SetValue(object newValue, ILifetimeContainer container = null) + { + _value = newValue; + if (_value is IDisposable) _container.Add(new DisposableAction(() => RemoveValue(_container))); + TryExit(); + } + + #endregion + + + #region IRequiresRecovery + + /// + /// A method that does whatever is needed to clean up + /// as part of cleaning up after an exception. + /// + /// + /// Don't do anything that could throw in this method, + /// it will cause later recover operations to get skipped + /// and play real havoc with the stack trace. + /// + public void Recover() + { + TryExit(); + } + + protected virtual void TryExit() + { +#if !NET40 + // Prevent first chance exception when abandoning a lock that has not been entered + if (!Monitor.IsEntered(_lockObj)) return; +#endif + try + { + Monitor.Exit(_lockObj); + } + catch (SynchronizationLockException) + { + // Noop here - we don't hold the lock and that's ok. + } + } + + #endregion + + #region Nested Types private class DisposableAction : IDisposable @@ -48,6 +136,5 @@ public void Dispose() } #endregion - } } diff --git a/tests/UnityDependencyInjectionTests.cs b/tests/UnityDependencyInjectionTests.cs index 0d9e4f5..9acaf77 100644 --- a/tests/UnityDependencyInjectionTests.cs +++ b/tests/UnityDependencyInjectionTests.cs @@ -40,7 +40,7 @@ public void Disposes_InReverseOrderOfCreation() // Assert Assert.Equal(outer, callback.Disposed[0]); - Assert.Equal(multipleServices.Reverse(), callback.Disposed.Skip(1).Take(3).OfType()); + Assert.Equal(outer.MultipleServices.Reverse(), callback.Disposed.Skip(1).Take(3).OfType()); Assert.Equal(outer.SingleService, callback.Disposed[4]); }