private async Task ProcessDocumentAsync(ImmutableArray <IIncrementalAnalyzer> analyzers, WorkItem workItem, CancellationTokenSource source) { if (this.CancellationToken.IsCancellationRequested) { return; } var processedEverything = false; var documentId = workItem.DocumentId; try { using (Logger.LogBlock(FunctionId.WorkCoordinator_ProcessDocumentAsync, source.Token)) { var cancellationToken = source.Token; var document = _processingSolution.GetDocument(documentId); if (document != null) { // if we are called because a document is opened, we invalidate the document so that // it can be re-analyzed. otherwise, since newly opened document has same version as before // analyzer will simply return same data back if (workItem.MustRefresh && !workItem.IsRetry) { var isOpen = document.IsOpen(); await ProcessOpenDocumentIfNeeded(analyzers, workItem, document, isOpen, cancellationToken).ConfigureAwait(false); await ProcessCloseDocumentIfNeeded(analyzers, workItem, document, isOpen, cancellationToken).ConfigureAwait(false); } // check whether we are having special reanalyze request await ProcessReanalyzeDocumentAsync(workItem, document, cancellationToken).ConfigureAwait(false); await ProcessDocumentAnalyzersAsync(document, analyzers, workItem, cancellationToken).ConfigureAwait(false); } else { SolutionCrawlerLogger.LogProcessDocumentNotExist(this.Processor._logAggregator); RemoveDocument(documentId); } if (!cancellationToken.IsCancellationRequested) { processedEverything = true; } } } catch (Exception e) when(FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } finally { // we got cancelled in the middle of processing the document. // let's make sure newly enqueued work item has all the flag needed. if (!processedEverything) { _workItemQueue.AddOrReplace(workItem.Retry(this.Listener.BeginAsyncOperation("ReenqueueWorkItem"))); } SolutionCrawlerLogger.LogProcessDocument(this.Processor._logAggregator, documentId.Id, processedEverything); // remove one that is finished running _workItemQueue.RemoveCancellationSource(workItem.DocumentId); } }
private async Task ProcessDocumentAsync(ImmutableArray <IIncrementalAnalyzer> analyzers, WorkItem workItem, CancellationToken cancellationToken) { Contract.ThrowIfNull(workItem.DocumentId); if (CancellationToken.IsCancellationRequested) { return; } var processedEverything = false; var documentId = workItem.DocumentId; // we should always use solution snapshot after workitem is removed from the queue. // otherwise, we can have a race such as below. // // 1.solution crawler picked up a solution // 2.before processing the solution, an workitem got changed // 3.and then the work item got picked up from the queue // 4.and use the work item with the solution that got picked up in step 1 // // step 2 is happening because solution has changed, but step 4 used old solution from step 1 // that doesn't have effects of the solution changes. // // solution crawler must remove the work item from the queue first and then pick up the soluton, // so that the queue gets new work item if there is any solution changes after the work item is removed // from the queue // // using later version of solution is always fine since, as long as there is new work item in the queue, // solution crawler will eventually call the last workitem with the lastest solution // making everything to catch up var solution = Processor.CurrentSolution; try { using (Logger.LogBlock(FunctionId.WorkCoordinator_ProcessDocumentAsync, w => w.ToString(), workItem, cancellationToken)) { var document = solution.GetDocument(documentId); if (document != null) { // if we are called because a document is opened, we invalidate the document so that // it can be re-analyzed. otherwise, since newly opened document has same version as before // analyzer will simply return same data back if (workItem.MustRefresh && !workItem.IsRetry) { var isOpen = document.IsOpen(); await ProcessOpenDocumentIfNeededAsync(analyzers, workItem, document, isOpen, cancellationToken).ConfigureAwait(false); await ProcessCloseDocumentIfNeededAsync(analyzers, workItem, document, isOpen, cancellationToken).ConfigureAwait(false); } // check whether we are having special reanalyze request await ProcessReanalyzeDocumentAsync(workItem, document, cancellationToken).ConfigureAwait(false); await Processor.ProcessDocumentAnalyzersAsync(document, analyzers, workItem, cancellationToken).ConfigureAwait(false); } else { SolutionCrawlerLogger.LogProcessDocumentNotExist(Processor._logAggregator); await RemoveDocumentAsync(documentId, cancellationToken).ConfigureAwait(false); } if (!cancellationToken.IsCancellationRequested) { processedEverything = true; } } } catch (Exception e) when(FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } finally { // we got cancelled in the middle of processing the document. // let's make sure newly enqueued work item has all the flag needed. // Avoid retry attempts after cancellation is requested, since work will not be processed // after that point. if (!processedEverything && !CancellationToken.IsCancellationRequested) { _workItemQueue.AddOrReplace(workItem.Retry(Listener.BeginAsyncOperation("ReenqueueWorkItem"))); } SolutionCrawlerLogger.LogProcessDocument(Processor._logAggregator, documentId.Id, processedEverything); // remove one that is finished running _workItemQueue.MarkWorkItemDoneFor(workItem.DocumentId); } }