File: ExternalAccess\UnitTesting\SolutionCrawler\UnitTestingWorkCoordinator.AbstractUnitTestingPriorityProcessor.cs
Web Access
Project: src\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.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Notification;
using Microsoft.CodeAnalysis.Shared.TestHooks;
 
namespace Microsoft.CodeAnalysis.ExternalAccess.UnitTesting.SolutionCrawler;
 
internal sealed partial class UnitTestingSolutionCrawlerRegistrationService
{
    internal sealed partial class UnitTestingWorkCoordinator
    {
        private sealed partial class UnitTestingIncrementalAnalyzerProcessor
        {
            private abstract class AbstractUnitTestingPriorityProcessor : UnitTestingGlobalOperationAwareIdleProcessor
            {
                protected readonly UnitTestingIncrementalAnalyzerProcessor Processor;
 
                private readonly object _gate = new();
                private Lazy<ImmutableArray<IUnitTestingIncrementalAnalyzer>> _lazyAnalyzers;
 
                public AbstractUnitTestingPriorityProcessor(
                    IAsynchronousOperationListener listener,
                    UnitTestingIncrementalAnalyzerProcessor processor,
                    Lazy<ImmutableArray<IUnitTestingIncrementalAnalyzer>> lazyAnalyzers,
                    IGlobalOperationNotificationService? globalOperationNotificationService,
                    TimeSpan backOffTimeSpan,
                    CancellationToken shutdownToken)
                    : base(listener, globalOperationNotificationService, backOffTimeSpan, shutdownToken)
                {
                    _lazyAnalyzers = lazyAnalyzers;
 
                    Processor = processor;
                }
 
                public ImmutableArray<IUnitTestingIncrementalAnalyzer> Analyzers
                {
                    get
                    {
                        lock (_gate)
                        {
                            return _lazyAnalyzers.Value;
                        }
                    }
                }
 
                public void AddAnalyzer(IUnitTestingIncrementalAnalyzer analyzer)
                {
                    lock (_gate)
                    {
                        var analyzers = _lazyAnalyzers.Value;
                        _lazyAnalyzers = new Lazy<ImmutableArray<IUnitTestingIncrementalAnalyzer>>(() => analyzers.Add(analyzer));
                    }
                }
 
                protected override void OnPaused()
                    => UnitTestingSolutionCrawlerLogger.LogGlobalOperation(Processor._logAggregator);
 
                protected abstract Task HigherQueueOperationTask { get; }
                protected abstract bool HigherQueueHasWorkItem { get; }
 
                protected async Task WaitForHigherPriorityOperationsAsync()
                {
                    using (Logger.LogBlock(FunctionId.WorkCoordinator_WaitForHigherPriorityOperationsAsync, CancellationToken))
                    {
                        while (true)
                        {
                            CancellationToken.ThrowIfCancellationRequested();
 
                            // we wait for global operation and higher queue operation if there is anything going on
                            await HigherQueueOperationTask.ConfigureAwait(false);
 
                            if (HigherQueueHasWorkItem)
                            {
                                // There was still something more important in another queue.  Back off again (e.g.
                                // call UpdateLastAccessTime) then wait that amount of time and check again to see
                                // if that queue is clear.
                                UpdateLastAccessTime();
                                await WaitForIdleAsync(Listener).ConfigureAwait(false);
                                continue;
                            }
 
                            if (GetIsPaused())
                            {
                                // if we're paused, we still want to keep waiting until we become unpaused. After we
                                // become unpaused though, loop around those to see if there is still high pri work
                                // to do.
                                await WaitForIdleAsync(Listener).ConfigureAwait(false);
                                continue;
                            }
 
                            // There was no higher queue work item and we're not paused. However, we may not have
                            // waited long enough to actually satisfy our own backoff-delay.  If so, wait until
                            // we're actually idle.
                            if (ShouldContinueToBackOff())
                            {
                                // Do the wait.  If it returns 'true' then we did the full wait.  Loop around again
                                // to see if there is higher priority work, or if we got paused.
 
                                // However, if it returns 'false' then that means the delay completed quickly
                                // because some unit/integration test is asking us to expedite our work.  In that
                                // case, just return out immediately so we can process what is in our queue.
                                if (await WaitForIdleAsync(Listener).ConfigureAwait(false))
                                    continue;
 
                                // intentional fall-through.
                            }
 
                            return;
                        }
                    }
                }
            }
        }
    }
}