update MEF to preview 9
[mcs.git] / class / System.ComponentModel.Composition / src / ComponentModel / System / ComponentModel / Composition / Hosting / ImportEngine.cs
blob29bd329c3cacf193768ca0e15da3ca81e5cbf038
1 // -----------------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 // -----------------------------------------------------------------------
4 using System;
5 using System.Collections.Generic;
6 using System.ComponentModel.Composition.Primitives;
7 using System.Diagnostics;
8 using System.Linq;
9 using System.Runtime.CompilerServices;
10 using Microsoft.Internal;
11 using Microsoft.Internal.Collections;
13 namespace System.ComponentModel.Composition.Hosting
15 // This class guarantees thread-safety under the follwoing conditions:
16 // - Each composition is executed on a single thread
17 // - No recomposition ever takes place
18 // - The class is created with isThreadSafe=true
19 public partial class ImportEngine : ICompositionService, IDisposable
21 private const int MaximumNumberOfCompositionIterations = 100;
23 private volatile bool _isDisposed;
24 private ExportProvider _sourceProvider;
25 private Stack<PartManager> _recursionStateStack = new Stack<PartManager>();
26 private ConditionalWeakTable<ComposablePart, PartManager> _partManagers = new ConditionalWeakTable<ComposablePart, PartManager>();
27 private RecompositionManager _recompositionManager = new RecompositionManager();
28 private readonly CompositionLock _lock = null;
30 /// <summary>
31 /// Initializes a new instance of the <see cref="ImportEngine"/> class.
32 /// </summary>
33 /// <param name="sourceProvider">
34 /// The <see cref="ExportProvider"/> which provides the
35 /// <see cref="ImportEngine"/> access to <see cref="Export"/>s.
36 /// </param>
37 public ImportEngine(ExportProvider sourceProvider)
38 : this(sourceProvider, false)
42 public ImportEngine(ExportProvider sourceProvider, bool isThreadSafe)
44 Requires.NotNull(sourceProvider, "sourceProvider");
46 this._sourceProvider = sourceProvider;
47 this._sourceProvider.ExportsChanging += this.OnExportsChanging;
48 this._lock = new CompositionLock(isThreadSafe);
51 /// <summary>
52 /// Previews all the required imports for the given <see cref="ComposablePart"/> to
53 /// ensure they can all be satisified. The preview does not actually set the imports
54 /// only ensures that they exist in the source provider. If the preview succeeds then
55 /// the <see cref="ImportEngine"/> also enforces that changes to exports in the source
56 /// provider will not break any of the required imports. If this enforcement needs to be
57 /// lifted for this part then <see cref="ReleaseImports"/> needs to be called for this
58 /// <see cref="ComposablePart"/>.
59 /// </summary>
60 /// <param name="part">
61 /// The <see cref="ComposablePart"/> to preview the required imports.
62 /// </param>
63 /// <param name="atomicComposition"></param>
64 /// <exception cref="CompositionException">
65 /// An error occurred during previewing and <paramref name="atomicComposition"/> is null.
66 /// <see cref="CompositionException.Errors"/> will contain a collection of errors that occurred.
67 /// The pre-existing composition is in an unknown state, depending on the errors that occured.
68 /// </exception>
69 /// <exception cref="ChangeRejectedException">
70 /// An error occurred during the previewing and <paramref name="atomicComposition"/> is not null.
71 /// <see cref="CompositionException.Errors"/> will contain a collection of errors that occurred.
72 /// The pre-existing composition remains in valid state.
73 /// </exception>
74 /// <exception cref="ObjectDisposedException">
75 /// The <see cref="ImportEngine"/> has been disposed of.
76 /// </exception>
77 public void PreviewImports(ComposablePart part, AtomicComposition atomicComposition)
79 this.ThrowIfDisposed();
81 Requires.NotNull(part, "part");
83 // NOTE : this is a very intricate area threading-wise, please use caution when changing, otherwise state corruption or deadlocks will ensue
84 // The gist of what we are doing is as follows:
85 // We need to lock the composition, as we will proceed modifying our internal state. The tricky part is when we release the lock
86 // 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
87 // This is the reason we CAN'T use "using here.
88 // 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
89 // We add the "release" lock to BOTH Commit and Revert queues, because they are mutually exclusive, and we need to release the lock regardless.
91 // This will take the lock, if necesary
92 IDisposable compositionLockHolder = this._lock.IsThreadSafe ? this._lock.LockComposition() : null;
93 bool compositionLockTaken = (compositionLockHolder != null);
94 try
96 // revert actions are processed in the reverse order, so we have to add the "release lock" action now
97 if (compositionLockTaken && (atomicComposition != null))
99 atomicComposition.AddRevertAction(() => compositionLockHolder.Dispose());
102 var partManager = GetPartManager(part, true);
103 var result = TryPreviewImportsStateMachine(partManager, part, atomicComposition);
104 result.ThrowOnErrors(atomicComposition);
106 StartSatisfyingImports(partManager, atomicComposition);
108 // Add the "release lock" to the commit actions
109 if (compositionLockTaken && (atomicComposition != null))
111 atomicComposition.AddCompleteAction(() => compositionLockHolder.Dispose());
114 finally
116 // We haven't updated the queues, so we can release the lock now
117 if (compositionLockTaken && (atomicComposition == null))
119 compositionLockHolder.Dispose();
124 /// <summary>
125 /// Satisfies the imports of the specified composable part. If the satisfy succeeds then
126 /// the <see cref="ImportEngine"/> also enforces that changes to exports in the source
127 /// provider will not break any of the required imports. If this enforcement needs to be
128 /// lifted for this part then <see cref="ReleaseImports"/> needs to be called for this
129 /// <see cref="ComposablePart"/>.
130 /// </summary>
131 /// <param name="part">
132 /// The <see cref="ComposablePart"/> to set the imports.
133 /// </param>
134 /// <exception cref="ArgumentNullException">
135 /// <paramref name="part"/> is <see langword="null"/>.
136 /// </exception>
137 /// <exception cref="CompositionException">
138 /// An error occurred during composition. <see cref="CompositionException.Errors"/> will
139 /// contain a collection of errors that occurred.
140 /// </exception>
141 /// <exception cref="ObjectDisposedException">
142 /// The <see cref="ImportEngine"/> has been disposed of.
143 /// </exception>
144 public void SatisfyImports(ComposablePart part)
146 this.ThrowIfDisposed();
148 Requires.NotNull(part, "part");
150 // NOTE : the following two calls use the state lock
151 PartManager partManager = this.GetPartManager(part, true);
152 if (partManager.State == ImportState.Composed)
154 return;
157 using (this._lock.LockComposition())
159 var result = TrySatisfyImports(partManager, part, true);
160 result.ThrowOnErrors(); // throw CompositionException not ChangeRejectedException
164 /// <summary>
165 /// Sets the imports of the specified composable part exactly once and they will not
166 /// ever be recomposed.
167 /// </summary>
168 /// <param name="part">
169 /// The <see cref="ComposablePart"/> to set the imports.
170 /// </param>
171 /// <exception cref="ArgumentNullException">
172 /// <paramref name="part"/> is <see langword="null"/>.
173 /// </exception>
174 /// <exception cref="CompositionException">
175 /// An error occurred during composition. <see cref="CompositionException.Errors"/> will
176 /// contain a collection of errors that occurred.
177 /// </exception>
178 /// <exception cref="ObjectDisposedException">
179 /// The <see cref="ICompositionService"/> has been disposed of.
180 /// </exception>
181 public void SatisfyImportsOnce(ComposablePart part)
183 this.ThrowIfDisposed();
185 Requires.NotNull(part, "part");
187 // NOTE : the following two calls use the state lock
188 PartManager partManager = this.GetPartManager(part, true);
189 if (partManager.State == ImportState.Composed)
191 return;
194 using (this._lock.LockComposition())
196 var result = TrySatisfyImports(partManager, part, false);
197 result.ThrowOnErrors(); // throw CompositionException not ChangeRejectedException
201 /// <summary>
202 /// Removes any state stored in the <see cref="ImportEngine"/> for the associated
203 /// <see cref="ComposablePart"/> and releases all the <see cref="Export"/>s used to
204 /// satisfy the imports on the <see cref="ComposablePart"/>.
205 ///
206 /// Also removes the enforcement for changes that would break a required import on
207 /// <paramref name="part"/>.
208 /// </summary>
209 /// <param name="part">
210 /// The <see cref="ComposablePart"/> to release the imports on.
211 /// </param>
212 /// <param name="atomicComposition">
213 /// The <see cref="AtomicComposition"/> that the release imports is running under.
214 /// </param>
215 public void ReleaseImports(ComposablePart part, AtomicComposition atomicComposition)
217 this.ThrowIfDisposed();
219 Requires.NotNull(part, "part");
221 using (this._lock.LockComposition())
223 PartManager partManager = this.GetPartManager(part, false);
224 if (partManager != null)
226 this.StopSatisfyingImports(partManager, atomicComposition);
231 /// <summary>
232 /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
233 /// </summary>
234 public void Dispose()
236 this.Dispose(true);
237 GC.SuppressFinalize(this);
240 /// <summary>
241 /// Releases unmanaged and - optionally - managed resources
242 /// </summary>
243 /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
244 protected virtual void Dispose(bool disposing)
246 if (disposing)
248 if (!this._isDisposed)
250 bool disposeLock = false;
251 ExportProvider sourceProviderToUnsubscribeFrom = null;
252 using (this._lock.LockStateForWrite())
254 if (!this._isDisposed)
256 sourceProviderToUnsubscribeFrom = this._sourceProvider;
257 this._sourceProvider = null;
258 this._recompositionManager = null;
259 this._partManagers = null;
260 this._isDisposed = true;
261 disposeLock = true;
265 if (sourceProviderToUnsubscribeFrom != null)
267 sourceProviderToUnsubscribeFrom.ExportsChanging -= this.OnExportsChanging;
270 if (disposeLock)
272 this._lock.Dispose();
278 private CompositionResult TryPreviewImportsStateMachine(PartManager partManager,
279 ComposablePart part, AtomicComposition atomicComposition)
281 var result = CompositionResult.SucceededResult;
283 if (partManager.State == ImportState.ImportsPreviewing)
285 // We shouldn't nomally ever hit this case but if we do
286 // then we should just error with a cycle error.
287 return new CompositionResult(ErrorBuilder.CreatePartCycle(part));
290 // Transition from NoImportsStatisified to ImportsPreviewed
291 if (partManager.State == ImportState.NoImportsSatisfied)
293 partManager.State = ImportState.ImportsPreviewing;
295 var requiredImports = part.ImportDefinitions.Where(IsRequiredImportForPreview);
297 // If this atomicComposition gets rolledback for any reason we need to reset our state
298 atomicComposition.AddRevertActionAllowNull(() => partManager.State = ImportState.NoImportsSatisfied);
300 result = result.MergeResult(
301 this.TrySatisfyImportSubset(partManager, requiredImports, atomicComposition));
303 if (!result.Succeeded)
305 partManager.State = ImportState.NoImportsSatisfied;
306 return result;
309 partManager.State = ImportState.ImportsPreviewed;
312 return result;
315 private CompositionResult TrySatisfyImportsStateMachine(PartManager partManager, ComposablePart part)
317 var result = CompositionResult.SucceededResult;
319 while (partManager.State < ImportState.Composed)
321 var previousState = partManager.State;
323 switch (partManager.State)
325 // "ed" states which represent a some sort of steady state and will
326 // attempt to do a state transition
327 case ImportState.NoImportsSatisfied:
328 case ImportState.ImportsPreviewed:
330 partManager.State = ImportState.PreExportImportsSatisfying;
332 var prereqImports = part.ImportDefinitions.Where(import => import.IsPrerequisite);
333 result = result.MergeResult(
334 this.TrySatisfyImportSubset(partManager, prereqImports, null));
336 partManager.State = ImportState.PreExportImportsSatisfied;
337 break;
339 case ImportState.PreExportImportsSatisfied:
341 partManager.State = ImportState.PostExportImportsSatisfying;
343 var requiredImports = part.ImportDefinitions.Where(import => !import.IsPrerequisite);
345 result = result.MergeResult(
346 this.TrySatisfyImportSubset(partManager, requiredImports, null));
348 partManager.State = ImportState.PostExportImportsSatisfied;
349 break;
351 case ImportState.PostExportImportsSatisfied:
353 partManager.State = ImportState.ComposedNotifying;
355 partManager.ClearSavedImports();
356 result = result.MergeResult(partManager.TryOnComposed());
358 partManager.State = ImportState.Composed;
359 break;
363 // "ing" states which represent some sort of cycle
364 // These state should always return, error or not, instead of breaking
365 case ImportState.ImportsPreviewing:
367 // We shouldn't nomally ever hit this case but if we do
368 // then we should just error with a cycle error.
369 return new CompositionResult(ErrorBuilder.CreatePartCycle(part));
371 case ImportState.PreExportImportsSatisfying:
372 case ImportState.PostExportImportsSatisfying:
374 if (InPrerequisiteLoop())
376 return result.MergeError(ErrorBuilder.CreatePartCycle(part));
378 // Cycles in post export imports are allowed so just return in that case
379 return result;
381 case ImportState.ComposedNotifying:
383 // We are currently notifying so don't notify again just return
384 return result;
388 // if an error occured while doing a state transition
389 if (!result.Succeeded)
391 // revert to the previous state and return the error
392 partManager.State = previousState;
393 return result;
396 return result;
399 private CompositionResult TrySatisfyImports(PartManager partManager, ComposablePart part, bool shouldTrackImports)
401 Assumes.NotNull(part);
403 var result = CompositionResult.SucceededResult;
405 // get out if the part is already composed
406 if (partManager.State == ImportState.Composed)
408 return result;
411 // Track number of recursive iterations and throw an exception before the stack
412 // fills up and debugging the root cause becomes tricky
413 if (this._recursionStateStack.Count >= MaximumNumberOfCompositionIterations)
415 return result.MergeError(
416 ErrorBuilder.ComposeTookTooManyIterations(MaximumNumberOfCompositionIterations));
419 // Maintain the stack to detect whether recursive loops cross prerequisites
420 this._recursionStateStack.Push(partManager);
423 result = result.MergeResult(
424 TrySatisfyImportsStateMachine(partManager, part));
426 finally
428 this._recursionStateStack.Pop();
431 if (shouldTrackImports)
433 StartSatisfyingImports(partManager, null);
436 return result;
439 private CompositionResult TrySatisfyImportSubset(PartManager partManager,
440 IEnumerable<ImportDefinition> imports, AtomicComposition atomicComposition)
442 CompositionResult result = CompositionResult.SucceededResult;
444 var part = partManager.Part;
445 foreach (ImportDefinition import in imports)
447 var exports = partManager.GetSavedImport(import);
449 if (exports == null)
451 CompositionResult<IEnumerable<Export>> exportsResult = TryGetExports(
452 this._sourceProvider, part, import, atomicComposition);
454 if (!exportsResult.Succeeded)
456 result = result.MergeResult(exportsResult.ToResult());
457 continue;
459 exports = exportsResult.Value.AsArray();
462 if (atomicComposition == null)
464 result = result.MergeResult(
465 partManager.TrySetImport(import, exports));
467 else
469 partManager.SetSavedImport(import, exports, atomicComposition);
472 return result;
475 private void OnExportsChanging(object sender, ExportsChangeEventArgs e)
477 CompositionResult result = CompositionResult.SucceededResult;
479 // Prepare for the recomposition effort by minimizing the amount of work we'll have to do later
480 AtomicComposition atomicComposition = e.AtomicComposition;
482 IEnumerable<PartManager> affectedParts = this._recompositionManager.GetAffectedParts(e.ChangedContractNames);
484 // When in a atomicComposition account for everything that isn't yet reflected in the
485 // index
486 if (atomicComposition != null)
488 EngineContext engineContext;
489 if (atomicComposition.TryGetValue(this, out engineContext))
491 // always added the new part managers to see if they will also be
492 // affected by these changes
493 affectedParts = affectedParts.ConcatAllowingNull(engineContext.GetAddedPartManagers())
494 .Except(engineContext.GetRemovedPartManagers());
498 var changedExports = e.AddedExports.ConcatAllowingNull(e.RemovedExports);
500 foreach (var partManager in affectedParts)
502 result = result.MergeResult(this.TryRecomposeImports(partManager, changedExports, atomicComposition));
505 result.ThrowOnErrors(atomicComposition);
508 private CompositionResult TryRecomposeImports(PartManager partManager,
509 IEnumerable<ExportDefinition> changedExports, AtomicComposition atomicComposition)
511 var result = CompositionResult.SucceededResult;
513 switch (partManager.State)
515 case ImportState.ImportsPreviewed:
516 case ImportState.Composed:
517 // Validate states to continue.
518 break;
520 default:
522 // All other states are invalid and for recomposition.
523 return new CompositionResult(ErrorBuilder.InvalidStateForRecompposition(partManager.Part));
527 var affectedImports = RecompositionManager.GetAffectedImports(partManager.Part, changedExports);
528 bool partComposed = (partManager.State == ImportState.Composed);
530 bool recomposedImport = false;
531 foreach (var import in affectedImports)
533 result = result.MergeResult(
534 TryRecomposeImport(partManager, partComposed, import, atomicComposition));
536 recomposedImport = true;
539 // Knowing that the part has already been composed before and that the only possible
540 // changes are to recomposable imports, we can safely go ahead and do this now or
541 // schedule it for later
542 if (result.Succeeded && recomposedImport && partComposed)
544 if (atomicComposition == null)
546 result = result.MergeResult(partManager.TryOnComposed());
548 else
550 atomicComposition.AddCompleteAction(() => partManager.TryOnComposed().ThrowOnErrors());
554 return result;
557 private CompositionResult TryRecomposeImport(PartManager partManager, bool partComposed,
558 ImportDefinition import, AtomicComposition atomicComposition)
560 if (partComposed && !import.IsRecomposable)
562 return new CompositionResult(ErrorBuilder.PreventedByExistingImport(partManager.Part, import));
565 // During recomposition you must always requery with the new atomicComposition you cannot use any
566 // cached value in the part manager
567 var exportsResult = TryGetExports(this._sourceProvider, partManager.Part, import, atomicComposition);
568 if (!exportsResult.Succeeded)
570 return exportsResult.ToResult();
572 var exports = exportsResult.Value.AsArray();
574 if (partComposed)
576 // Knowing that the part has already been composed before and that the only possible
577 // changes are to recomposable imports, we can safely go ahead and do this now or
578 // schedule it for later
579 if (atomicComposition == null)
581 return partManager.TrySetImport(import, exports);
583 else
585 atomicComposition.AddCompleteAction(() => partManager.TrySetImport(import, exports).ThrowOnErrors());
588 else
590 partManager.SetSavedImport(import, exports, atomicComposition);
593 return CompositionResult.SucceededResult;
596 private void StartSatisfyingImports(PartManager partManager, AtomicComposition atomicComposition)
598 // When not running in a atomicCompositional state, schedule reindexing after ensuring
599 // that this isn't a redundant addition
600 if (atomicComposition == null)
602 if (!partManager.TrackingImports)
604 partManager.TrackingImports = true;
605 this._recompositionManager.AddPartToIndex(partManager);
608 else
610 // While in a atomicCompositional state use a less efficient but effective means
611 // of achieving the same results
612 GetEngineContext(atomicComposition).AddPartManager(partManager);
616 private void StopSatisfyingImports(PartManager partManager, AtomicComposition atomicComposition)
618 // When not running in a atomicCompositional state, schedule reindexing after ensuring
619 // that this isn't a redundant removal
620 if (atomicComposition == null)
622 this._partManagers.Remove(partManager.Part);
624 // Take care of lifetime requirements
625 partManager.DisposeAllDependencies();
627 if (partManager.TrackingImports)
629 partManager.TrackingImports = false;
630 this._recompositionManager.AddPartToUnindex(partManager);
633 else
635 // While in a atomicCompositional state use a less efficient but effective means
636 // of achieving the same results
637 GetEngineContext(atomicComposition).RemovePartManager(partManager);
641 private PartManager GetPartManager(ComposablePart part, bool createIfNotpresent)
643 PartManager partManager = null;
644 using (this._lock.LockStateForRead())
646 if (this._partManagers.TryGetValue(part, out partManager))
648 return partManager;
652 if (createIfNotpresent)
654 using (this._lock.LockStateForWrite())
656 if (!this._partManagers.TryGetValue(part, out partManager))
658 partManager = new PartManager(this, part);
659 this._partManagers.Add(part, partManager);
663 return partManager;
667 private EngineContext GetEngineContext(AtomicComposition atomicComposition)
669 Assumes.NotNull(atomicComposition);
671 EngineContext engineContext;
672 if (!atomicComposition.TryGetValue(this, true, out engineContext))
674 EngineContext parentContext;
675 atomicComposition.TryGetValue(this, false, out parentContext);
676 engineContext = new EngineContext(this, parentContext);
677 atomicComposition.SetValue(this, engineContext);
678 atomicComposition.AddCompleteAction(engineContext.Complete);
680 return engineContext;
683 private bool InPrerequisiteLoop()
685 PartManager firstPart = this._recursionStateStack.First();
686 PartManager lastPart = null;
688 foreach (PartManager testPart in this._recursionStateStack.Skip(1))
690 if (testPart.State == ImportState.PreExportImportsSatisfying)
692 return true;
695 if (testPart == firstPart)
697 lastPart = testPart;
698 break;
702 // This should only be called when a loop has been detected - so it should always be on the stack
703 Assumes.IsTrue(lastPart == firstPart);
704 return false;
707 [DebuggerStepThrough]
708 private void ThrowIfDisposed()
710 if (this._isDisposed)
712 throw ExceptionBuilder.CreateObjectDisposed(this);
716 private static CompositionResult<IEnumerable<Export>> TryGetExports(ExportProvider provider,
717 ComposablePart part, ImportDefinition definition, AtomicComposition atomicComposition)
721 var exports = provider.GetExports(definition, atomicComposition).AsArray();
722 return new CompositionResult<IEnumerable<Export>>(exports);
724 catch (ImportCardinalityMismatchException ex)
726 // Either not enough or too many exports that match the definition
727 CompositionException exception = new CompositionException(ErrorBuilder.CreateImportCardinalityMismatch(ex, definition));
729 return new CompositionResult<IEnumerable<Export>>(
730 ErrorBuilder.CreatePartCannotSetImport(part, definition, exception));
734 internal static bool IsRequiredImportForPreview(ImportDefinition import)
736 return import.Cardinality == ImportCardinality.ExactlyOne;
739 // Ordering of this enum is important so be sure to use caution if you
740 // try to reorder them.
741 private enum ImportState
743 NoImportsSatisfied = 0,
744 ImportsPreviewing = 1,
745 ImportsPreviewed = 2,
746 PreExportImportsSatisfying = 3,
747 PreExportImportsSatisfied = 4,
748 PostExportImportsSatisfying = 5,
749 PostExportImportsSatisfied = 6,
750 ComposedNotifying = 7,
751 Composed = 8,