| File: System\ComponentModel\Composition\Hosting\AtomicComposition.cs | Web Access |
| Project: src\runtime\src\libraries\System.ComponentModel.Composition\src\System.ComponentModel.Composition.csproj (System.ComponentModel.Composition) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Microsoft.Internal; namespace System.ComponentModel.Composition.Hosting { /// <summary> /// AtomicComposition provides lightweight atomicCompositional semantics to enable temporary /// state to be managed for a series of nested atomicCompositions. Each atomicComposition maintains /// queryable state along with a sequence of actions necessary to complete the state when /// the atomicComposition is no longer in danger of being rolled back. State is completed or /// rolled back when the atomicComposition is disposed, depending on the state of the /// CompleteOnDispose property which defaults to false. The using(...) pattern in C# is a /// convenient mechanism for defining atomicComposition scopes. /// /// The least obvious aspects of AtomicComposition deal with nesting. /// /// Firstly, no complete actions are actually performed until the outermost atomicComposition is /// completed. Completeting or rolling back nested atomicCompositions serves only to change which /// actions would be completed the outer atomicComposition. /// /// Secondly, state is added in the form of queries associated with an object key. The /// key represents a unique object the state is being held on behalf of. The quieries are /// accessed through the Query methods which provide automatic chaining to execute queries /// across the target atomicComposition and its inner atomicComposition as appropriate. /// /// Lastly, when a nested atomicComposition is created for a given outer the outer atomicComposition is locked. /// It remains locked until the inner atomicComposition is disposed or completeed preventing the addition of /// state, actions or other inner atomicCompositions. /// </summary> public class AtomicComposition : IDisposable { private readonly AtomicComposition? _outerAtomicComposition; private KeyValuePair<object, object?>[]? _values; private int _valueCount; private List<Action>? _completeActionList; private List<Action>? _revertActionList; private bool _isDisposed; private bool _isCompleted; private bool _containsInnerAtomicComposition; public AtomicComposition() : this(null) { } public AtomicComposition(AtomicComposition? outerAtomicComposition) { // Lock the inner atomicComposition so that we can assume nothing changes except on // the innermost scope, and thereby optimize the query path if (outerAtomicComposition != null) { _outerAtomicComposition = outerAtomicComposition; _outerAtomicComposition.ContainsInnerAtomicComposition = true; } } public void SetValue(object key, object? value) { ThrowIfDisposed(); ThrowIfCompleted(); ThrowIfContainsInnerAtomicComposition(); Requires.NotNull(key, nameof(key)); SetValueInternal(key, value); } public bool TryGetValue<T>(object key, [MaybeNullWhen(false)] out T value) { return TryGetValue(key, false, out value); } public bool TryGetValue<T>(object key, bool localAtomicCompositionOnly, [MaybeNullWhen(false)] out T value) { ThrowIfDisposed(); ThrowIfCompleted(); Requires.NotNull(key, nameof(key)); return TryGetValueInternal(key, localAtomicCompositionOnly, out value); } public void AddCompleteAction(Action completeAction) { ThrowIfDisposed(); ThrowIfCompleted(); ThrowIfContainsInnerAtomicComposition(); Requires.NotNull(completeAction, nameof(completeAction)); _completeActionList ??= new List<Action>(); _completeActionList.Add(completeAction); } public void AddRevertAction(Action revertAction) { ThrowIfDisposed(); ThrowIfCompleted(); ThrowIfContainsInnerAtomicComposition(); Requires.NotNull(revertAction, nameof(revertAction)); _revertActionList ??= new List<Action>(); _revertActionList.Add(revertAction); } public void Complete() { ThrowIfDisposed(); ThrowIfCompleted(); if (_outerAtomicComposition == null) { // Execute all the complete actions FinalComplete(); } else { // Copy the actions and state to the outer atomicComposition CopyComplete(); } _isCompleted = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { ThrowIfDisposed(); _isDisposed = true; _outerAtomicComposition?.ContainsInnerAtomicComposition = false; // Revert is always immediate and involves forgetting information and // exceuting any appropriate revert actions if (!_isCompleted) { if (_revertActionList != null) { List<Exception>? exceptions = null; // Execute the revert actions in reverse order to ensure // everything incrementally rollsback its state. for (int i = _revertActionList.Count - 1; i >= 0; i--) { Action action = _revertActionList[i]; try { action(); } catch (CompositionException) { // This can only happen after preview is completed, so ... abandon remainder of events is correct throw; } catch (Exception e) { //If any exceptions leak through the actions we will swallow them for now // complete processing the list // and we will throw InvalidOperationException with an AggregateException as it's innerException exceptions ??= new List<Exception>(); exceptions.Add(e); } } _revertActionList = null; if (exceptions != null) { throw new InvalidOperationException(SR.InvalidOperation_RevertAndCompleteActionsMustNotThrow, new AggregateException(exceptions)); } } } } private void FinalComplete() { // Completeting the outer most scope is easy, just execute all the actions if (_completeActionList != null) { List<Exception>? exceptions = null; foreach (Action action in _completeActionList) { try { action(); } catch (CompositionException) { // This can only happen after preview is completed, so ... abandon remainder of events is correct throw; } catch (Exception e) { //If any exceptions leak through the actions we will swallow them for now complete processing the list // and we will throw InvalidOperationException with an AggregateException as it's innerException exceptions ??= new List<Exception>(); exceptions.Add(e); } } _completeActionList = null; if (exceptions != null) { throw new InvalidOperationException(SR.InvalidOperation_RevertAndCompleteActionsMustNotThrow, new AggregateException(exceptions)); } } } 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 bool ContainsInnerAtomicComposition { set { if (value && _containsInnerAtomicComposition) { throw new InvalidOperationException(SR.AtomicComposition_AlreadyNested); } _containsInnerAtomicComposition = value; } } private bool TryGetValueInternal<T>(object key, bool localAtomicCompositionOnly, [MaybeNullWhen(false)] out T value) { for (var index = 0; index < _valueCount; index++) { if (_values![index].Key == key) { value = (T)_values[index].Value!; return true; } } // If there's no atomicComposition available then recurse until we hit the outermost // scope, where upon we go ahead and return null if (!localAtomicCompositionOnly && _outerAtomicComposition != null) { return _outerAtomicComposition.TryGetValueInternal<T>(key, localAtomicCompositionOnly, out value); } value = default; return false; } private void SetValueInternal(object key, object? value) { // Handle overwrites quickly for (var index = 0; index < _valueCount; index++) { if (_values![index].Key == key) { _values[index] = new KeyValuePair<object, object?>(key, value); return; } } // Expand storage when needed if (_values == null || _valueCount == _values.Length) { var newQueries = new KeyValuePair<object, object?>[_valueCount == 0 ? 5 : _valueCount * 2]; if (_values != null) { Array.Copy(_values, newQueries, _valueCount); } _values = newQueries; } // Store a new entry _values[_valueCount] = new KeyValuePair<object, object?>(key, value); _valueCount++; return; } [DebuggerStepThrough] private void ThrowIfContainsInnerAtomicComposition() { if (_containsInnerAtomicComposition) { throw new InvalidOperationException(SR.AtomicComposition_PartOfAnotherAtomicComposition); } } [DebuggerStepThrough] private void ThrowIfCompleted() { if (_isCompleted) { throw new InvalidOperationException(SR.AtomicComposition_AlreadyCompleted); } } [DebuggerStepThrough] private void ThrowIfDisposed() { if (_isDisposed) { throw ExceptionBuilder.CreateObjectDisposed(this); } } } }