public async Task <TaskResult> RunAsync(AgentJobRequestMessage message, CancellationToken jobRequestCancellationToken) { // Validate parameters. Trace.Entering(); ArgUtil.NotNull(message, nameof(message)); ArgUtil.NotNull(message.Environment, nameof(message.Environment)); ArgUtil.NotNull(message.Environment.Variables, nameof(message.Environment.Variables)); ArgUtil.NotNull(message.Tasks, nameof(message.Tasks)); Trace.Info("Job ID {0}", message.JobId); // System.AccessToken if (message.Environment.Variables.ContainsKey(Constants.Variables.System.EnableAccessToken) && StringUtil.ConvertToBoolean(message.Environment.Variables[Constants.Variables.System.EnableAccessToken])) { // TODO: get access token use Util Method message.Environment.Variables[Constants.Variables.System.AccessToken] = message.Environment.SystemConnection.Authorization.Parameters["AccessToken"]; } // Make sure SystemConnection Url and Endpoint Url match Config Url base ReplaceConfigUriBaseInJobRequestMessage(message); // Setup the job server and job server queue. var jobServer = HostContext.GetService <IJobServer>(); VssCredentials jobServerCredential = ApiUtil.GetVssCredential(message.Environment.SystemConnection); Uri jobServerUrl = message.Environment.SystemConnection.Url; Trace.Info($"Creating job server with URL: {jobServerUrl}"); // jobServerQueue is the throttling reporter. _jobServerQueue = HostContext.GetService <IJobServerQueue>(); VssConnection jobConnection = ApiUtil.CreateConnection(jobServerUrl, jobServerCredential, new DelegatingHandler[] { new ThrottlingReportHandler(_jobServerQueue) }); await jobServer.ConnectAsync(jobConnection); _jobServerQueue.Start(message); IExecutionContext jobContext = null; try { // Create the job execution context. jobContext = HostContext.CreateService <IExecutionContext>(); jobContext.InitializeJob(message, jobRequestCancellationToken); Trace.Info("Starting the job execution context."); jobContext.Start(); jobContext.Section(StringUtil.Loc("StepStarting", message.JobName)); // Set agent version variable. jobContext.Variables.Set(Constants.Variables.Agent.Version, Constants.Agent.Version); jobContext.Output(StringUtil.Loc("AgentVersion", Constants.Agent.Version)); // Print proxy setting information for better diagnostic experience var agentWebProxy = HostContext.GetService <IVstsAgentWebProxy>(); if (!string.IsNullOrEmpty(agentWebProxy.ProxyAddress)) { jobContext.Output(StringUtil.Loc("AgentRunningBehindProxy", agentWebProxy.ProxyAddress)); } // Validate directory permissions. string workDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); Trace.Info($"Validating directory permissions for: '{workDirectory}'"); try { Directory.CreateDirectory(workDirectory); IOUtil.ValidateExecutePermission(workDirectory); } catch (Exception ex) { Trace.Error(ex); jobContext.Error(ex); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } // Set agent variables. AgentSettings settings = HostContext.GetService <IConfigurationStore>().GetSettings(); jobContext.Variables.Set(Constants.Variables.Agent.Id, settings.AgentId.ToString(CultureInfo.InvariantCulture)); jobContext.Variables.Set(Constants.Variables.Agent.HomeDirectory, IOUtil.GetRootPath()); jobContext.Variables.Set(Constants.Variables.Agent.JobName, message.JobName); jobContext.Variables.Set(Constants.Variables.Agent.MachineName, Environment.MachineName); jobContext.Variables.Set(Constants.Variables.Agent.Name, settings.AgentName); jobContext.Variables.Set(Constants.Variables.Agent.RootDirectory, IOUtil.GetWorkPath(HostContext)); #if OS_WINDOWS jobContext.Variables.Set(Constants.Variables.Agent.ServerOMDirectory, Path.Combine(IOUtil.GetExternalsPath(), Constants.Path.ServerOMDirectory)); #endif jobContext.Variables.Set(Constants.Variables.Agent.WorkFolder, IOUtil.GetWorkPath(HostContext)); jobContext.Variables.Set(Constants.Variables.System.WorkFolder, IOUtil.GetWorkPath(HostContext)); if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AGENT_TOOLSDIRECTORY"))) { string toolsDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), Constants.Path.ToolDirectory); Directory.CreateDirectory(toolsDirectory); jobContext.Variables.Set(Constants.Variables.Agent.ToolsDirectory, toolsDirectory); } // Setup TEMP directories _tempDirectoryManager = HostContext.GetService <ITempDirectoryManager>(); _tempDirectoryManager.InitializeTempDirectory(jobContext); // todo: task server can throw. try/catch and fail job gracefully. // prefer task definitions url, then TFS collection url, then TFS account url var taskServer = HostContext.GetService <ITaskServer>(); Uri taskServerUri = null; if (!string.IsNullOrEmpty(jobContext.Variables.System_TaskDefinitionsUri)) { taskServerUri = new Uri(jobContext.Variables.System_TaskDefinitionsUri); } else if (!string.IsNullOrEmpty(jobContext.Variables.System_TFCollectionUrl)) { taskServerUri = new Uri(jobContext.Variables.System_TFCollectionUrl); } var taskServerCredential = ApiUtil.GetVssCredential(message.Environment.SystemConnection); if (taskServerUri != null) { Trace.Info($"Creating task server with {taskServerUri}"); await taskServer.ConnectAsync(ApiUtil.CreateConnection(taskServerUri, taskServerCredential)); } if (taskServerUri == null || !await taskServer.TaskDefinitionEndpointExist(jobRequestCancellationToken)) { Trace.Info($"Can't determine task download url from JobMessage or the endpoint doesn't exist."); var configStore = HostContext.GetService <IConfigurationStore>(); taskServerUri = new Uri(configStore.GetSettings().ServerUrl); Trace.Info($"Recreate task server with configuration server url: {taskServerUri}"); await taskServer.ConnectAsync(ApiUtil.CreateConnection(taskServerUri, taskServerCredential)); } // Expand the endpoint data values. foreach (ServiceEndpoint endpoint in jobContext.Endpoints) { jobContext.Variables.ExpandValues(target: endpoint.Data); VarUtil.ExpandEnvironmentVariables(HostContext, target: endpoint.Data); } // Get the job extension. Trace.Info("Getting job extension."); var hostType = jobContext.Variables.System_HostType; var extensionManager = HostContext.GetService <IExtensionManager>(); // We should always have one job extension IJobExtension jobExtension = (extensionManager.GetExtensions <IJobExtension>() ?? new List <IJobExtension>()) .Where(x => x.HostType.HasFlag(hostType)) .FirstOrDefault(); ArgUtil.NotNull(jobExtension, nameof(jobExtension)); List <IStep> preJobSteps = new List <IStep>(); List <IStep> jobSteps = new List <IStep>(); List <IStep> postJobSteps = new List <IStep>(); try { Trace.Info("Initialize job. Getting all job steps."); var initalizeResult = await jobExtension.InitializeJob(jobContext, message); preJobSteps = initalizeResult.PreJobSteps; jobSteps = initalizeResult.JobSteps; postJobSteps = initalizeResult.PostJobStep; } catch (OperationCanceledException ex) when(jobContext.CancellationToken.IsCancellationRequested) { // set the job to canceled // don't log error issue to job ExecutionContext, since server owns the job level issue Trace.Error($"Job is canclled during initialize."); Trace.Error($"Caught exception: {ex}"); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Canceled)); } catch (Exception ex) { // set the job to failed. // don't log error issue to job ExecutionContext, since server owns the job level issue Trace.Error($"Job initialize failed."); Trace.Error($"Caught exception from {nameof(jobExtension.InitializeJob)}: {ex}"); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } // trace out all steps Trace.Info($"Total pre-job steps: {preJobSteps.Count}."); Trace.Verbose($"Pre-job steps: '{string.Join(", ", preJobSteps.Select(x => x.DisplayName))}'"); Trace.Info($"Total job steps: {jobSteps.Count}."); Trace.Verbose($"Job steps: '{string.Join(", ", jobSteps.Select(x => x.DisplayName))}'"); Trace.Info($"Total post-job steps: {postJobSteps.Count}."); Trace.Verbose($"Post-job steps: '{string.Join(", ", postJobSteps.Select(x => x.DisplayName))}'"); // Run all pre job steps // All pre job steps are critical to the job // Stop exection on any step failure or cancelled Trace.Info("Run all pre-job steps."); var stepsRunner = HostContext.GetService <IStepsRunner>(); try { await stepsRunner.RunAsync(jobContext, preJobSteps, JobRunStage.PreJob); } catch (Exception ex) { // StepRunner should never throw exception out. // End up here mean there is a bug in StepRunner // Log the error and fail the job. Trace.Error($"Caught exception from pre-job steps {nameof(StepsRunner)}: {ex}"); jobContext.Error(ex); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } Trace.Info($"Job result after all pre-job steps finish: {jobContext.Result}"); // Base on the Job result after all pre-job steps finish. // Run all job steps only if the job result is still Succeeded or SucceededWithIssues if (jobContext.Result == null || jobContext.Result == TaskResult.Succeeded || jobContext.Result == TaskResult.SucceededWithIssues) { Trace.Info("Run all job steps."); try { await stepsRunner.RunAsync(jobContext, jobSteps, JobRunStage.Main); } catch (Exception ex) { // StepRunner should never throw exception out. // End up here mean there is a bug in StepRunner // Log the error and fail the job. Trace.Error($"Caught exception from job steps {nameof(StepsRunner)}: {ex}"); jobContext.Error(ex); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } } else { Trace.Info("Skip all job steps due to pre-job step failure."); foreach (var step in jobSteps) { step.ExecutionContext.Start(); step.ExecutionContext.Complete(TaskResult.Skipped); } } Trace.Info($"Job result after all job steps finish: {jobContext.Result}"); // Always run all post job steps // step might not run base on it's own condition. Trace.Info("Run all post-job steps."); try { await stepsRunner.RunAsync(jobContext, postJobSteps, JobRunStage.PostJob); } catch (Exception ex) { // StepRunner should never throw exception out. // End up here mean there is a bug in StepRunner // Log the error and fail the job. Trace.Error($"Caught exception from post-job steps {nameof(StepsRunner)}: {ex}"); jobContext.Error(ex); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } Trace.Info($"Job result after all post-job steps finish: {jobContext.Result}"); // Complete the job. Trace.Info("Completing the job execution context."); return(await CompleteJobAsync(jobServer, jobContext, message)); } finally { await ShutdownQueue(throwOnFailure : false); } }
public async Task <TaskResult> RunAsync(Pipelines.AgentJobRequestMessage message, CancellationToken jobRequestCancellationToken) { // Validate parameters. Trace.Entering(); ArgUtil.NotNull(message, nameof(message)); ArgUtil.NotNull(message.Resources, nameof(message.Resources)); ArgUtil.NotNull(message.Variables, nameof(message.Variables)); ArgUtil.NotNull(message.Steps, nameof(message.Steps)); Trace.Info("Job ID {0}", message.JobId); DateTime jobStartTimeUtc = DateTime.UtcNow; // Agent.RunMode RunMode runMode; if (message.Variables.ContainsKey(Constants.Variables.Agent.RunMode) && Enum.TryParse(message.Variables[Constants.Variables.Agent.RunMode].Value, ignoreCase: true, result: out runMode) && runMode == RunMode.Local) { HostContext.RunMode = runMode; } ServiceEndpoint systemConnection = message.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase)); // System.AccessToken if (message.Variables.ContainsKey(Constants.Variables.System.EnableAccessToken) && StringUtil.ConvertToBoolean(message.Variables[Constants.Variables.System.EnableAccessToken].Value)) { message.Variables[Constants.Variables.System.AccessToken] = new VariableValue(systemConnection.Authorization.Parameters["AccessToken"], false); } // back compat TfsServerUrl message.Variables[Constants.Variables.System.TFServerUrl] = systemConnection.Url.AbsoluteUri; // Make sure SystemConnection Url and Endpoint Url match Config Url base for OnPremises server if (!message.Variables.ContainsKey(Constants.Variables.System.ServerType)) { if (!UrlUtil.IsHosted(systemConnection.Url.AbsoluteUri)) // TODO: remove this after TFS/RM move to M133 { ReplaceConfigUriBaseInJobRequestMessage(message); } } else if (string.Equals(message.Variables[Constants.Variables.System.ServerType]?.Value, "OnPremises", StringComparison.OrdinalIgnoreCase)) { ReplaceConfigUriBaseInJobRequestMessage(message); } // Setup the job server and job server queue. var jobServer = HostContext.GetService <IJobServer>(); VssCredentials jobServerCredential = ApiUtil.GetVssCredential(systemConnection); Uri jobServerUrl = systemConnection.Url; Trace.Info($"Creating job server with URL: {jobServerUrl}"); // jobServerQueue is the throttling reporter. _jobServerQueue = HostContext.GetService <IJobServerQueue>(); VssConnection jobConnection = ApiUtil.CreateConnection(jobServerUrl, jobServerCredential, new DelegatingHandler[] { new ThrottlingReportHandler(_jobServerQueue) }); await jobServer.ConnectAsync(jobConnection); _jobServerQueue.Start(message); IExecutionContext jobContext = null; CancellationTokenRegistration?agentShutdownRegistration = null; try { // Create the job execution context. jobContext = HostContext.CreateService <IExecutionContext>(); jobContext.InitializeJob(message, jobRequestCancellationToken); Trace.Info("Starting the job execution context."); jobContext.Start(); jobContext.Section(StringUtil.Loc("StepStarting", message.JobDisplayName)); agentShutdownRegistration = HostContext.AgentShutdownToken.Register(() => { // log an issue, then agent get shutdown by Ctrl-C or Ctrl-Break. // the server will use Ctrl-Break to tells the agent that operating system is shutting down. string errorMessage; switch (HostContext.AgentShutdownReason) { case ShutdownReason.UserCancelled: errorMessage = StringUtil.Loc("UserShutdownAgent"); break; case ShutdownReason.OperatingSystemShutdown: errorMessage = StringUtil.Loc("OperatingSystemShutdown", Environment.MachineName); break; default: throw new ArgumentException(HostContext.AgentShutdownReason.ToString(), nameof(HostContext.AgentShutdownReason)); } jobContext.AddIssue(new Issue() { Type = IssueType.Error, Message = errorMessage }); }); // Set agent version variable. jobContext.Variables.Set(Constants.Variables.Agent.Version, Constants.Agent.Version); jobContext.Output(StringUtil.Loc("AgentVersion", Constants.Agent.Version)); // Print proxy setting information for better diagnostic experience var agentWebProxy = HostContext.GetService <IVstsAgentWebProxy>(); if (!string.IsNullOrEmpty(agentWebProxy.ProxyAddress)) { jobContext.Output(StringUtil.Loc("AgentRunningBehindProxy", agentWebProxy.ProxyAddress)); } // Validate directory permissions. string workDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); Trace.Info($"Validating directory permissions for: '{workDirectory}'"); try { Directory.CreateDirectory(workDirectory); IOUtil.ValidateExecutePermission(workDirectory); } catch (Exception ex) { Trace.Error(ex); jobContext.Error(ex); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } // Set agent variables. AgentSettings settings = HostContext.GetService <IConfigurationStore>().GetSettings(); jobContext.Variables.Set(Constants.Variables.Agent.Id, settings.AgentId.ToString(CultureInfo.InvariantCulture)); jobContext.Variables.Set(Constants.Variables.Agent.HomeDirectory, HostContext.GetDirectory(WellKnownDirectory.Root)); jobContext.Variables.Set(Constants.Variables.Agent.JobName, message.JobDisplayName); jobContext.Variables.Set(Constants.Variables.Agent.MachineName, Environment.MachineName); jobContext.Variables.Set(Constants.Variables.Agent.Name, settings.AgentName); jobContext.Variables.Set(Constants.Variables.Agent.OS, VarUtil.OS); jobContext.Variables.Set(Constants.Variables.Agent.RootDirectory, IOUtil.GetWorkPath(HostContext)); #if OS_WINDOWS jobContext.Variables.Set(Constants.Variables.Agent.ServerOMDirectory, Path.Combine(IOUtil.GetExternalsPath(), Constants.Path.ServerOMDirectory)); #endif jobContext.Variables.Set(Constants.Variables.Agent.WorkFolder, IOUtil.GetWorkPath(HostContext)); jobContext.Variables.Set(Constants.Variables.System.WorkFolder, IOUtil.GetWorkPath(HostContext)); string toolsDirectory = Environment.GetEnvironmentVariable("AGENT_TOOLSDIRECTORY") ?? Environment.GetEnvironmentVariable(Constants.Variables.Agent.ToolsDirectory); if (string.IsNullOrEmpty(toolsDirectory)) { toolsDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), Constants.Path.ToolDirectory); Directory.CreateDirectory(toolsDirectory); } else { Trace.Info($"Set tool cache directory base on environment: '{toolsDirectory}'"); Directory.CreateDirectory(toolsDirectory); } jobContext.Variables.Set(Constants.Variables.Agent.ToolsDirectory, toolsDirectory); // Setup TEMP directories _tempDirectoryManager = HostContext.GetService <ITempDirectoryManager>(); _tempDirectoryManager.InitializeTempDirectory(jobContext); // todo: task server can throw. try/catch and fail job gracefully. // prefer task definitions url, then TFS collection url, then TFS account url var taskServer = HostContext.GetService <ITaskServer>(); Uri taskServerUri = null; if (!string.IsNullOrEmpty(jobContext.Variables.System_TaskDefinitionsUri)) { taskServerUri = new Uri(jobContext.Variables.System_TaskDefinitionsUri); } else if (!string.IsNullOrEmpty(jobContext.Variables.System_TFCollectionUrl)) { taskServerUri = new Uri(jobContext.Variables.System_TFCollectionUrl); } var taskServerCredential = ApiUtil.GetVssCredential(systemConnection); if (taskServerUri != null) { Trace.Info($"Creating task server with {taskServerUri}"); await taskServer.ConnectAsync(ApiUtil.CreateConnection(taskServerUri, taskServerCredential)); } if (taskServerUri == null || !await taskServer.TaskDefinitionEndpointExist()) { Trace.Info($"Can't determine task download url from JobMessage or the endpoint doesn't exist."); var configStore = HostContext.GetService <IConfigurationStore>(); taskServerUri = new Uri(configStore.GetSettings().ServerUrl); Trace.Info($"Recreate task server with configuration server url: {taskServerUri}"); await taskServer.ConnectAsync(ApiUtil.CreateConnection(taskServerUri, taskServerCredential)); } // Expand the endpoint data values. foreach (ServiceEndpoint endpoint in jobContext.Endpoints) { jobContext.Variables.ExpandValues(target: endpoint.Data); VarUtil.ExpandEnvironmentVariables(HostContext, target: endpoint.Data); } // Get the job extension. Trace.Info("Getting job extension."); var hostType = jobContext.Variables.System_HostType; var extensionManager = HostContext.GetService <IExtensionManager>(); // We should always have one job extension IJobExtension jobExtension = (extensionManager.GetExtensions <IJobExtension>() ?? new List <IJobExtension>()) .Where(x => x.HostType.HasFlag(hostType)) .FirstOrDefault(); ArgUtil.NotNull(jobExtension, nameof(jobExtension)); List <IStep> jobSteps = new List <IStep>(); try { Trace.Info("Initialize job. Getting all job steps."); var initializeResult = await jobExtension.InitializeJob(jobContext, message); jobSteps.AddRange(initializeResult.PreJobSteps); jobSteps.AddRange(initializeResult.JobSteps); jobSteps.AddRange(initializeResult.PostJobStep); } catch (OperationCanceledException ex) when(jobContext.CancellationToken.IsCancellationRequested) { // set the job to canceled // don't log error issue to job ExecutionContext, since server owns the job level issue Trace.Error($"Job is canceled during initialize."); Trace.Error($"Caught exception: {ex}"); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Canceled)); } catch (Exception ex) { // set the job to failed. // don't log error issue to job ExecutionContext, since server owns the job level issue Trace.Error($"Job initialize failed."); Trace.Error($"Caught exception from {nameof(jobExtension.InitializeJob)}: {ex}"); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } // trace out all steps Trace.Info($"Total job steps: {jobSteps.Count}."); Trace.Verbose($"Job steps: '{string.Join(", ", jobSteps.Select(x => x.DisplayName))}'"); bool processCleanup = jobContext.Variables.GetBoolean("process.clean") ?? true; HashSet <string> existingProcesses = new HashSet <string>(StringComparer.OrdinalIgnoreCase); string processLookupId = null; if (processCleanup) { processLookupId = $"vsts_{Guid.NewGuid()}"; // Set the VSTS_PROCESS_LOOKUP_ID env variable. jobContext.SetVariable(Constants.ProcessLookupId, processLookupId, false, false); // Take a snapshot of current running processes Dictionary <int, Process> processes = SnapshotProcesses(); foreach (var proc in processes) { // Pid_ProcessName existingProcesses.Add($"{proc.Key}_{proc.Value.ProcessName}"); } } // Run all job steps Trace.Info("Run all job steps."); var stepsRunner = HostContext.GetService <IStepsRunner>(); try { await stepsRunner.RunAsync(jobContext, jobSteps); } catch (Exception ex) { // StepRunner should never throw exception out. // End up here mean there is a bug in StepRunner // Log the error and fail the job. Trace.Error($"Caught exception from job steps {nameof(StepsRunner)}: {ex}"); jobContext.Error(ex); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } finally { if (processCleanup) { // Only check environment variable for any process that doesn't run before we invoke our process. Dictionary <int, Process> currentProcesses = SnapshotProcesses(); foreach (var proc in currentProcesses) { if (existingProcesses.Contains($"{proc.Key}_{proc.Value.ProcessName}")) { Trace.Verbose($"Skip existing process. PID: {proc.Key} ({proc.Value.ProcessName})"); } else { Trace.Info($"Inspecting process environment variables. PID: {proc.Key} ({proc.Value.ProcessName})"); Dictionary <string, string> env = new Dictionary <string, string>(); try { env = proc.Value.GetEnvironmentVariables(); foreach (var e in env) { Trace.Verbose($"PID:{proc.Key} ({e.Key}={e.Value})"); } } catch (Exception ex) { Trace.Verbose("Ignore any exception during read process environment variables."); Trace.Verbose(ex.ToString()); } if (env.TryGetValue(Constants.ProcessLookupId, out string lookupId) && lookupId.Equals(processLookupId, StringComparison.OrdinalIgnoreCase)) { Trace.Info($"Terminate orphan process: pid ({proc.Key}) ({proc.Value.ProcessName})"); try { proc.Value.Kill(); } catch (Exception ex) { Trace.Error("Catch exception during orphan process cleanup."); Trace.Error(ex); } } } } } } Trace.Info($"Job result after all job steps finish: {jobContext.Result ?? TaskResult.Succeeded}"); if (jobContext.Variables.GetBoolean(Constants.Variables.Agent.Diagnostic) ?? false) { Trace.Info("Support log upload starting."); IDiagnosticLogManager diagnosticLogManager = HostContext.GetService <IDiagnosticLogManager>(); try { await diagnosticLogManager.UploadDiagnosticLogsAsync(executionContext : jobContext, message : message, jobStartTimeUtc : jobStartTimeUtc); Trace.Info("Support log upload complete."); } catch (Exception ex) { // Log the error but make sure we continue gracefully. Trace.Info("Error uploading support logs."); Trace.Error(ex); } } Trace.Info("Completing the job execution context."); return(await CompleteJobAsync(jobServer, jobContext, message)); } finally { if (agentShutdownRegistration != null) { agentShutdownRegistration.Value.Dispose(); agentShutdownRegistration = null; } await ShutdownQueue(throwOnFailure : false); } }
public async Task <TaskResult> RunAsync(Pipelines.AgentJobRequestMessage message, CancellationToken jobRequestCancellationToken) { // Validate parameters. Trace.Entering(); ArgUtil.NotNull(message, nameof(message)); ArgUtil.NotNull(message.Resources, nameof(message.Resources)); ArgUtil.NotNull(message.Variables, nameof(message.Variables)); ArgUtil.NotNull(message.Steps, nameof(message.Steps)); Trace.Info("Job ID {0}", message.JobId); DateTime jobStartTimeUtc = DateTime.UtcNow; // Agent.RunMode RunMode runMode; if (message.Variables.ContainsKey(Constants.Variables.Agent.RunMode) && Enum.TryParse(message.Variables[Constants.Variables.Agent.RunMode].Value, ignoreCase: true, result: out runMode) && runMode == RunMode.Local) { HostContext.RunMode = runMode; } ServiceEndpoint systemConnection = message.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase)); // System.AccessToken if (message.Variables.ContainsKey(Constants.Variables.System.EnableAccessToken) && StringUtil.ConvertToBoolean(message.Variables[Constants.Variables.System.EnableAccessToken].Value)) { message.Variables[Constants.Variables.System.AccessToken] = new VariableValue(systemConnection.Authorization.Parameters["AccessToken"], false); } // back compat TfsServerUrl message.Variables[Constants.Variables.System.TFServerUrl] = systemConnection.Url.AbsoluteUri; // Make sure SystemConnection Url and Endpoint Url match Config Url base for OnPremises server // System.ServerType will always be there after M133 if (!message.Variables.ContainsKey(Constants.Variables.System.ServerType) || string.Equals(message.Variables[Constants.Variables.System.ServerType]?.Value, "OnPremises", StringComparison.OrdinalIgnoreCase)) { ReplaceConfigUriBaseInJobRequestMessage(message); } // Setup the job server and job server queue. var jobServer = HostContext.GetService <IJobServer>(); VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection); Uri jobServerUrl = systemConnection.Url; Trace.Info($"Creating job server with URL: {jobServerUrl}"); // jobServerQueue is the throttling reporter. _jobServerQueue = HostContext.GetService <IJobServerQueue>(); VssConnection jobConnection = VssUtil.CreateConnection(jobServerUrl, jobServerCredential, new DelegatingHandler[] { new ThrottlingReportHandler(_jobServerQueue) }); await jobServer.ConnectAsync(jobConnection); _jobServerQueue.Start(message); HostContext.WritePerfCounter($"WorkerJobServerQueueStarted_{message.RequestId.ToString()}"); IExecutionContext jobContext = null; CancellationTokenRegistration?agentShutdownRegistration = null; try { // Create the job execution context. jobContext = HostContext.CreateService <IExecutionContext>(); jobContext.InitializeJob(message, jobRequestCancellationToken); Trace.Info("Starting the job execution context."); jobContext.Start(); jobContext.Section(StringUtil.Loc("StepStarting", message.JobDisplayName)); agentShutdownRegistration = HostContext.AgentShutdownToken.Register(() => { // log an issue, then agent get shutdown by Ctrl-C or Ctrl-Break. // the server will use Ctrl-Break to tells the agent that operating system is shutting down. string errorMessage; switch (HostContext.AgentShutdownReason) { case ShutdownReason.UserCancelled: errorMessage = StringUtil.Loc("UserShutdownAgent"); break; case ShutdownReason.OperatingSystemShutdown: errorMessage = StringUtil.Loc("OperatingSystemShutdown", Environment.MachineName); break; default: throw new ArgumentException(HostContext.AgentShutdownReason.ToString(), nameof(HostContext.AgentShutdownReason)); } jobContext.AddIssue(new Issue() { Type = IssueType.Error, Message = errorMessage }); }); // Validate directory permissions. string workDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); Trace.Info($"Validating directory permissions for: '{workDirectory}'"); try { Directory.CreateDirectory(workDirectory); IOUtil.ValidateExecutePermission(workDirectory); } catch (Exception ex) { Trace.Error(ex); jobContext.Error(ex); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } // Set agent variables. AgentSettings settings = HostContext.GetService <IConfigurationStore>().GetSettings(); jobContext.Variables.Set(Constants.Variables.Agent.Id, settings.AgentId.ToString(CultureInfo.InvariantCulture)); jobContext.SetVariable(Constants.Variables.Agent.HomeDirectory, HostContext.GetDirectory(WellKnownDirectory.Root), isFilePath: true); jobContext.Variables.Set(Constants.Variables.Agent.JobName, message.JobDisplayName); jobContext.Variables.Set(Constants.Variables.Agent.MachineName, Environment.MachineName); jobContext.Variables.Set(Constants.Variables.Agent.Name, settings.AgentName); jobContext.Variables.Set(Constants.Variables.Agent.OS, VarUtil.OS); jobContext.Variables.Set(Constants.Variables.Agent.OSArchitecture, VarUtil.OSArchitecture); jobContext.SetVariable(Constants.Variables.Agent.RootDirectory, HostContext.GetDirectory(WellKnownDirectory.Work), isFilePath: true); if (PlatformUtil.RunningOnWindows) { jobContext.SetVariable(Constants.Variables.Agent.ServerOMDirectory, HostContext.GetDirectory(WellKnownDirectory.ServerOM), isFilePath: true); } if (!PlatformUtil.RunningOnWindows) { jobContext.Variables.Set(Constants.Variables.Agent.AcceptTeeEula, settings.AcceptTeeEula.ToString()); } jobContext.SetVariable(Constants.Variables.Agent.WorkFolder, HostContext.GetDirectory(WellKnownDirectory.Work), isFilePath: true); jobContext.SetVariable(Constants.Variables.System.WorkFolder, HostContext.GetDirectory(WellKnownDirectory.Work), isFilePath: true); string toolsDirectory = HostContext.GetDirectory(WellKnownDirectory.Tools); Directory.CreateDirectory(toolsDirectory); jobContext.SetVariable(Constants.Variables.Agent.ToolsDirectory, toolsDirectory, isFilePath: true); bool disableGitPrompt = jobContext.Variables.GetBoolean("VSTS_DISABLE_GIT_PROMPT") ?? StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("VSTS_DISABLE_GIT_PROMPT"), true); if (disableGitPrompt && string.IsNullOrEmpty(jobContext.Variables.Get("GIT_TERMINAL_PROMPT"))) { jobContext.SetVariable("GIT_TERMINAL_PROMPT", "0"); } // Setup TEMP directories _tempDirectoryManager = HostContext.GetService <ITempDirectoryManager>(); _tempDirectoryManager.InitializeTempDirectory(jobContext); // todo: task server can throw. try/catch and fail job gracefully. // prefer task definitions url, then TFS collection url, then TFS account url var taskServer = HostContext.GetService <ITaskServer>(); Uri taskServerUri = null; if (!string.IsNullOrEmpty(jobContext.Variables.System_TaskDefinitionsUri)) { taskServerUri = new Uri(jobContext.Variables.System_TaskDefinitionsUri); } else if (!string.IsNullOrEmpty(jobContext.Variables.System_TFCollectionUrl)) { taskServerUri = new Uri(jobContext.Variables.System_TFCollectionUrl); } var taskServerCredential = VssUtil.GetVssCredential(systemConnection); if (taskServerUri != null) { Trace.Info($"Creating task server with {taskServerUri}"); await taskServer.ConnectAsync(VssUtil.CreateConnection(taskServerUri, taskServerCredential)); } // for back compat TFS 2015 RTM/QU1, we may need to switch the task server url to agent config url if (!string.Equals(message.Variables.GetValueOrDefault(Constants.Variables.System.ServerType)?.Value, "Hosted", StringComparison.OrdinalIgnoreCase)) { if (taskServerUri == null || !await taskServer.TaskDefinitionEndpointExist()) { Trace.Info($"Can't determine task download url from JobMessage or the endpoint doesn't exist."); var configStore = HostContext.GetService <IConfigurationStore>(); taskServerUri = new Uri(configStore.GetSettings().ServerUrl); Trace.Info($"Recreate task server with configuration server url: {taskServerUri}"); await taskServer.ConnectAsync(VssUtil.CreateConnection(taskServerUri, taskServerCredential)); } } // Expand the endpoint data values. foreach (ServiceEndpoint endpoint in jobContext.Endpoints) { jobContext.Variables.ExpandValues(target: endpoint.Data); VarUtil.ExpandEnvironmentVariables(HostContext, target: endpoint.Data); } // Expand the repository property values. foreach (var repository in jobContext.Repositories) { // expand checkout option var checkoutOptions = repository.Properties.Get <JToken>(Pipelines.RepositoryPropertyNames.CheckoutOptions); if (checkoutOptions != null) { checkoutOptions = jobContext.Variables.ExpandValues(target: checkoutOptions); checkoutOptions = VarUtil.ExpandEnvironmentVariables(HostContext, target: checkoutOptions); repository.Properties.Set <JToken>(Pipelines.RepositoryPropertyNames.CheckoutOptions, checkoutOptions);; } // expand workspace mapping var mappings = repository.Properties.Get <JToken>(Pipelines.RepositoryPropertyNames.Mappings); if (mappings != null) { mappings = jobContext.Variables.ExpandValues(target: mappings); mappings = VarUtil.ExpandEnvironmentVariables(HostContext, target: mappings); repository.Properties.Set <JToken>(Pipelines.RepositoryPropertyNames.Mappings, mappings); } } // Expand container properties if (jobContext.Container != null) { this.ExpandProperties(jobContext.Container, jobContext.Variables); } foreach (var sidecar in jobContext.SidecarContainers) { this.ExpandProperties(sidecar, jobContext.Variables); } // Get the job extension. Trace.Info("Getting job extension."); var hostType = jobContext.Variables.System_HostType; var extensionManager = HostContext.GetService <IExtensionManager>(); // We should always have one job extension IJobExtension jobExtension = (extensionManager.GetExtensions <IJobExtension>() ?? new List <IJobExtension>()) .Where(x => x.HostType.HasFlag(hostType)) .FirstOrDefault(); ArgUtil.NotNull(jobExtension, nameof(jobExtension)); List <IStep> jobSteps = null; try { Trace.Info("Initialize job. Getting all job steps."); jobSteps = await jobExtension.InitializeJob(jobContext, message); } catch (OperationCanceledException ex) when(jobContext.CancellationToken.IsCancellationRequested) { // set the job to canceled // don't log error issue to job ExecutionContext, since server owns the job level issue Trace.Error($"Job is canceled during initialize."); Trace.Error($"Caught exception: {ex}"); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Canceled)); } catch (Exception ex) { // set the job to failed. // don't log error issue to job ExecutionContext, since server owns the job level issue Trace.Error($"Job initialize failed."); Trace.Error($"Caught exception from {nameof(jobExtension.InitializeJob)}: {ex}"); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } // trace out all steps Trace.Info($"Total job steps: {jobSteps.Count}."); Trace.Verbose($"Job steps: '{string.Join(", ", jobSteps.Select(x => x.DisplayName))}'"); HostContext.WritePerfCounter($"WorkerJobInitialized_{message.RequestId.ToString()}"); // Run all job steps Trace.Info("Run all job steps."); var stepsRunner = HostContext.GetService <IStepsRunner>(); try { await stepsRunner.RunAsync(jobContext, jobSteps); } catch (Exception ex) { // StepRunner should never throw exception out. // End up here mean there is a bug in StepRunner // Log the error and fail the job. Trace.Error($"Caught exception from job steps {nameof(StepsRunner)}: {ex}"); jobContext.Error(ex); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } finally { Trace.Info("Finalize job."); await jobExtension.FinalizeJob(jobContext); } Trace.Info($"Job result after all job steps finish: {jobContext.Result ?? TaskResult.Succeeded}"); if (jobContext.Variables.GetBoolean(Constants.Variables.Agent.Diagnostic) ?? false) { Trace.Info("Support log upload starting."); IDiagnosticLogManager diagnosticLogManager = HostContext.GetService <IDiagnosticLogManager>(); try { await diagnosticLogManager.UploadDiagnosticLogsAsync(executionContext : jobContext, message : message, jobStartTimeUtc : jobStartTimeUtc); Trace.Info("Support log upload complete."); } catch (Exception ex) { // Log the error but make sure we continue gracefully. Trace.Info("Error uploading support logs."); Trace.Error(ex); } } Trace.Info("Completing the job execution context."); return(await CompleteJobAsync(jobServer, jobContext, message)); } finally { if (agentShutdownRegistration != null) { agentShutdownRegistration.Value.Dispose(); agentShutdownRegistration = null; } await ShutdownQueue(throwOnFailure : false); } }
public async Task <TaskResult> RunAsync(Pipelines.AgentJobRequestMessage message, CancellationToken jobRequestCancellationToken) { // Validate parameters. Trace.Entering(); ArgUtil.NotNull(message, nameof(message)); ArgUtil.NotNull(message.Resources, nameof(message.Resources)); ArgUtil.NotNull(message.Variables, nameof(message.Variables)); ArgUtil.NotNull(message.Steps, nameof(message.Steps)); Trace.Info("Job ID {0}", message.JobId); DateTime jobStartTimeUtc = DateTime.UtcNow; ServiceEndpoint systemConnection = message.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase)); // Setup the job server and job server queue. var jobServer = HostContext.GetService <IJobServer>(); VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection); Uri jobServerUrl = systemConnection.Url; Trace.Info($"Creating job server with URL: {jobServerUrl}"); // jobServerQueue is the throttling reporter. _jobServerQueue = HostContext.GetService <IJobServerQueue>(); VssConnection jobConnection = VssUtil.CreateConnection(jobServerUrl, jobServerCredential, new DelegatingHandler[] { new ThrottlingReportHandler(_jobServerQueue) }); await jobServer.ConnectAsync(jobConnection); _jobServerQueue.Start(message); HostContext.WritePerfCounter($"WorkerJobServerQueueStarted_{message.RequestId.ToString()}"); IExecutionContext jobContext = null; CancellationTokenRegistration?runnerShutdownRegistration = null; try { // Create the job execution context. jobContext = HostContext.CreateService <IExecutionContext>(); jobContext.InitializeJob(message, jobRequestCancellationToken); Trace.Info("Starting the job execution context."); jobContext.Start(); jobContext.Debug($"Starting: {message.JobDisplayName}"); runnerShutdownRegistration = HostContext.RunnerShutdownToken.Register(() => { // log an issue, then runner get shutdown by Ctrl-C or Ctrl-Break. // the server will use Ctrl-Break to tells the runner that operating system is shutting down. string errorMessage; switch (HostContext.RunnerShutdownReason) { case ShutdownReason.UserCancelled: errorMessage = "The runner has received a shutdown signal. This can happen when the runner service is stopped, or a manually started runner is canceled."; break; case ShutdownReason.OperatingSystemShutdown: errorMessage = $"Operating system is shutting down for computer '{Environment.MachineName}'"; break; default: throw new ArgumentException(HostContext.RunnerShutdownReason.ToString(), nameof(HostContext.RunnerShutdownReason)); } jobContext.AddIssue(new Issue() { Type = IssueType.Error, Message = errorMessage }); }); // Validate directory permissions. string workDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); Trace.Info($"Validating directory permissions for: '{workDirectory}'"); try { Directory.CreateDirectory(workDirectory); IOUtil.ValidateExecutePermission(workDirectory); } catch (Exception ex) { Trace.Error(ex); jobContext.Error(ex); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } if (jobContext.Global.WriteDebug) { jobContext.SetRunnerContext("debug", "1"); } jobContext.SetRunnerContext("os", VarUtil.OS); string toolsDirectory = HostContext.GetDirectory(WellKnownDirectory.Tools); Directory.CreateDirectory(toolsDirectory); jobContext.SetRunnerContext("tool_cache", toolsDirectory); // Setup TEMP directories _tempDirectoryManager = HostContext.GetService <ITempDirectoryManager>(); _tempDirectoryManager.InitializeTempDirectory(jobContext); // Get the job extension. Trace.Info("Getting job extension."); IJobExtension jobExtension = HostContext.CreateService <IJobExtension>(); List <IStep> jobSteps = null; try { Trace.Info("Initialize job. Getting all job steps."); jobSteps = await jobExtension.InitializeJob(jobContext, message); } catch (OperationCanceledException ex) when(jobContext.CancellationToken.IsCancellationRequested) { // set the job to canceled // don't log error issue to job ExecutionContext, since server owns the job level issue Trace.Error($"Job is canceled during initialize."); Trace.Error($"Caught exception: {ex}"); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Canceled)); } catch (Exception ex) { // set the job to failed. // don't log error issue to job ExecutionContext, since server owns the job level issue Trace.Error($"Job initialize failed."); Trace.Error($"Caught exception from {nameof(jobExtension.InitializeJob)}: {ex}"); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } // trace out all steps Trace.Info($"Total job steps: {jobSteps.Count}."); Trace.Verbose($"Job steps: '{string.Join(", ", jobSteps.Select(x => x.DisplayName))}'"); HostContext.WritePerfCounter($"WorkerJobInitialized_{message.RequestId.ToString()}"); // Run all job steps Trace.Info("Run all job steps."); var stepsRunner = HostContext.GetService <IStepsRunner>(); try { foreach (var step in jobSteps) { jobContext.JobSteps.Enqueue(step); } await stepsRunner.RunAsync(jobContext); } catch (Exception ex) { // StepRunner should never throw exception out. // End up here mean there is a bug in StepRunner // Log the error and fail the job. Trace.Error($"Caught exception from job steps {nameof(StepsRunner)}: {ex}"); jobContext.Error(ex); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } finally { Trace.Info("Finalize job."); jobExtension.FinalizeJob(jobContext, message, jobStartTimeUtc); } Trace.Info($"Job result after all job steps finish: {jobContext.Result ?? TaskResult.Succeeded}"); Trace.Info("Completing the job execution context."); return(await CompleteJobAsync(jobServer, jobContext, message)); } finally { if (runnerShutdownRegistration != null) { runnerShutdownRegistration.Value.Dispose(); runnerShutdownRegistration = null; } await ShutdownQueue(throwOnFailure : false); } }
public async Task <TaskResult> RunAsync(Pipelines.AgentJobRequestMessage message, CancellationToken jobRequestCancellationToken) { // Validate parameters. Trace.Entering(); ArgUtil.NotNull(message, nameof(message)); ArgUtil.NotNull(message.Resources, nameof(message.Resources)); ArgUtil.NotNull(message.Variables, nameof(message.Variables)); ArgUtil.NotNull(message.Steps, nameof(message.Steps)); Trace.Info("Job ID {0}", message.JobId); DateTime jobStartTimeUtc = DateTime.UtcNow; ServiceEndpoint systemConnection = message.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase)); // Setup the job server and job server queue. var jobServer = HostContext.GetService <IJobServer>(); VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection); Uri jobServerUrl = systemConnection.Url; Trace.Info($"Creating job server with URL: {jobServerUrl}"); // jobServerQueue is the throttling reporter. _jobServerQueue = HostContext.GetService <IJobServerQueue>(); VssConnection jobConnection = VssUtil.CreateConnection(jobServerUrl, jobServerCredential, new DelegatingHandler[] { new ThrottlingReportHandler(_jobServerQueue) }); await jobServer.ConnectAsync(jobConnection); _jobServerQueue.Start(message); HostContext.WritePerfCounter($"WorkerJobServerQueueStarted_{message.RequestId.ToString()}"); IExecutionContext jobContext = null; CancellationTokenRegistration?runnerShutdownRegistration = null; try { // Create the job execution context. jobContext = HostContext.CreateService <IExecutionContext>(); jobContext.InitializeJob(message, jobRequestCancellationToken); Trace.Info("Starting the job execution context."); jobContext.Start(); jobContext.Debug($"Starting: {message.JobDisplayName}"); // RUST: If the event type is not allowed exit the job before anything is run. var rustExpectedEvent = System.Environment.GetEnvironmentVariable("RUST_WHITELISTED_EVENT_NAME"); if (rustExpectedEvent != null) { var rustGitHubContext = (Pipelines.ContextData.DictionaryContextData)message.ContextData["github"]; var rustEventName = rustGitHubContext["event_name"].ToString(); if (rustEventName != rustExpectedEvent) { return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Canceled)); } } runnerShutdownRegistration = HostContext.RunnerShutdownToken.Register(() => { // log an issue, then runner get shutdown by Ctrl-C or Ctrl-Break. // the server will use Ctrl-Break to tells the runner that operating system is shutting down. string errorMessage; switch (HostContext.RunnerShutdownReason) { case ShutdownReason.UserCancelled: errorMessage = "The runner has received a shutdown signal. This can happen when the runner service is stopped, or a manually started runner is canceled."; break; case ShutdownReason.OperatingSystemShutdown: errorMessage = $"Operating system is shutting down for computer '{Environment.MachineName}'"; break; default: throw new ArgumentException(HostContext.RunnerShutdownReason.ToString(), nameof(HostContext.RunnerShutdownReason)); } jobContext.AddIssue(new Issue() { Type = IssueType.Error, Message = errorMessage }); }); // Validate directory permissions. string workDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); Trace.Info($"Validating directory permissions for: '{workDirectory}'"); try { Directory.CreateDirectory(workDirectory); IOUtil.ValidateExecutePermission(workDirectory); } catch (Exception ex) { Trace.Error(ex); jobContext.Error(ex); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } if (jobContext.Global.WriteDebug) { jobContext.SetRunnerContext("debug", "1"); } jobContext.SetRunnerContext("os", VarUtil.OS); jobContext.SetRunnerContext("arch", VarUtil.OSArchitecture); var runnerSettings = HostContext.GetService <IConfigurationStore>().GetSettings(); jobContext.SetRunnerContext("name", runnerSettings.AgentName); string toolsDirectory = HostContext.GetDirectory(WellKnownDirectory.Tools); Directory.CreateDirectory(toolsDirectory); jobContext.SetRunnerContext("tool_cache", toolsDirectory); // Setup TEMP directories _tempDirectoryManager = HostContext.GetService <ITempDirectoryManager>(); _tempDirectoryManager.InitializeTempDirectory(jobContext); // Get the job extension. Trace.Info("Getting job extension."); IJobExtension jobExtension = HostContext.CreateService <IJobExtension>(); List <IStep> jobSteps = null; try { Trace.Info("Initialize job. Getting all job steps."); jobSteps = await jobExtension.InitializeJob(jobContext, message); } catch (OperationCanceledException ex) when(jobContext.CancellationToken.IsCancellationRequested) { // set the job to canceled // don't log error issue to job ExecutionContext, since server owns the job level issue Trace.Error($"Job is canceled during initialize."); Trace.Error($"Caught exception: {ex}"); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Canceled)); } catch (Exception ex) { // set the job to failed. // don't log error issue to job ExecutionContext, since server owns the job level issue Trace.Error($"Job initialize failed."); Trace.Error($"Caught exception from {nameof(jobExtension.InitializeJob)}: {ex}"); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } // trace out all steps Trace.Info($"Total job steps: {jobSteps.Count}."); Trace.Verbose($"Job steps: '{string.Join(", ", jobSteps.Select(x => x.DisplayName))}'"); HostContext.WritePerfCounter($"WorkerJobInitialized_{message.RequestId.ToString()}"); if (systemConnection.Data.TryGetValue("GenerateIdTokenUrl", out var generateIdTokenUrl) && !string.IsNullOrEmpty(generateIdTokenUrl)) { // Server won't issue ID_TOKEN for non-inprogress job. // If the job is trying to use OIDC feature, we want the job to be marked as in-progress before running any customer's steps as much as we can. // Timeline record update background process runs every 500ms, so delay 1000ms is enough for most of the cases Trace.Info($"Waiting for job to be marked as started."); await Task.WhenAny(_jobServerQueue.JobRecordUpdated.Task, Task.Delay(1000)); } // Run all job steps Trace.Info("Run all job steps."); var stepsRunner = HostContext.GetService <IStepsRunner>(); try { foreach (var step in jobSteps) { jobContext.JobSteps.Enqueue(step); } await stepsRunner.RunAsync(jobContext); } catch (Exception ex) { // StepRunner should never throw exception out. // End up here mean there is a bug in StepRunner // Log the error and fail the job. Trace.Error($"Caught exception from job steps {nameof(StepsRunner)}: {ex}"); jobContext.Error(ex); return(await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Failed)); } finally { Trace.Info("Finalize job."); jobExtension.FinalizeJob(jobContext, message, jobStartTimeUtc); } Trace.Info($"Job result after all job steps finish: {jobContext.Result ?? TaskResult.Succeeded}"); Trace.Info("Completing the job execution context."); return(await CompleteJobAsync(jobServer, jobContext, message)); } finally { if (runnerShutdownRegistration != null) { runnerShutdownRegistration.Value.Dispose(); runnerShutdownRegistration = null; } await ShutdownQueue(throwOnFailure : false); } }