| File: System\Composition\Hosting\Core\LifetimeContext.cs | Web Access |
| Project: src\runtime\src\libraries\System.Composition.Hosting\src\System.Composition.Hosting.csproj (System.Composition.Hosting) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.Composition.Hosting.Util; using System.Diagnostics; using System.Threading; namespace System.Composition.Hosting.Core { /// <summary> /// Represents a node in the lifetime tree. A <see cref="LifetimeContext"/> is the unit of /// sharing for shared parts, controls the disposal of bound parts, and can be used to retrieve /// instances either as part of an existing <see cref="CompositionOperation"/> or as the basis of a new /// composition operation. An individual lifetime context can be marked to contain parts that are /// constrained by particular sharing boundaries. /// </summary> /// <remarks> /// Contains two pieces of _independently protected_ shared state. Shared part instances is /// lock-free-readable and does not result in issues if added to during disposal. It is protected /// by being locked itself. Activation logic is unavoidably called under this lock. /// Bound part instances is always protected, by locking [this], and should never be written to /// after disposal and so is set to null under a lock in Dispose(). If it were allowed it would result in /// disposable parts not being released. Dispose methods on parts are called outside the lock. /// </remarks> /// <seealso cref="Export{T}"/> public sealed class LifetimeContext : CompositionContext, IDisposable { private readonly LifetimeContext _root; private readonly LifetimeContext _parent; // Protects the two members holding shared instances private readonly object _sharingLock = new object(); private SmallSparseInitonlyArray _sharedPartInstances, _instancesUndergoingInitialization; // Protects the member holding bound instances. private readonly object _boundPartLock = new object(); private List<IDisposable> _boundPartInstances = new List<IDisposable>(0); private readonly string[] _sharingBoundaries; private readonly ExportDescriptorRegistry _partRegistry; private static int s_nextSharingId = -1; /// <summary> /// Generates an identifier that can be used to locate shared part instances. /// </summary> /// <returns>A new unique identifier.</returns> public static int AllocateSharingId() { return Interlocked.Increment(ref s_nextSharingId); } internal LifetimeContext(ExportDescriptorRegistry partRegistry, string[] sharingBoundaries) { _root = this; _sharingBoundaries = sharingBoundaries; _partRegistry = partRegistry; } internal LifetimeContext(LifetimeContext parent, string[] sharingBoundaries) { _parent = parent; _root = parent._root; _sharingBoundaries = sharingBoundaries; _partRegistry = parent._partRegistry; } /// <summary> /// Find the broadest lifetime context within all of the specified sharing boundaries. /// </summary> /// <param name="sharingBoundary">The sharing boundary to find a lifetime context within.</param> /// <returns>The broadest lifetime context within all of the specified sharing boundaries.</returns> /// <remarks>Currently, the root cannot be a boundary.</remarks> public LifetimeContext FindContextWithin(string sharingBoundary) { if (sharingBoundary == null) return _root; var toCheck = this; while (toCheck != null) { foreach (var implemented in toCheck._sharingBoundaries) { if (implemented == sharingBoundary) return toCheck; } toCheck = toCheck._parent; } // To generate acceptable error messages here we're going to need to pass in a description // of the component, or otherwise find a way to get one. string message = SR.Format(SR.Component_NotCreatableOutsideSharingBoundary, sharingBoundary); var ex = new CompositionFailedException(message); Debug.WriteLine(SR.Diagnostic_ThrowingException, ex.ToString()); throw ex; } /// <summary> /// Release the lifetime context and any disposable part instances /// that are bound to it. /// </summary> public void Dispose() { IEnumerable<IDisposable> toDispose = null; lock (_boundPartLock) { if (_boundPartInstances != null) { toDispose = _boundPartInstances; _boundPartInstances = null; } } if (toDispose != null) { foreach (var instance in toDispose) instance.Dispose(); } } /// <summary> /// Bind the lifetime of a disposable part to the current /// lifetime context. /// </summary> /// <param name="instance">The disposable part to bind.</param> public void AddBoundInstance(IDisposable instance) { lock (_boundPartLock) { if (_boundPartInstances == null) throw new ObjectDisposedException(ToString()); _boundPartInstances.Add(instance); } } /// <summary> /// Either retrieve an existing part instance with the specified sharing id, or /// create and share a new part instance using <paramref name="creator"/> within /// <paramref name="operation"/>. /// </summary> /// <param name="sharingId">Sharing id for the part in question.</param> /// <param name="operation">Operation in which to activate a new part instance if necessary.</param> /// <param name="creator">Activator that can activate a new part instance if necessary.</param> /// <returns>The part instance corresponding to <paramref name="sharingId"/> within this lifetime context.</returns> /// <remarks>This method is lock-free if the part instance already exists. If the part instance must be created, /// a lock will be taken that will serialize other writes via this method (concurrent reads will continue to /// be safe and lock-free). It is important that the composition, and thus lock acquisition, is strictly /// leaf-to-root in the lifetime tree.</remarks> public object GetOrCreate(int sharingId, CompositionOperation operation, CompositeActivator creator) { object result; if (_sharedPartInstances != null && _sharedPartInstances.TryGetValue(sharingId, out result)) return result; // Remains locked for the rest of the operation. operation.EnterSharingLock(_sharingLock); if (_sharedPartInstances == null) { _sharedPartInstances = new SmallSparseInitonlyArray(); _instancesUndergoingInitialization = new SmallSparseInitonlyArray(); } else if (_sharedPartInstances.TryGetValue(sharingId, out result)) { return result; } // Already being initialized _on the same thread_. if (_instancesUndergoingInitialization.TryGetValue(sharingId, out result)) return result; result = creator(this, operation); _instancesUndergoingInitialization.Add(sharingId, result); operation.AddPostCompositionAction(() => { _sharedPartInstances.Add(sharingId, result); }); return result; } /// <summary> /// Retrieve the single <paramref name="contract"/> instance from the /// <see cref="CompositionContext"/>. /// </summary> /// <param name="contract">The contract to retrieve.</param> /// <returns>An instance of the export.</returns> /// <param name="export">The export if available, otherwise, null.</param> /// <exception cref="CompositionFailedException" /> public override bool TryGetExport(CompositionContract contract, out object export) { ExportDescriptor defaultForExport; if (!_partRegistry.TryGetSingleForExport(contract, out defaultForExport)) { export = null; return false; } export = CompositionOperation.Run(this, defaultForExport.Activator); return true; } /// <summary> /// Describes this lifetime context. /// </summary> /// <returns>A string description.</returns> public override string ToString() { if (_parent == null) return "Root Lifetime Context"; if (_sharingBoundaries.Length == 0) return "Non-sharing Lifetime Context"; return "Boundary for " + string.Join(", ", _sharingBoundaries); } } }