1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Collections.Generic; 6 using System.Composition.Hosting.Util; 7 using System.Threading; 8 using Microsoft.Internal; 9 10 namespace System.Composition.Hosting.Core 11 { 12 /// <summary> 13 /// Represents a node in the lifetime tree. A <see cref="LifetimeContext"/> is the unit of 14 /// sharing for shared parts, controls the disposal of bound parts, and can be used to retrieve 15 /// instances either as part of an existing <see cref="CompositionOperation"/> or as the basis of a new 16 /// composition operation. An individual lifetime context can be marked to contain parts that are 17 /// constrained by particular sharing boundaries. 18 /// </summary> 19 /// <remarks> 20 /// Contains two pieces of _independently protected_ shared state. Shared part instances is 21 /// lock-free-readable and does not result in issues if added to during disposal. It is protected 22 /// by being locked itself. Activation logic is unavoidably called under this lock. 23 /// Bound part instances is always protected, by locking [this], and should never be written to 24 /// after disposal and so is set to null under a lock in Dispose(). If it were allowed it would result in 25 /// disposable parts not being released. Dispose methods on parts are called outside the lock. 26 /// </remarks> 27 /// <seealso cref="Export{T}"/> 28 public sealed class LifetimeContext : CompositionContext, IDisposable 29 { 30 private readonly LifetimeContext _root; 31 private readonly LifetimeContext _parent; 32 33 // Protects the two members holding shared instances 34 private readonly object _sharingLock = new object(); 35 private SmallSparseInitonlyArray _sharedPartInstances, _instancesUndergoingInitialization; 36 37 // Protects the member holding bound instances. 38 private readonly object _boundPartLock = new object(); 39 private List<IDisposable> _boundPartInstances = new List<IDisposable>(0); 40 41 private readonly string[] _sharingBoundaries; 42 private readonly ExportDescriptorRegistry _partRegistry; 43 44 private static int s_nextSharingId = -1; 45 46 /// <summary> 47 /// Generates an identifier that can be used to locate shared part instances. 48 /// </summary> 49 /// <returns>A new unique identifier.</returns> AllocateSharingId()50 public static int AllocateSharingId() 51 { 52 return Interlocked.Increment(ref s_nextSharingId); 53 } 54 LifetimeContext(ExportDescriptorRegistry partRegistry, string[] sharingBoundaries)55 internal LifetimeContext(ExportDescriptorRegistry partRegistry, string[] sharingBoundaries) 56 { 57 _root = this; 58 _sharingBoundaries = sharingBoundaries; 59 _partRegistry = partRegistry; 60 } 61 LifetimeContext(LifetimeContext parent, string[] sharingBoundaries)62 internal LifetimeContext(LifetimeContext parent, string[] sharingBoundaries) 63 { 64 _parent = parent; 65 _root = parent._root; 66 _sharingBoundaries = sharingBoundaries; 67 _partRegistry = parent._partRegistry; 68 } 69 70 /// <summary> 71 /// Find the broadest lifetime context within all of the specified sharing boundaries. 72 /// </summary> 73 /// <param name="sharingBoundary">The sharing boundary to find a lifetime context within.</param> 74 /// <returns>The broadest lifetime context within all of the specified sharing boundaries.</returns> 75 /// <remarks>Currently, the root cannot be a boundary.</remarks> FindContextWithin(string sharingBoundary)76 public LifetimeContext FindContextWithin(string sharingBoundary) 77 { 78 if (sharingBoundary == null) 79 return _root; 80 81 var toCheck = this; 82 while (toCheck != null) 83 { 84 foreach (var implemented in toCheck._sharingBoundaries) 85 { 86 if (implemented == sharingBoundary) 87 return toCheck; 88 } 89 90 toCheck = toCheck._parent; 91 } 92 93 // To generate acceptable error messages here we're going to need to pass in a description 94 // of the component, or otherwise find a way to get one. 95 throw ThrowHelper.CompositionException(SR.Format(SR.Component_NotCreatableOutsideSharingBoundary, sharingBoundary)); 96 } 97 98 /// <summary> 99 /// Release the lifetime context and any disposable part instances 100 /// that are bound to it. 101 /// </summary> Dispose()102 public void Dispose() 103 { 104 IEnumerable<IDisposable> toDispose = null; 105 lock (_boundPartLock) 106 { 107 if (_boundPartInstances != null) 108 { 109 toDispose = _boundPartInstances; 110 _boundPartInstances = null; 111 } 112 } 113 114 if (toDispose != null) 115 { 116 foreach (var instance in toDispose) 117 instance.Dispose(); 118 } 119 } 120 121 /// <summary> 122 /// Bind the lifetime of a disposable part to the current 123 /// lifetime context. 124 /// </summary> 125 /// <param name="instance">The disposable part to bind.</param> AddBoundInstance(IDisposable instance)126 public void AddBoundInstance(IDisposable instance) 127 { 128 lock (_boundPartLock) 129 { 130 if (_boundPartInstances == null) 131 throw new ObjectDisposedException(ToString()); 132 133 _boundPartInstances.Add(instance); 134 } 135 } 136 137 /// <summary> 138 /// Either retrieve an existing part instance with the specified sharing id, or 139 /// create and share a new part instance using <paramref name="creator"/> within 140 /// <paramref name="operation"/>. 141 /// </summary> 142 /// <param name="sharingId">Sharing id for the part in question.</param> 143 /// <param name="operation">Operation in which to activate a new part instance if necessary.</param> 144 /// <param name="creator">Activator that can activate a new part instance if necessary.</param> 145 /// <returns>The part instance corresponding to <paramref name="sharingId"/> within this lifetime context.</returns> 146 /// <remarks>This method is lock-free if the part instance already exists. If the part instance must be created, 147 /// a lock will be taken that will serialize other writes via this method (concurrent reads will continue to 148 /// be safe and lock-free). It is important that the composition, and thus lock acquisition, is strictly 149 /// leaf-to-root in the lifetime tree.</remarks> GetOrCreate(int sharingId, CompositionOperation operation, CompositeActivator creator)150 public object GetOrCreate(int sharingId, CompositionOperation operation, CompositeActivator creator) 151 { 152 object result; 153 if (_sharedPartInstances != null && _sharedPartInstances.TryGetValue(sharingId, out result)) 154 return result; 155 156 // Remains locked for the rest of the operation. 157 operation.EnterSharingLock(_sharingLock); 158 159 if (_sharedPartInstances == null) 160 { 161 _sharedPartInstances = new SmallSparseInitonlyArray(); 162 _instancesUndergoingInitialization = new SmallSparseInitonlyArray(); 163 } 164 else if (_sharedPartInstances.TryGetValue(sharingId, out result)) 165 { 166 return result; 167 } 168 169 // Already being initialized _on the same thread_. 170 if (_instancesUndergoingInitialization.TryGetValue(sharingId, out result)) 171 return result; 172 173 result = creator(this, operation); 174 175 _instancesUndergoingInitialization.Add(sharingId, result); 176 177 operation.AddPostCompositionAction(() => 178 { 179 _sharedPartInstances.Add(sharingId, result); 180 }); 181 182 return result; 183 } 184 185 /// <summary> 186 /// Retrieve the single <paramref name="contract"/> instance from the 187 /// <see cref="CompositionContext"/>. 188 /// </summary> 189 /// <param name="contract">The contract to retrieve.</param> 190 /// <returns>An instance of the export.</returns> 191 /// <param name="export">The export if available, otherwise, null.</param> 192 /// <exception cref="CompositionFailedException" /> TryGetExport(CompositionContract contract, out object export)193 public override bool TryGetExport(CompositionContract contract, out object export) 194 { 195 ExportDescriptor defaultForExport; 196 if (!_partRegistry.TryGetSingleForExport(contract, out defaultForExport)) 197 { 198 export = null; 199 return false; 200 } 201 202 export = CompositionOperation.Run(this, defaultForExport.Activator); 203 return true; 204 } 205 206 /// <summary> 207 /// Describes this lifetime context. 208 /// </summary> 209 /// <returns>A string description.</returns> ToString()210 public override string ToString() 211 { 212 if (_parent == null) 213 return "Root Lifetime Context"; 214 215 if (_sharingBoundaries.Length == 0) 216 return "Non-sharing Lifetime Context"; 217 218 return "Boundary for " + string.Join(", ", _sharingBoundaries); 219 } 220 } 221 } 222