private bool DetermineRejection(ComposablePartDefinition definition, AtomicComposition parentAtomicComposition) { ChangeRejectedException exception = null; // if there is no active atomic composition and rejection is disabled, there's no need to do any of the below if (!EnsureRejection(parentAtomicComposition)) { return(false); } using (var localAtomicComposition = new AtomicComposition(parentAtomicComposition)) { // The part definition we're currently working on is treated optimistically // as if we know it hasn't been rejected. This handles recursion, and if we // later decide that it has been rejected we'll discard all nested progress so // all side-effects of the mistake are erased. // // Note that this means that recursive failures that would be detected by the // import engine are not discovered by rejection currently. Loops among // prerequisites, runaway import chains involving factories, and prerequisites // that cannot be fully satisfied still result in runtime errors. Doing // otherwise would be possible but potentially expensive - and could be a v2 // improvement if deemed worthwhile. UpdateAtomicCompositionQueryForPartEquals(localAtomicComposition, definition, AtomicCompositionQueryState.TreatAsValidated); var newPart = definition.CreatePart(); try { _importEngine.PreviewImports(newPart, localAtomicComposition); // Reuse the partially-fleshed out part the next time we need a shared // instance to keep the expense of pre-validation to a minimum. Note that // _activatedParts holds references to both shared and non-shared parts. // The non-shared parts will only be used for rejection purposes only but // the shared parts will be handed out when requested via GetExports as // well as be used for rejection purposes. localAtomicComposition.AddCompleteActionAllowNull(() => { using (_lock.LockStateForWrite()) { if (!_activatedParts.ContainsKey(definition)) { _activatedParts.Add(definition, new CatalogPart(newPart)); IDisposable newDisposablePart = newPart as IDisposable; if (newDisposablePart != null) { _partsToDispose.Add(newDisposablePart); } } } }); // Success! Complete any recursive work that was conditioned on this part's validation localAtomicComposition.Complete(); return(false); } catch (ChangeRejectedException ex) { exception = ex; } } // If we've reached this point then this part has been rejected so we need to // record the rejection in our parent composition or execute it immediately if // one doesn't exist. parentAtomicComposition.AddCompleteActionAllowNull(() => { using (_lock.LockStateForWrite()) { _rejectedParts.Add(definition); } CompositionTrace.PartDefinitionRejected(definition, exception); }); if (parentAtomicComposition != null) { UpdateAtomicCompositionQueryForPartEquals(parentAtomicComposition, definition, AtomicCompositionQueryState.TreatAsRejected); } return(true); }
public void AtomicComposition_NestedQueries() { // This is a rather convoluted test that exercises the way AtomicComposition used to work to // ensure consistency of the newer design var key = new Object(); using (var contextA = new AtomicComposition()) { SetQuery(contextA, key, (int parameter, Func <int, bool> parentQuery) => { if (parameter == 22) { return(true); } if (parentQuery != null) { return(parentQuery(parameter)); } return(false); }); TestQuery(contextA, key, 22, true); using (var contextB = new AtomicComposition(contextA)) { TestQuery(contextB, key, 22, true); SetQuery(contextB, key, (int parameter, Func <int, bool> parentQuery) => { if (parentQuery != null) { return(!parentQuery(parameter)); } Assert.False(true); return(false); }); TestQuery(contextB, key, 21, true); TestQuery(contextB, key, 22, false); using (var contextC = new AtomicComposition(contextB)) { SetQuery(contextC, key, (int parameter, Func <int, bool> parentQuery) => { if (parameter == 23) { return(true); } if (parentQuery != null) { return(!parentQuery(parameter)); } Assert.False(true); return(false); }); TestQuery(contextC, key, 21, false); TestQuery(contextC, key, 22, true); TestQuery(contextC, key, 23, true); contextC.Complete(); } using (var contextD = new AtomicComposition(contextB)) { SetQuery(contextD, key, (int parameter, Func <int, bool> parentQuery) => { if (parentQuery != null) { return(parentQuery(parameter + 1)); } Assert.False(true); return(false); }); TestQuery(contextD, key, 21, true); TestQuery(contextD, key, 22, true); TestQuery(contextD, key, 23, false); // No complete } contextB.Complete(); } TestQuery(contextA, key, 21, false); TestQuery(contextA, key, 22, true); TestQuery(contextA, key, 23, true); contextA.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>(); 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(); } }
public void Compose(CompositionBatch batch) { this.ThrowIfDisposed(); this.EnsureRunning(); Requires.NotNull(batch, "batch"); // Quick exit test can be done prior to cloning since it's just an optimization, not a // change in behavior if ((batch.PartsToAdd.Count == 0) && (batch.PartsToRemove.Count == 0)) { return; } CompositionResult result = CompositionResult.SucceededResult; // Clone the batch, so that the external changes wouldn't happen half-way thorugh compose // NOTE : this does not guarantee the atomicity of cloning, which is not the goal anyway, // rather the fact that all subsequent calls will deal with an unchanging batch batch = new CompositionBatch(batch.PartsToAdd, batch.PartsToRemove); var newParts = GetUpdatedPartsList(batch); // Allow only recursive calls from the import engine to see the changes until // they've been verified ... using (var atomicComposition = new AtomicComposition()) { // Don't allow reentrant calls to compose during previewing to prevent // corrupted state. if (this._currentlyComposing) { throw new InvalidOperationException(Strings.ReentrantCompose); } this._currentlyComposing = true; try { // In the meantime recursive calls need to be able to see the list as well atomicComposition.SetValue(this, newParts); // Recompose any existing imports effected by the these changes first so that // adapters, resurrected parts, etc. can all play their role in satisfying // imports for added parts this.Recompose(batch, atomicComposition); // Ensure that required imports can be satisfied foreach (ComposablePart part in batch.PartsToAdd) { // collect the result of previewing all the adds in the batch try { this._importEngine.PreviewImports(part, atomicComposition); } catch (ChangeRejectedException ex) { result = result.MergeResult(new CompositionResult(ex.Errors)); } } result.ThrowOnErrors(atomicComposition); // Complete the new parts since they passed previewing.` using (this._lock.LockStateForWrite()) { this._parts = newParts; } atomicComposition.Complete(); } finally { this._currentlyComposing = false; } } // Satisfy Imports // - Satisfy imports on all newly added component parts foreach (ComposablePart part in batch.PartsToAdd) { result = result.MergeResult(CompositionServices.TryInvoke(() => this._importEngine.SatisfyImports(part))); } // return errors result.ThrowOnErrors(); }
public void Compose(CompositionBatch batch) { ThrowIfDisposed(); EnsureRunning(); Requires.NotNull(batch, nameof(batch)); // Quick exit test can be done prior to cloning since it's just an optimization, not a // change in behavior if ((batch.PartsToAdd.Count == 0) && (batch.PartsToRemove.Count == 0)) { return; } CompositionResult result = CompositionResult.SucceededResult; // Get updated parts list and a cloned batch var newParts = GetUpdatedPartsList(ref batch); // Allow only recursive calls from the import engine to see the changes until // they've been verified ... using (var atomicComposition = new AtomicComposition()) { // Don't allow reentrant calls to compose during previewing to prevent // corrupted state. if (_currentlyComposing) { throw new InvalidOperationException(SR.ReentrantCompose); } _currentlyComposing = true; try { // In the meantime recursive calls need to be able to see the list as well atomicComposition.SetValue(this, newParts); // Recompose any existing imports effected by the these changes first so that // adapters, resurrected parts, etc. can all play their role in satisfying // imports for added parts Recompose(batch, atomicComposition); // Ensure that required imports can be satisfied foreach (ComposablePart part in batch.PartsToAdd) { // collect the result of previewing all the adds in the batch try { ImportEngine.PreviewImports(part, atomicComposition); } catch (ChangeRejectedException ex) { result = result.MergeResult(new CompositionResult(ex.Errors)); } } result.ThrowOnErrors(atomicComposition); // Complete the new parts since they passed previewing.` using (_lock.LockStateForWrite()) { _parts = newParts; } atomicComposition.Complete(); } finally { _currentlyComposing = false; } } // Satisfy Imports // - Satisfy imports on all newly added component parts foreach (ComposablePart part in batch.PartsToAdd) { result = result.MergeResult(CompositionServices.TryInvoke(() => ImportEngine.SatisfyImports(part))); } // return errors result.ThrowOnErrors(); }
/// <summary> /// Refreshes the <see cref="ComposablePartDefinition"/>s with the latest files in the directory that match /// the searchPattern. If any files have been added they will be added to the catalog and if any files were /// removed they will be removed from the catalog. For files that have been removed keep in mind that the /// assembly cannot be unloaded from the process so <see cref="ComposablePartDefinition"/>s for those files /// will simply be removed from the catalog. /// /// Possible exceptions that can be thrown are any that <see cref="Directory.GetFiles(string, string)"/> or /// <see cref="Assembly.Load(AssemblyName)"/> can throw. /// </summary> /// <exception cref="DirectoryNotFoundException"> /// The specified <paramref name="path"/> has been removed since object construction. /// </exception> public void Refresh() { this.ThrowIfDisposed(); Assumes.NotNull(this._loadedFiles); List <Tuple <string, AssemblyCatalog> > catalogsToAdd; List <Tuple <string, AssemblyCatalog> > catalogsToRemove; ComposablePartDefinition[] addedDefinitions; ComposablePartDefinition[] removedDefinitions; object changeReferenceObject; string[] afterFiles; string[] beforeFiles; while (true) { afterFiles = this.GetFiles(); using (new ReadLock(this._thisLock)) { changeReferenceObject = this._loadedFiles; beforeFiles = this._loadedFiles.ToArray(); } this.DiffChanges(beforeFiles, afterFiles, out catalogsToAdd, out catalogsToRemove); // Don't go any further if there's no work to do if (catalogsToAdd.Count == 0 && catalogsToRemove.Count == 0) { return; } // Notify listeners to give them a preview before completeting the changes addedDefinitions = catalogsToAdd .SelectMany(cat => cat.Item2.Parts) .ToArray <ComposablePartDefinition>(); removedDefinitions = catalogsToRemove .SelectMany(cat => cat.Item2.Parts) .ToArray <ComposablePartDefinition>(); using (var atomicComposition = new AtomicComposition()) { var changingArgs = new ComposablePartCatalogChangeEventArgs(addedDefinitions, removedDefinitions, atomicComposition); this.OnChanging(changingArgs); // if the change went through then write the catalog changes using (new WriteLock(this._thisLock)) { if (changeReferenceObject != this._loadedFiles) { // Someone updated the list while we were diffing so we need to try the diff again continue; } foreach (var catalogToAdd in catalogsToAdd) { this._assemblyCatalogs.Add(catalogToAdd.Item1, catalogToAdd.Item2); this._catalogCollection.Add(catalogToAdd.Item2); } foreach (var catalogToRemove in catalogsToRemove) { this._assemblyCatalogs.Remove(catalogToRemove.Item1); this._catalogCollection.Remove(catalogToRemove.Item2); } this._partsQuery = this._catalogCollection.AsQueryable().SelectMany(catalog => catalog.Parts); this._loadedFiles = afterFiles.ToReadOnlyCollection(); // Lastly complete any changes added to the atomicComposition during the change event atomicComposition.Complete(); // Break out of the while(true) break; } // WriteLock } // AtomicComposition } // while (true) var changedArgs = new ComposablePartCatalogChangeEventArgs(addedDefinitions, removedDefinitions, null); this.OnChanged(changedArgs); }