Ejemplo n.º 1
0
        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);
        }
Ejemplo n.º 2
0
        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();
            }
        }
Ejemplo n.º 3
0
        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();
        }
Ejemplo n.º 5
0
        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();
        }
Ejemplo n.º 6
0
        /// <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);
        }