/// <summary> /// Previews all the required imports for the given <see cref="ComposablePart"/> to /// ensure they can all be satisified. The preview does not actually set the imports /// only ensures that they exist in the source provider. If the preview succeeds then /// the <see cref="ImportEngine"/> also enforces that changes to exports in the source /// provider will not break any of the required imports. If this enforcement needs to be /// lifted for this part then <see cref="ReleaseImports"/> needs to be called for this /// <see cref="ComposablePart"/>. /// </summary> /// <param name="part"> /// The <see cref="ComposablePart"/> to preview the required imports. /// </param> /// <param name="atomicComposition"></param> /// <exception cref="CompositionException"> /// An error occurred during previewing and <paramref name="atomicComposition"/> is null. /// <see cref="CompositionException.Errors"/> will contain a collection of errors that occurred. /// The pre-existing composition is in an unknown state, depending on the errors that occured. /// </exception> /// <exception cref="ChangeRejectedException"> /// An error occurred during the previewing and <paramref name="atomicComposition"/> is not null. /// <see cref="CompositionException.Errors"/> will contain a collection of errors that occurred. /// The pre-existing composition remains in valid state. /// </exception> /// <exception cref="ObjectDisposedException"> /// The <see cref="ImportEngine"/> has been disposed of. /// </exception> public void PreviewImports(ComposablePart part, AtomicComposition atomicComposition) { this.ThrowIfDisposed(); Requires.NotNull(part, "part"); // Do not do any previewing if SilentRejection is disabled. if (this._compositionOptions.HasFlag(CompositionOptions.DisableSilentRejection)) { return; } // NOTE : this is a very intricate area threading-wise, please use caution when changing, otherwise state corruption or deadlocks will ensue // The gist of what we are doing is as follows: // We need to lock the composition, as we will proceed modifying our internal state. The tricky part is when we release the lock // Due to the fact that some actions will take place AFTER we leave this method, we need to KEEP THAT LOCK HELD until the transation is commiited or rolled back // This is the reason we CAN'T use "using here. // Instead, if the transaction is present we will queue up the release of the lock, otherwise we will release it when we exit this method // We add the "release" lock to BOTH Commit and Revert queues, because they are mutually exclusive, and we need to release the lock regardless. // This will take the lock, if necesary IDisposable compositionLockHolder = this._lock.IsThreadSafe ? this._lock.LockComposition() : null; bool compositionLockTaken = (compositionLockHolder != null); try { // revert actions are processed in the reverse order, so we have to add the "release lock" action now if (compositionLockTaken && (atomicComposition != null)) { atomicComposition.AddRevertAction(() => compositionLockHolder.Dispose()); } var partManager = GetPartManager(part, true); var result = TryPreviewImportsStateMachine(partManager, part, atomicComposition); result.ThrowOnErrors(atomicComposition); StartSatisfyingImports(partManager, atomicComposition); // Add the "release lock" to the commit actions if (compositionLockTaken && (atomicComposition != null)) { atomicComposition.AddCompleteAction(() => compositionLockHolder.Dispose()); } } finally { // We haven't updated the queues, so we can release the lock now if (compositionLockTaken && (atomicComposition == null)) { compositionLockHolder.Dispose(); } } }
public void Complete_ShouldExecuteActions() { bool executedAction = false; var ct = new AtomicComposition(); ct.AddCompleteAction(() => executedAction = true); ct.Complete(); Assert.True(executedAction); }
internal static void AddCompleteActionAllowNull(this AtomicComposition atomicComposition, Action action) { Assumes.NotNull(action); if (atomicComposition == null) { action(); } else { atomicComposition.AddCompleteAction(action); } }
public void AfterComplete_AllMethodsShouldThrow() { var ct = new AtomicComposition(); ct.Complete(); Assert.Throws <InvalidOperationException>(() => ct.AddCompleteAction(() => ct = null)); Assert.Throws <InvalidOperationException>(() => ct.Complete()); Assert.Throws <InvalidOperationException>(() => ct.SetValue(ct, 10)); object value; Assert.Throws <InvalidOperationException>(() => ct.TryGetValue(ct, out value)); }
public void Dispose_AllMethodsShouldThrow() { var ct = new AtomicComposition(); ct.Dispose(); Assert.Throws <ObjectDisposedException>(() => ct.AddCompleteAction(() => ct = null)); Assert.Throws <ObjectDisposedException>(() => ct.Complete()); Assert.Throws <ObjectDisposedException>(() => ct.SetValue(ct, 10)); object value; Assert.Throws <ObjectDisposedException>(() => ct.TryGetValue(ct, out value)); }
private EngineContext GetEngineContext(AtomicComposition atomicComposition) { ArgumentNullException.ThrowIfNull(atomicComposition); if (!atomicComposition.TryGetValue(this, true, out EngineContext? engineContext)) { atomicComposition.TryGetValue(this, false, out EngineContext? parentContext); engineContext = new EngineContext(this, parentContext); atomicComposition.SetValue(this, engineContext); atomicComposition.AddCompleteAction(engineContext.Complete); } return(engineContext !); }
private void OnCatalogChanging(object sender, ComposablePartCatalogChangeEventArgs e) { using (var atomicComposition = new AtomicComposition(e.AtomicComposition)) { // Save the preview catalog to use in place of the original while handling // this event atomicComposition.SetValue(this._catalog, new CatalogChangeProxy(this._catalog, e.AddedDefinitions, e.RemovedDefinitions)); IEnumerable <ExportDefinition> addedExports = e.AddedDefinitions .SelectMany(part => part.ExportDefinitions) .ToArray(); IEnumerable <ExportDefinition> removedExports = e.RemovedDefinitions .SelectMany(part => part.ExportDefinitions) .ToArray(); // Remove any parts based on eliminated definitions (in a atomicComposition-friendly // fashion) foreach (var definition in e.RemovedDefinitions) { ComposablePart removedPart = null; bool removed = false; using (new ReadLock(_lock)) { removed = this._activatedParts.TryGetValue(definition, out removedPart); } if (removed) { ReleasePart(null, removedPart, atomicComposition); atomicComposition.AddCompleteActionAllowNull(() => { using (new WriteLock(_lock)) { this._activatedParts.Remove(definition); } }); } } UpdateRejections(addedExports.ConcatAllowingNull(removedExports), atomicComposition); this.OnExportsChanging( new ExportsChangeEventArgs(addedExports, removedExports, atomicComposition)); atomicComposition.AddCompleteAction(() => this.OnExportsChanged( new ExportsChangeEventArgs(addedExports, removedExports, null))); atomicComposition.Complete(); } }
private void OnCatalogChanging(object sender, ComposablePartCatalogChangeEventArgs e) { using (var atomicComposition = new AtomicComposition(e.AtomicComposition)) { // Save the preview catalog to use in place of the original while handling // this event atomicComposition.SetValue(_catalog, new CatalogChangeProxy(_catalog, e.AddedDefinitions, e.RemovedDefinitions)); IEnumerable <ExportDefinition> addedExports = GetExportsFromPartDefinitions(e.AddedDefinitions); IEnumerable <ExportDefinition> removedExports = GetExportsFromPartDefinitions(e.RemovedDefinitions); // Remove any parts based on eliminated definitions (in a atomicComposition-friendly // fashion) foreach (var definition in e.RemovedDefinitions) { CatalogPart removedPart = null; bool removed = false; using (_lock.LockStateForRead()) { removed = _activatedParts.TryGetValue(definition, out removedPart); } if (removed) { var capturedDefinition = definition; DisposePart(null, removedPart, atomicComposition); atomicComposition.AddCompleteActionAllowNull(() => { using (_lock.LockStateForWrite()) { _activatedParts.Remove(capturedDefinition); } }); } } UpdateRejections(addedExports.ConcatAllowingNull(removedExports), atomicComposition); OnExportsChanging( new ExportsChangeEventArgs(addedExports, removedExports, atomicComposition)); atomicComposition.AddCompleteAction(() => OnExportsChanged( new ExportsChangeEventArgs(addedExports, removedExports, null))); atomicComposition.Complete(); } }
private CompositionResult TryRecomposeImports(PartManager partManager, IEnumerable <ExportDefinition> changedExports, AtomicComposition atomicComposition) { var result = CompositionResult.SucceededResult; switch (partManager.State) { case ImportState.ImportsPreviewed: case ImportState.Composed: // Validate states to continue. break; default: { // All other states are invalid and for recomposition. return(new CompositionResult(ErrorBuilder.InvalidStateForRecompposition(partManager.Part))); } } var affectedImports = RecompositionManager.GetAffectedImports(partManager.Part, changedExports); bool partComposed = (partManager.State == ImportState.Composed); bool recomposedImport = false; foreach (var import in affectedImports) { result = result.MergeResult( TryRecomposeImport(partManager, partComposed, import, atomicComposition)); recomposedImport = true; } // Knowing that the part has already been composed before and that the only possible // changes are to recomposable imports, we can safely go ahead and do this now or // schedule it for later if (result.Succeeded && recomposedImport && partComposed) { if (atomicComposition == null) { result = result.MergeResult(partManager.TryOnComposed()); } else { atomicComposition.AddCompleteAction(() => partManager.TryOnComposed().ThrowOnErrors()); } } return(result); }
private EngineContext GetEngineContext(AtomicComposition atomicComposition) { Assumes.NotNull(atomicComposition); EngineContext engineContext; if (!atomicComposition.TryGetValue(this, true, out engineContext)) { EngineContext parentContext; atomicComposition.TryGetValue(this, false, out parentContext); engineContext = new EngineContext(this, parentContext); atomicComposition.SetValue(this, engineContext); atomicComposition.AddCompleteAction(engineContext.Complete); } return(engineContext); }
public void Complete_ShouldCopyActionsToInner() { bool executedAction = false; var innerAtomicComposition = new AtomicComposition(); using (var ct = new AtomicComposition(innerAtomicComposition)) { ct.AddCompleteAction(() => executedAction = true); ct.Complete(); Assert.False(executedAction, "Action should not have been exectued yet"); } innerAtomicComposition.Complete(); Assert.True(executedAction); }
private EngineContext GetEngineContext(AtomicComposition atomicComposition) { if (atomicComposition == null) { throw new ArgumentNullException(nameof(atomicComposition)); } EngineContext engineContext; if (!atomicComposition.TryGetValue(this, true, out engineContext)) { EngineContext parentContext; atomicComposition.TryGetValue(this, false, out parentContext); engineContext = new EngineContext(this, parentContext); atomicComposition.SetValue(this, engineContext); atomicComposition.AddCompleteAction(engineContext.Complete); } return(engineContext); }
private CompositionResult TryRecomposeImport(PartManager partManager, bool partComposed, ImportDefinition import, AtomicComposition atomicComposition) { if (partComposed && !import.IsRecomposable) { return(new CompositionResult(ErrorBuilder.PreventedByExistingImport(partManager.Part, import))); } // During recomposition you must always requery with the new atomicComposition you cannot use any // cached value in the part manager var exportsResult = TryGetExports(this._sourceProvider, partManager.Part, import, atomicComposition); if (!exportsResult.Succeeded) { return(exportsResult.ToResult()); } var exports = exportsResult.Value.AsArray(); if (partComposed) { // Knowing that the part has already been composed before and that the only possible // changes are to recomposable imports, we can safely go ahead and do this now or // schedule it for later if (atomicComposition == null) { return(partManager.TrySetImport(import, exports)); } else { atomicComposition.AddCompleteAction(() => partManager.TrySetImport(import, exports).ThrowOnErrors()); } } else { partManager.SetSavedImport(import, exports, atomicComposition); } return(CompositionResult.SucceededResult); }
private void CopyComplete() { if (_outerAtomicComposition == null) { throw new Exception(SR.Diagnostic_InternalExceptionMessage); } _outerAtomicComposition.ContainsInnerAtomicComposition = false; // Inner scopes are much odder, because completeting them means coalescing them into the // outer scope - the complete or revert actions are deferred until the outermost scope completes // or any intermediate rolls back if (_completeActionList != null) { foreach (Action action in _completeActionList) { _outerAtomicComposition.AddCompleteAction(action); } } if (_revertActionList != null) { foreach (Action action in _revertActionList) { _outerAtomicComposition.AddRevertAction(action); } } // We can copy over existing atomicComposition entries because they're either already chained or // overwrite by design and can now be completed or rolled back together for (var index = 0; index < _valueCount; index++) { _outerAtomicComposition.SetValueInternal( _values[index].Key, _values[index].Value); } }
private void UpdateRejections(IEnumerable <ExportDefinition> changedExports, AtomicComposition atomicComposition) { using (var localAtomicComposition = new AtomicComposition(atomicComposition)) { // Reconsider every part definition that has been previously // rejected to see if any of them can be added back. var affectedRejections = new HashSet <ComposablePartDefinition>(); ComposablePartDefinition[] rejectedParts; using (_lock.LockStateForRead()) { rejectedParts = _rejectedParts.ToArray(); } foreach (var definition in rejectedParts) { if (QueryPartState(localAtomicComposition, definition) == AtomicCompositionQueryState.TreatAsValidated) { continue; } foreach (var import in definition.ImportDefinitions.Where(ImportEngine.IsRequiredImportForPreview)) { if (changedExports.Any(export => import.IsConstraintSatisfiedBy(export))) { affectedRejections.Add(definition); break; } } } UpdateAtomicCompositionQueryForPartInHashSet(localAtomicComposition, affectedRejections, AtomicCompositionQueryState.NeedsTesting); // Determine if any of the resurrectable parts is now available so that we can // notify listeners of the exact changes to exports var resurrectedExports = new List <ExportDefinition>(); foreach (var partDefinition in affectedRejections) { if (!IsRejected(partDefinition, localAtomicComposition)) { // Notify listeners of the newly available exports and // prepare to remove the rejected part from the list of rejections resurrectedExports.AddRange(partDefinition.ExportDefinitions); // Capture the local so that the closure below refers to the current definition // in the loop and not the value of 'partDefinition' when the closure executes var capturedPartDefinition = partDefinition; localAtomicComposition.AddCompleteAction(() => { using (_lock.LockStateForWrite()) { _rejectedParts.Remove(capturedPartDefinition); } CompositionTrace.PartDefinitionResurrected(capturedPartDefinition); }); } } // Notify anyone sourcing exports that the resurrected exports have appeared if (resurrectedExports.Any()) { OnExportsChanging( new ExportsChangeEventArgs(resurrectedExports, Array.Empty <ExportDefinition>(), localAtomicComposition)); localAtomicComposition.AddCompleteAction(() => OnExportsChanged( new ExportsChangeEventArgs(resurrectedExports, Array.Empty <ExportDefinition>(), null))); } localAtomicComposition.Complete(); } }
private void UpdateRejections(IEnumerable <ExportDefinition> changedExports, AtomicComposition atomicComposition) { using (var localAtomicComposition = new AtomicComposition(atomicComposition)) { // Reconsider every part definition that has been previously // rejected to see if any of them can be added back. var affectedRejections = new HashSet <ComposablePartDefinition>(); var atomicCompositionQuery = GetAtomicCompositionQuery(localAtomicComposition); ComposablePartDefinition[] rejectedParts; using (new ReadLock(this._lock)) { rejectedParts = this._rejectedParts.ToArray(); } foreach (var definition in rejectedParts) { if (atomicCompositionQuery(definition) == AtomicCompositionQueryState.TreatAsValidated) { continue; } foreach (var import in definition.ImportDefinitions.Where(ImportEngine.IsRequiredImportForPreview)) { if (changedExports.Any(export => import.IsConstraintSatisfiedBy(export))) { affectedRejections.Add(definition); break; } } } UpdateAtomicCompositionQuery(localAtomicComposition, def => affectedRejections.Contains(def), AtomicCompositionQueryState.NeedsTesting); // Determine if any of the resurrectable parts is now available so that we can // notify listeners of the exact changes to exports var resurrectedExports = new List <ExportDefinition>(); foreach (var partDefinition in affectedRejections) { if (!IsRejected(partDefinition, localAtomicComposition)) { // Notify listeners of the newly available exports and // prepare to remove the rejected part from the list of rejections resurrectedExports.AddRange(partDefinition.ExportDefinitions); localAtomicComposition.AddCompleteAction(() => { using (new WriteLock(this._lock)) { this._rejectedParts.Remove(partDefinition); } }); } } // Notify anyone sourcing exports that the resurrected exports have appeared if (resurrectedExports.Any()) { this.OnExportsChanging( new ExportsChangeEventArgs(resurrectedExports, new ExportDefinition[0], localAtomicComposition)); localAtomicComposition.AddCompleteAction(() => this.OnExportsChanged( new ExportsChangeEventArgs(resurrectedExports, new ExportDefinition[0], null))); } localAtomicComposition.Complete(); } }