| File: ExternalAccess\UnitTesting\SolutionCrawler\UnitTestingSolutionCrawlerProgressReporter.cs | Web Access |
| Project: src\roslyn\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features) |
// 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. using System; using System.Threading; namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.SolutionCrawler; internal sealed partial class UnitTestingSolutionCrawlerRegistrationService : IUnitTestingSolutionCrawlerRegistrationService { /// <summary> /// Progress reporter /// /// this progress reporter is a best effort implementation. it doesn't stop world to find out accurate data /// /// what this reporter care is we show start/stop background work and show things are moving or paused /// without too much cost. /// /// due to how solution cralwer calls Start/Stop (see caller of those 2), those 2 can't have a race /// and that is all we care for this reporter /// </summary> internal sealed class UnitTestingSolutionCrawlerProgressReporter : IUnitTestingSolutionCrawlerProgressReporter { // we use ref count here since solution crawler has multiple queues per priority // where an item can be enqueued and dequeued independently. // first item added in any of those queues will cause the "start" event to be sent // and the very last item processed from those queues will cause "stop" event to be sent // evaluating and paused is also ref counted since work in the lower priority queue can // be canceled due to new higher priority work item enqueued to higher queue. // but before lower priority work actually exit due to cancellation, higher work could // start processing. causing an overlap. the ref count make sure that exiting lower // work doesn't flip evaluating state to paused state. private int _progressStartCount = 0; private int _progressEvaluateCount = 0; public event EventHandler<UnitTestingProgressData>? ProgressChanged; public bool InProgress => _progressStartCount > 0; public void Start() => ChangeProgressStatus(ref _progressStartCount, UnitTestingProgressStatus.Started); public void Stop() => ChangeProgressStatus(ref _progressStartCount, UnitTestingProgressStatus.Stopped); private void Evaluate() => ChangeProgressStatus(ref _progressEvaluateCount, UnitTestingProgressStatus.Evaluating); private void Pause() => ChangeProgressStatus(ref _progressEvaluateCount, UnitTestingProgressStatus.Paused); public void UpdatePendingItemCount(int pendingItemCount) { if (_progressStartCount > 0) { var progressData = new UnitTestingProgressData(UnitTestingProgressStatus.PendingItemCountUpdated, pendingItemCount); OnProgressChanged(progressData); } } /// <summary> /// Allows the solution crawler to start evaluating work enqueued to it. /// Returns an IDisposable that the caller must dispose of to indicate that it no longer needs the crawler to continue evaluating. /// Multiple callers can call into this simultaneously. /// Only when the last one actually disposes the scope-object will the crawler /// actually revert back to the paused state where no work proceeds. /// </summary> public IDisposable GetEvaluatingScope() => new UnitTestingProgressStatusRAII(this); private void ChangeProgressStatus(ref int referenceCount, UnitTestingProgressStatus status) { var start = status is UnitTestingProgressStatus.Started or UnitTestingProgressStatus.Evaluating; if (start ? (Interlocked.Increment(ref referenceCount) == 1) : (Interlocked.Decrement(ref referenceCount) == 0)) { var progressData = new UnitTestingProgressData(status, pendingItemCount: null); OnProgressChanged(progressData); } } private void OnProgressChanged(UnitTestingProgressData progressData) => ProgressChanged?.Invoke(this, progressData); private readonly struct UnitTestingProgressStatusRAII : IDisposable { private readonly UnitTestingSolutionCrawlerProgressReporter _owner; public UnitTestingProgressStatusRAII(UnitTestingSolutionCrawlerProgressReporter owner) { _owner = owner; _owner.Evaluate(); } public void Dispose() => _owner.Pause(); } } /// <summary> /// reporter that doesn't do anything /// </summary> private sealed class UnitTestingNullReporter : IUnitTestingSolutionCrawlerProgressReporter { public static readonly UnitTestingNullReporter Instance = new(); public bool InProgress => false; public event EventHandler<UnitTestingProgressData> ProgressChanged { add { } remove { } } } }