-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Properly guard upload #2439
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Properly guard upload #2439
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,7 +20,7 @@ public interface IJobServerQueue : IRunnerService, IThrottlingReporter | |
| void Start(Pipelines.AgentJobRequestMessage jobRequest); | ||
| void QueueWebConsoleLine(Guid stepRecordId, string line, long? lineNumber = null); | ||
| void QueueFileUpload(Guid timelineId, Guid timelineRecordId, string type, string name, string path, bool deleteSource); | ||
| void QueueSummaryUpload(Guid stepRecordId, string name, string path, bool deleteSource); | ||
| void QueueResultsUpload(Guid timelineRecordId, string name, string path, string type, bool deleteSource, bool finalize, bool firstBlock, long totalLines); | ||
| void QueueTimelineRecordUpdate(Guid timelineId, TimelineRecord timelineRecord); | ||
| } | ||
|
|
||
|
|
@@ -31,7 +31,7 @@ public sealed class JobServerQueue : RunnerService, IJobServerQueue | |
| private static readonly TimeSpan _delayForWebConsoleLineDequeue = TimeSpan.FromMilliseconds(500); | ||
| private static readonly TimeSpan _delayForTimelineUpdateDequeue = TimeSpan.FromMilliseconds(500); | ||
| private static readonly TimeSpan _delayForFileUploadDequeue = TimeSpan.FromMilliseconds(1000); | ||
| private static readonly TimeSpan _delayForSummaryUploadDequeue = TimeSpan.FromMilliseconds(1000); | ||
| private static readonly TimeSpan _delayForResultsUploadDequeue = TimeSpan.FromMilliseconds(1000); | ||
|
|
||
| // Job message information | ||
| private Guid _scopeIdentifier; | ||
|
|
@@ -46,7 +46,7 @@ public sealed class JobServerQueue : RunnerService, IJobServerQueue | |
| // queue for file upload (log file or attachment) | ||
| private readonly ConcurrentQueue<UploadFileInfo> _fileUploadQueue = new(); | ||
|
|
||
| private readonly ConcurrentQueue<SummaryUploadFileInfo> _summaryFileUploadQueue = new(); | ||
| private readonly ConcurrentQueue<ResultsUploadFileInfo> _resultsFileUploadQueue = new(); | ||
|
|
||
| // queue for timeline or timeline record update (one queue per timeline) | ||
| private readonly ConcurrentDictionary<Guid, ConcurrentQueue<TimelineRecord>> _timelineUpdateQueue = new(); | ||
|
|
@@ -60,7 +60,7 @@ public sealed class JobServerQueue : RunnerService, IJobServerQueue | |
| // Task for each queue's dequeue process | ||
| private Task _webConsoleLineDequeueTask; | ||
| private Task _fileUploadDequeueTask; | ||
| private Task _summaryUploadDequeueTask; | ||
| private Task _resultsUploadDequeueTask; | ||
| private Task _timelineUpdateDequeueTask; | ||
|
|
||
| // common | ||
|
|
@@ -84,6 +84,8 @@ public sealed class JobServerQueue : RunnerService, IJobServerQueue | |
| private bool _webConsoleLineAggressiveDequeue = true; | ||
| private bool _firstConsoleOutputs = true; | ||
|
|
||
| private bool _resultsClientInitiated = false; | ||
|
|
||
| public override void Initialize(IHostContext hostContext) | ||
| { | ||
| base.Initialize(hostContext); | ||
|
|
@@ -109,9 +111,9 @@ public void Start(Pipelines.AgentJobRequestMessage jobRequest) | |
| { | ||
| Trace.Info("Initializing results client"); | ||
| _jobServer.InitializeResultsClient(new Uri(resultsReceiverEndpoint), accessToken); | ||
| _resultsClientInitiated = true; | ||
| } | ||
|
|
||
|
|
||
| if (_queueInProcess) | ||
| { | ||
| Trace.Info("No-opt, all queue process tasks are running."); | ||
|
|
@@ -140,12 +142,12 @@ public void Start(Pipelines.AgentJobRequestMessage jobRequest) | |
| _fileUploadDequeueTask = ProcessFilesUploadQueueAsync(); | ||
|
|
||
| Trace.Info("Start results file upload queue."); | ||
| _summaryUploadDequeueTask = ProcessSummaryUploadQueueAsync(); | ||
| _resultsUploadDequeueTask = ProcessResultsUploadQueueAsync(); | ||
|
|
||
| Trace.Info("Start process timeline update queue."); | ||
| _timelineUpdateDequeueTask = ProcessTimelinesUpdateQueueAsync(); | ||
|
|
||
| _allDequeueTasks = new Task[] { _webConsoleLineDequeueTask, _fileUploadDequeueTask, _timelineUpdateDequeueTask, _summaryUploadDequeueTask }; | ||
| _allDequeueTasks = new Task[] { _webConsoleLineDequeueTask, _fileUploadDequeueTask, _timelineUpdateDequeueTask, _resultsUploadDequeueTask }; | ||
| _queueInProcess = true; | ||
| } | ||
|
|
||
|
|
@@ -176,9 +178,9 @@ public async Task ShutdownAsync() | |
| await ProcessFilesUploadQueueAsync(runOnce: true); | ||
| Trace.Info("File upload queue drained."); | ||
|
|
||
| Trace.Verbose("Draining results summary upload queue."); | ||
| await ProcessSummaryUploadQueueAsync(runOnce: true); | ||
| Trace.Info("Results summary upload queue drained."); | ||
| Trace.Verbose("Draining results upload queue."); | ||
| await ProcessResultsUploadQueueAsync(runOnce: true); | ||
| Trace.Info("Results upload queue drained."); | ||
|
|
||
| // ProcessTimelinesUpdateQueueAsync() will throw exception during shutdown | ||
| // if there is any timeline records that failed to update contains output variabls. | ||
|
|
@@ -230,21 +232,49 @@ public void QueueFileUpload(Guid timelineId, Guid timelineRecordId, string type, | |
| _fileUploadQueue.Enqueue(newFile); | ||
| } | ||
|
|
||
| public void QueueSummaryUpload(Guid stepRecordId, string name, string path, bool deleteSource) | ||
| public void QueueResultsUpload(Guid timelineRecordId, string name, string path, string type, bool deleteSource, bool finalize, bool firstBlock, long totalLines) | ||
| { | ||
| if (!_resultsClientInitiated) | ||
| { | ||
| Trace.Verbose("Skipping results upload"); | ||
|
yacaovsnc marked this conversation as resolved.
|
||
| try | ||
| { | ||
| if (deleteSource) | ||
| { | ||
| File.Delete(path); | ||
| } | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| Trace.Info("Catch exception during delete skipped results upload file."); | ||
| Trace.Error(ex); | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| if (timelineRecordId == _jobTimelineRecordId && String.Equals(type, CoreAttachmentType.ResultsLog, StringComparison.Ordinal)) | ||
| { | ||
| Trace.Verbose("Skipping job log {0} for record {1}", path, timelineRecordId); | ||
| return; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here, do we need to delete the file?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not going to delete here since I am going to remove this check now in my next PR. I already have the code mostly ready, just need to test and submit a PR. The general ff case is handled by the first delete above. |
||
| } | ||
|
|
||
|
yacaovsnc marked this conversation as resolved.
|
||
| // all parameter not null, file path exist. | ||
| var newFile = new SummaryUploadFileInfo() | ||
| var newFile = new ResultsUploadFileInfo() | ||
| { | ||
| Name = name, | ||
| Path = path, | ||
| Type = type, | ||
| PlanId = _planId.ToString(), | ||
| JobId = _jobTimelineRecordId.ToString(), | ||
| StepId = stepRecordId.ToString(), | ||
| DeleteSource = deleteSource | ||
| RecordId = timelineRecordId, | ||
| DeleteSource = deleteSource, | ||
| Finalize = finalize, | ||
| FirstBlock = firstBlock, | ||
| TotalLines = totalLines, | ||
| }; | ||
|
|
||
| Trace.Verbose("Enqueue results file upload queue: file '{0}' attach to job {1} step {2}", newFile.Path, _jobTimelineRecordId, stepRecordId); | ||
| _summaryFileUploadQueue.Enqueue(newFile); | ||
| Trace.Verbose("Enqueue results file upload queue: file '{0}' attach to job {1} step {2}", newFile.Path, _jobTimelineRecordId, timelineRecordId); | ||
| _resultsFileUploadQueue.Enqueue(newFile); | ||
| } | ||
|
|
||
| public void QueueTimelineRecordUpdate(Guid timelineId, TimelineRecord timelineRecord) | ||
|
|
@@ -437,18 +467,18 @@ private async Task ProcessFilesUploadQueueAsync(bool runOnce = false) | |
| } | ||
| } | ||
|
|
||
| private async Task ProcessSummaryUploadQueueAsync(bool runOnce = false) | ||
| private async Task ProcessResultsUploadQueueAsync(bool runOnce = false) | ||
| { | ||
| Trace.Info("Starting results-based upload queue..."); | ||
|
|
||
| while (!_jobCompletionSource.Task.IsCompleted || runOnce) | ||
| { | ||
| List<SummaryUploadFileInfo> filesToUpload = new(); | ||
| SummaryUploadFileInfo dequeueFile; | ||
| while (_summaryFileUploadQueue.TryDequeue(out dequeueFile)) | ||
| List<ResultsUploadFileInfo> filesToUpload = new(); | ||
| ResultsUploadFileInfo dequeueFile; | ||
| while (_resultsFileUploadQueue.TryDequeue(out dequeueFile)) | ||
| { | ||
| filesToUpload.Add(dequeueFile); | ||
| // process at most 10 file upload. | ||
| // process at most 10 file uploads. | ||
| if (!runOnce && filesToUpload.Count > 10) | ||
| { | ||
| break; | ||
|
|
@@ -459,19 +489,27 @@ private async Task ProcessSummaryUploadQueueAsync(bool runOnce = false) | |
| { | ||
| if (runOnce) | ||
| { | ||
| Trace.Info($"Uploading {filesToUpload.Count} summary files in one shot through results service."); | ||
| Trace.Info($"Uploading {filesToUpload.Count} file(s) in one shot through results service."); | ||
| } | ||
|
|
||
| int errorCount = 0; | ||
| foreach (var file in filesToUpload) | ||
| { | ||
| try | ||
| { | ||
| await UploadSummaryFile(file); | ||
| if (String.Equals(file.Type, ChecksAttachmentType.StepSummary, StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| await UploadSummaryFile(file); | ||
| } | ||
| else if (String.Equals(file.Type, CoreAttachmentType.ResultsLog, StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| Trace.Info($"Got a step log file to send to results service."); | ||
| await UploadResultsStepLogFile(file); | ||
| } | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| var issue = new Issue() { Type = IssueType.Warning, Message = $"Caught exception during summary file upload to results. {ex.Message}" }; | ||
| var issue = new Issue() { Type = IssueType.Warning, Message = $"Caught exception during file upload to results. {ex.Message}" }; | ||
| issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.ResultsUploadFailure; | ||
|
|
||
| var telemetryRecord = new TimelineRecord() | ||
|
|
@@ -481,16 +519,13 @@ private async Task ProcessSummaryUploadQueueAsync(bool runOnce = false) | |
| telemetryRecord.Issues.Add(issue); | ||
| QueueTimelineRecordUpdate(_jobTimelineId, telemetryRecord); | ||
|
|
||
| Trace.Info("Catch exception during summary file upload to results, keep going since the process is best effort."); | ||
| Trace.Info("Catch exception during file upload to results, keep going since the process is best effort."); | ||
| Trace.Error(ex); | ||
| } | ||
| finally | ||
| { | ||
| errorCount++; | ||
| } | ||
| } | ||
|
|
||
| Trace.Info("Tried to upload {0} summary files to results, success rate: {1}/{0}.", filesToUpload.Count, filesToUpload.Count - errorCount); | ||
| Trace.Info("Tried to upload {0} file(s) to results, success rate: {1}/{0}.", filesToUpload.Count, filesToUpload.Count - errorCount); | ||
| } | ||
|
|
||
| if (runOnce) | ||
|
|
@@ -499,7 +534,7 @@ private async Task ProcessSummaryUploadQueueAsync(bool runOnce = false) | |
| } | ||
| else | ||
| { | ||
| await Task.Delay(_delayForSummaryUploadDequeue); | ||
| await Task.Delay(_delayForResultsUploadDequeue); | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -776,15 +811,14 @@ private async Task UploadFile(UploadFileInfo file) | |
| } | ||
| } | ||
|
|
||
| private async Task UploadSummaryFile(SummaryUploadFileInfo file) | ||
| private async Task UploadSummaryFile(ResultsUploadFileInfo file) | ||
| { | ||
| bool uploadSucceed = false; | ||
| try | ||
| { | ||
| // Upload the step summary | ||
| Trace.Info($"Starting to upload summary file to results service {file.Name}, {file.Path}"); | ||
| var cancellationTokenSource = new CancellationTokenSource(); | ||
| await _jobServer.CreateStepSymmaryAsync(file.PlanId, file.JobId, file.StepId, file.Path, cancellationTokenSource.Token); | ||
| await _jobServer.CreateStepSummaryAsync(file.PlanId, file.JobId, file.RecordId, file.Path, CancellationToken.None); | ||
|
|
||
| uploadSucceed = true; | ||
| } | ||
|
|
@@ -804,6 +838,33 @@ private async Task UploadSummaryFile(SummaryUploadFileInfo file) | |
| } | ||
| } | ||
| } | ||
|
|
||
| private async Task UploadResultsStepLogFile(ResultsUploadFileInfo file) | ||
| { | ||
| bool uploadSucceed = false; | ||
| try | ||
| { | ||
| Trace.Info($"Starting upload of step log file to results service {file.Name}, {file.Path}"); | ||
| await _jobServer.CreateResultsStepLogAsync(file.PlanId, file.JobId, file.RecordId, file.Path, file.Finalize, file.FirstBlock, file.TotalLines, CancellationToken.None); | ||
|
|
||
| uploadSucceed = true; | ||
| } | ||
| finally | ||
| { | ||
| if (uploadSucceed && file.DeleteSource) | ||
| { | ||
| try | ||
| { | ||
| File.Delete(file.Path); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| Trace.Info("Exception encountered during deletion of a temporary file that was already successfully uploaded to results."); | ||
| Trace.Error(ex); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| internal class PendingTimelineRecord | ||
|
|
@@ -822,14 +883,18 @@ internal class UploadFileInfo | |
| public bool DeleteSource { get; set; } | ||
| } | ||
|
|
||
| internal class SummaryUploadFileInfo | ||
| internal class ResultsUploadFileInfo | ||
| { | ||
| public string Name { get; set; } | ||
| public string Type { get; set; } | ||
| public string Path { get; set; } | ||
| public string PlanId { get; set; } | ||
| public string JobId { get; set; } | ||
| public string StepId { get; set; } | ||
| public Guid RecordId { get; set; } | ||
| public bool DeleteSource { get; set; } | ||
| public bool Finalize { get; set; } | ||
| public bool FirstBlock { get; set; } | ||
| public long TotalLines { get; set; } | ||
| } | ||
|
|
||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.