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;
}