| File: Symbols\SymbolCompletionState.cs | Web Access |
| Project: src\roslyn\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable disable using System.Diagnostics; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; using System.Text; using Microsoft.CodeAnalysis.Collections; namespace Microsoft.CodeAnalysis.CSharp.Symbols { internal struct SymbolCompletionState { /// <summary> /// This field keeps track of the <see cref="CompletionPart"/>s for which we already retrieved /// diagnostics. We shouldn't return from ForceComplete (i.e. indicate that diagnostics are /// available) until this is equal to <see cref="CompletionPart.All"/>, except that when completing /// with a given position, we might not complete <see cref="CompletionPart"/>.Member*. /// /// Since completeParts is used as a flag indicating completion of other assignments /// it must be volatile to ensure the read is not reordered/optimized to happen /// before the writes. /// </summary> private volatile int _completeParts; internal int IncompleteParts { get { return ~_completeParts & (int)CompletionPart.All; } } /// <summary> /// Used to force (source) symbols to a given state of completion when the only potential remaining /// part is attributes. This does force the invariant on the caller that the implementation of /// of <see cref="Symbol.GetAttributes"/> will set the part <see cref="CompletionPart.Attributes"/> on /// the thread that actually completes the loading of attributes. Failure to do so will potentially /// result in a deadlock. /// </summary> /// <param name="symbol">The owning source symbol.</param> internal void DefaultForceComplete(Symbol symbol, CancellationToken cancellationToken) { Debug.Assert(symbol.RequiresCompletion); if (!HasComplete(CompletionPart.Attributes)) { _ = symbol.GetAttributes(); // Consider the following items: // 1. It is possible for parallel calls to GetAttributes to exist // 2. GetAttributes implementation can validly return when the attributes are available but before the // CompletionParts.Attributes value is set. // 3. GetAttributes implementation typically have the invariant that the thread which completes the // loading of attributes is the one which sets CompletionParts.Attributes. // 4. This call cannot correctly return until CompletionParts.Attributes is set. // // Note: #2 above is common practice amongst all of the symbols. // // Note: #3 above is an invariant that has existed in the code base for some time. It's not 100% clear // whether this invariant is tied to correctness or not. The most compelling example though is // SourceEventSymbol which raises SymbolDeclaredEvent before CompletionPart.Attributes is noted as completed. // Many other implementations have this pattern but no apparent code which could depend on it. SpinWaitComplete(CompletionPart.Attributes, cancellationToken); } // any other values are completion parts intended for other kinds of symbols NotePartComplete(CompletionPart.All); } internal bool HasComplete(CompletionPart part) { // completeParts is used as a flag indicating completion of other assignments // Volatile.Read is used to ensure the read is not reordered/optimized to happen // before the writes. return (_completeParts & (int)part) == (int)part; } internal bool NotePartComplete(CompletionPart part) { // passing volatile completeParts byref is ok here. // ThreadSafeFlagOperations.Set performs interlocked assignments #pragma warning disable 0420 return ThreadSafeFlagOperations.Set(ref _completeParts, (int)part); #pragma warning restore 0420 } /// <summary> /// Produce the next (i.e. lowest) CompletionPart (bit) that is not set. /// </summary> internal CompletionPart NextIncompletePart { get { // NOTE: It's very important to store this value in a local. // If we were to inline the field access, the value of the // field could change between the two accesses and the formula // might not produce a result with a single 1-bit. int incomplete = IncompleteParts; int next = incomplete & ~(incomplete - 1); Debug.Assert(HasAtMostOneBitSet(next), "ForceComplete won't handle the result correctly if more than one bit is set."); return (CompletionPart)next; } } /// <remarks> /// Since this formula is rather opaque, a demonstration of its correctness is /// provided in Roslyn.Compilers.CSharp.UnitTests.CompletionTests.TestHasAtMostOneBitSet. /// </remarks> internal static bool HasAtMostOneBitSet(int bits) { return (bits & (bits - 1)) == 0; } internal void SpinWaitComplete(CompletionPart part, CancellationToken cancellationToken) { if (HasComplete(part)) { return; } // Don't return until we've seen all of the requested CompletionParts. This ensures all // diagnostics have been reported (not necessarily on this thread). var spinWait = new SpinWait(); while (!HasComplete(part)) { cancellationToken.ThrowIfCancellationRequested(); spinWait.SpinOnce(); } } public override string ToString() { StringBuilder result = new StringBuilder(); result.Append("CompletionParts("); bool any = false; for (int i = 0; ; i++) { int bit = (1 << i); if ((bit & (int)CompletionPart.All) == 0) break; if ((bit & _completeParts) != 0) { if (any) result.Append(", "); result.Append(i.ToString(System.Globalization.CultureInfo.InvariantCulture)); any = true; } } result.Append(')'); return result.ToString(); } } }