private async Task StartBackgroundJobRunnerAsync()

in src/NuGet.Clients/NuGet.SolutionRestoreManager/SolutionRestoreWorker.cs [457:642]


        private async Task<bool> StartBackgroundJobRunnerAsync(CancellationToken token)
        {
            // Hops onto a background pool thread
            await TaskScheduler.Default;

            var status = false;
            // Check if the solution is fully loaded
            while (!_solutionLoadedEvent.IsSet)
            {
                // Needed when OnAfterBackgroundSolutionLoadComplete fires before
                // Advise has been called.
                if (await IsSolutionFullyLoadedAsync())
                {
                    _solutionLoadedEvent.Set();
                    break;
                }
                else
                {
                    // Waits for 100ms to let solution fully load or canceled
                    await _solutionLoadedEvent.WaitAsync()
                        .WithTimeout(TimeSpan.FromMilliseconds(DelaySolutionLoadRetry))
                        .WithCancellation(token);
                }
            }

            ImplicitRestoreReason restoreReason = ImplicitRestoreReason.None;
            DateTime? bulkRestoreCoordinationCheckStartTime = default;
            // Loops until there are pending restore requests or it's get cancelled
            while (!token.IsCancellationRequested)
            {
                lock (_lockPendingRequestsObj)
                {
                    // if no pending restore requests then shut down the restore job runner.
                    if (_pendingRequests.Value.Count == 0)
                    {
                        break;
                    }
                }

                // Grabs a local copy of pending restore operation
                using (var restoreOperation = _pendingRestore)
                {
                    try
                    {
                        // Blocks the execution until first request is scheduled
                        // Monitors the cancelllation token as well.
                        var request = _pendingRequests.Value.Take(token);

                        token.ThrowIfCancellationRequested();

                        // Claims the ownership over the active task
                        // Awaits for currently running restore to complete
                        await PromoteTaskToActiveAsync(restoreOperation, token);

                        token.ThrowIfCancellationRequested();
                        DateTime lastNominationReceived = DateTime.UtcNow;
                        int requestCount = 1;
                        int projectsReadyCheckCount = 0;
                        int projectRestoreInfoSourcesCount = -1;
                        ExplicitRestoreReason explicitRestoreReason = request.ExplicitRestoreReason;
                        List<TimeSpan> projectReadyTimings = null;
                        // Drains the queue
                        while (!_pendingRequests.Value.IsCompleted
                            && !token.IsCancellationRequested)
                        {
                            SolutionRestoreRequest next;

                            // check if there are pending nominations
                            var isAllProjectsNominated = await _solutionManager.Value.IsAllProjectsNominatedAsync();

                            // Try to get a request without a timeout. We don't want to *block* the threadpool thread.
                            if (!_pendingRequests.Value.TryTake(out next, millisecondsTimeout: 0, token))
                            {
                                if (isAllProjectsNominated)
                                {
                                    var projectReadyCheckMeasurement = Stopwatch.StartNew();
                                    if (bulkRestoreCoordinationCheckStartTime == default)
                                    {
                                        bulkRestoreCoordinationCheckStartTime = DateTime.UtcNow;
                                    }
                                    projectsReadyCheckCount++;
                                    // If we are about to start restore, we should run through all the projects to ensure there isn't a pending nomination.
                                    IReadOnlyList<object> restoreProjectInfoSources = _solutionManager.Value.GetAllProjectRestoreInfoSources();
                                    projectRestoreInfoSourcesCount = restoreProjectInfoSources.Count;
                                    var allProjectsReady = true;
                                    var bulkCheckTimeout = false;
                                    for (int i = 0; i < restoreProjectInfoSources.Count && !bulkCheckTimeout; i++)
                                    {
                                        var restoreInfoSource = (IVsProjectRestoreInfoSource)restoreProjectInfoSources[i];
                                        if (restoreInfoSource.HasPendingNomination)
                                        {
                                            allProjectsReady = false;
                                            TimeSpan timeoutTime = CalculateTimeoutTime(bulkRestoreCoordinationCheckStartTime.Value, DateTime.UtcNow, BulkRestoreCoordinationTimeout);
                                            var timeoutTask = Task.Delay(timeoutTime, token);
                                            var whenNominatedTask = restoreInfoSource.WhenNominated(token);

                                            var result = await Task.WhenAny(whenNominatedTask, timeoutTask);
                                            if (result == timeoutTask)
                                            {
                                                bulkCheckTimeout = true;
                                            }
                                        }
                                    }

                                    projectReadyCheckMeasurement.Stop();
                                    if (projectReadyTimings == null)
                                    {
                                        projectReadyTimings = new();
                                    }
                                    projectReadyTimings.Add(projectReadyCheckMeasurement.Elapsed);

                                    if (allProjectsReady)
                                    {
                                        restoreReason = ImplicitRestoreReason.ProjectsReady;
                                        break;
                                    }
                                    if (bulkCheckTimeout)
                                    {
                                        restoreReason = ImplicitRestoreReason.ProjectsReadyCheckTimeout;
                                        break;
                                    }
                                }
                                else
                                {
                                    await Task.Delay(IdleTimeoutMs, token);
                                }
                            }
                            else
                            {
                                requestCount++;
                                lastNominationReceived = DateTime.UtcNow;
                                // Upgrade request if necessary
                                if (next != null && next.RestoreSource != request.RestoreSource)
                                {
                                    // there could be requests of two types: Auto-Restore or Explicit
                                    // Explicit is always preferred.
                                    request = new SolutionRestoreRequest(
                                        next.ForceRestore || request.ForceRestore,
                                        RestoreOperationSource.Explicit,
                                        next.ExplicitRestoreReason);

                                    // we don't want to delay explicit solution restore request so just break at this time.
                                    restoreReason = ImplicitRestoreReason.None;
                                    break;
                                }
                            }
                        }

                        token.ThrowIfCancellationRequested();

                        // Replaces pending restore operation with a new one.
                        // Older value is ignored.
                        var ignore = Interlocked.CompareExchange(
                            ref _pendingRestore, new BackgroundRestoreOperation(), restoreOperation);

                        token.ThrowIfCancellationRequested();

                        Dictionary<string, object> restoreStartTrackingData = GetRestoreTrackingData(
                            restoreReason,
                            requestCount,
                            projectRestoreInfoSourcesCount,
                            bulkRestoreCoordinationCheckStartTime,
                            projectsReadyCheckCount,
                            projectReadyTimings,
                            explicitRestoreReason);

                        // Runs restore job with scheduled request params
                        status = await ProcessRestoreRequestAsync(restoreOperation, request, restoreStartTrackingData, token);

                        // Repeats...
                    }
                    catch (OperationCanceledException) when (token.IsCancellationRequested)
                    {
                        // Ignores
                    }
                    catch (Exception e)
                    {
                        // Writes stack to activity log
                        Logger.LogError(e.ToString());
                        // Do not die just yet
                    }
                }
            }

            return status;
        }