/// <summary> /// Executes and, if necessary, retries an operation /// </summary> protected async Task <U> RetryAsync <U>( string operationName, Func <T, CancellationToken, Task <U> > fn, CancellationToken cancellationToken, IEnumerator <TimeSpan> retryIntervalEnumerator = null, bool reloadFirst = false, Guid?operationId = null, TimeSpan?timeout = null) { operationId = operationId ?? Guid.NewGuid(); retryIntervalEnumerator = retryIntervalEnumerator ?? m_retryIntervals.GetEnumerator(); timeout = timeout ?? s_defaultOperationTimeout; try { using (CancellationTokenSource timeoutCancellationSource = new CancellationTokenSource()) using (CancellationTokenSource innerCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationSource.Token)) { var instance = GetCurrentVersionedValue(); if (reloadFirst) { var reloaded = Reloader.Reload(instance.Version); m_logger.Warning("[{2}] Service client reloaded; new instance created: {0}, new client version: {1}", reloaded, Reloader.CurrentVersion, operationId.Value); } m_logger.Verbose("[{2}] Invoking '{0}' against instance version {1}", operationName, instance.Version, operationId.Value); return(await WithTimeoutAsync(fn(instance.Value, innerCancellationSource.Token), timeout.Value, timeoutCancellationSource)); } } catch (Exception e) when(m_nonRetryableExceptions.Contains(e.GetType())) { // We should not retry exceptions of this type. throw; } catch (Exception e) { if (e is TimeoutException) { m_logger.Warning("Timeout ({0}sec) happened while waiting {1}.", timeout.Value.TotalSeconds, operationName); } if (retryIntervalEnumerator.MoveNext()) { m_logger.Warning("[{2}] Waiting {1} before retrying on exception: {0}", e.ToString(), retryIntervalEnumerator.Current, operationId.Value); await Task.Delay(retryIntervalEnumerator.Current); return(await RetryAsync(operationName, fn, cancellationToken, retryIntervalEnumerator, reloadFirst : true, operationId : operationId)); } else { m_logger.Error("[{1}] Failing because number of retries were exhausted. Final exception: {0};", e.ToString(), operationId.Value); throw; } } }
/// <inheritdoc /> public async Task <AddDebugEntryResult> AddFileAsync(SymbolFile symbolFile) { Contract.Requires(symbolFile.IsIndexed, "File has not been indexed."); if (symbolFile.DebugEntries.Count == 0) { // If there are no debug entries, ask bxl to log a message and return early. Analysis.IgnoreResult(await m_apiClient.LogMessage(I($"File '{symbolFile.FullFilePath}' does not contain symbols and will not be added to '{RequestName}'."), isWarning: false)); return(AddDebugEntryResult.NoSymbolData); } await EnsureRequestIdInitalizedAsync(); List <DebugEntry> result; try { result = await m_symbolClient.CreateRequestDebugEntriesAsync( RequestId, symbolFile.DebugEntries.Select(e => CreateDebugEntry(e)), // First, we create debug entries with ThrowIfExists behavior not to silence the collision errors. DebugEntryCreateBehavior.ThrowIfExists, CancellationToken); } catch (DebugEntryExistsException) { string message = $"[SymbolDaemon] File: '{symbolFile.FullFilePath}' caused collision. " + (m_debugEntryCreateBehavior == DebugEntryCreateBehavior.ThrowIfExists ? string.Empty : $"SymbolDaemon will retry creating debug entry with {m_debugEntryCreateBehavior} behavior"); // Log a warning message in BuildXL log file Analysis.IgnoreResult(await m_apiClient.LogMessage(message, isWarning: true)); if (m_debugEntryCreateBehavior == DebugEntryCreateBehavior.ThrowIfExists) { // Log an error message in SymbolDaemon log file m_logger.Error(message); throw new DebugEntryExistsException(message); } // Log a warning message in SymbolDaemon log file m_logger.Warning(message); result = await m_symbolClient.CreateRequestDebugEntriesAsync( RequestId, symbolFile.DebugEntries.Select(e => CreateDebugEntry(e)), m_debugEntryCreateBehavior, CancellationToken); } var entriesWithMissingBlobs = result.Where(e => e.Status == DebugEntryStatus.BlobMissing).ToList(); if (entriesWithMissingBlobs.Count > 0) { // All the entries are based on the same file, so we need to call upload only once. // make sure that the file is on disk (it might not be on disk if we got DebugEntries from cache/metadata file) var file = await symbolFile.EnsureMaterializedAsync(); var uploadResult = await m_symbolClient.UploadFileAsync( // uploading to the location set by the symbol service entriesWithMissingBlobs[0].BlobUri, RequestId, symbolFile.FullFilePath, entriesWithMissingBlobs[0].BlobIdentifier, CancellationToken); m_logger.Info($"File: '{symbolFile.FullFilePath}' -- upload result: {uploadResult.ToString()}"); entriesWithMissingBlobs.ForEach(entry => entry.BlobDetails = uploadResult); entriesWithMissingBlobs = await m_symbolClient.CreateRequestDebugEntriesAsync( RequestId, entriesWithMissingBlobs, m_debugEntryCreateBehavior, CancellationToken); Contract.Assert(entriesWithMissingBlobs.All(e => e.Status != DebugEntryStatus.BlobMissing), "Entries with non-success code are present."); return(AddDebugEntryResult.UploadedAndAssociated); } return(AddDebugEntryResult.Associated); }