2023-06-02 21:47:59 +02:00
using System ;
2019-10-10 00:52:42 -04:00
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
2022-01-26 13:23:24 -05:00
using System.Net.Http ;
2023-04-26 15:15:19 -04:00
using System.Net.Http.Headers ;
2021-09-29 15:49:31 -04:00
using System.Text ;
2019-10-10 00:52:42 -04:00
using System.Threading ;
using System.Threading.Tasks ;
2023-06-16 00:13:08 -04:00
using GitHub.DistributedTask.ObjectTemplating.Tokens ;
2023-02-27 11:50:28 -05:00
using GitHub.DistributedTask.Pipelines ;
2022-01-26 13:23:24 -05:00
using GitHub.DistributedTask.WebApi ;
2019-10-10 00:52:42 -04:00
using GitHub.Runner.Common ;
2022-01-26 13:23:24 -05:00
using GitHub.Runner.Common.Util ;
2019-10-10 00:52:42 -04:00
using GitHub.Runner.Sdk ;
2026-03-23 14:02:15 +00:00
using GitHub.Runner.Worker.Dap ;
2022-01-26 13:23:24 -05:00
using GitHub.Services.Common ;
using GitHub.Services.WebApi ;
2024-11-07 21:00:03 -05:00
using Sdk.RSWebApi.Contracts ;
2022-01-26 13:23:24 -05:00
using Pipelines = GitHub . DistributedTask . Pipelines ;
2019-10-10 00:52:42 -04:00
namespace GitHub.Runner.Worker
{
[ServiceLocator(Default = typeof(JobRunner))]
public interface IJobRunner : IRunnerService
{
2023-02-27 11:50:28 -05:00
Task < TaskResult > RunAsync ( AgentJobRequestMessage message , CancellationToken jobRequestCancellationToken ) ;
2019-10-10 00:52:42 -04:00
}
public sealed class JobRunner : RunnerService , IJobRunner
{
2026-03-23 14:02:15 +00:00
private const string DebuggerConnectionTelemetryPrefix = "DebuggerConnectionResult" ;
2019-10-10 00:52:42 -04:00
private IJobServerQueue _jobServerQueue ;
2022-01-26 13:23:24 -05:00
private RunnerSettings _runnerSettings ;
2019-10-10 00:52:42 -04:00
private ITempDirectoryManager _tempDirectoryManager ;
2023-02-27 11:50:28 -05:00
public async Task < TaskResult > RunAsync ( AgentJobRequestMessage message , CancellationToken jobRequestCancellationToken )
2019-10-10 00:52:42 -04:00
{
// 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 ;
2024-03-23 23:12:39 -04:00
_runnerSettings = HostContext . GetService < IConfigurationStore > ( ) . GetSettings ( ) ;
2023-02-01 16:18:31 -05:00
IRunnerService server = null ;
2019-10-10 00:52:42 -04:00
2023-04-26 15:15:19 -04:00
// add orchestration id to useragent for better correlation.
if ( message . Variables . TryGetValue ( Constants . Variables . System . OrchestrationId , out VariableValue orchestrationId ) & &
! string . IsNullOrEmpty ( orchestrationId . Value ) )
{
2025-07-17 01:12:03 +01:00
if ( ! HostContext . UserAgents . Any ( x = > string . Equals ( x . Product ? . Name , "OrchestrationId" , StringComparison . OrdinalIgnoreCase ) ) )
2025-07-16 14:11:09 -04:00
{
// make the orchestration id the first item in the user-agent header to avoid get truncated in server log.
HostContext . UserAgents . Insert ( 0 , new ProductInfoHeaderValue ( "OrchestrationId" , orchestrationId . Value ) ) ;
}
2024-01-30 13:39:41 -05:00
// make sure orchestration id is in the user-agent header.
VssUtil . InitializeVssClientSettings ( HostContext . UserAgents , HostContext . WebProxy ) ;
2023-04-26 15:15:19 -04:00
}
2019-10-10 00:52:42 -04:00
ServiceEndpoint systemConnection = message . Resources . Endpoints . Single ( x = > string . Equals ( x . Name , WellKnownServiceEndpointNames . SystemVssConnection , StringComparison . OrdinalIgnoreCase ) ) ;
2023-02-27 11:50:28 -05:00
if ( MessageUtil . IsRunServiceJob ( message . MessageType ) )
2023-02-01 16:18:31 -05:00
{
var runServer = HostContext . GetService < IRunServer > ( ) ;
VssCredentials jobServerCredential = VssUtil . GetVssCredential ( systemConnection ) ;
await runServer . ConnectAsync ( systemConnection . Url , jobServerCredential ) ;
server = runServer ;
2023-04-26 12:51:56 -04:00
2023-05-03 16:04:21 -04:00
message . Variables . TryGetValue ( "system.github.launch_endpoint" , out VariableValue launchEndpointVariable ) ;
var launchReceiverEndpoint = launchEndpointVariable ? . Value ;
if ( systemConnection ? . Authorization ! = null & &
systemConnection . Authorization . Parameters . TryGetValue ( "AccessToken" , out var accessToken ) & &
! string . IsNullOrEmpty ( accessToken ) & &
! string . IsNullOrEmpty ( launchReceiverEndpoint ) )
{
Trace . Info ( "Initializing launch client" ) ;
var launchServer = HostContext . GetService < ILaunchServer > ( ) ;
launchServer . InitializeLaunchClient ( new Uri ( launchReceiverEndpoint ) , accessToken ) ;
}
2023-04-26 12:51:56 -04:00
_jobServerQueue = HostContext . GetService < IJobServerQueue > ( ) ;
2024-04-16 16:52:10 -04:00
_jobServerQueue . Start ( message , resultsServiceOnly : true ) ;
2023-02-01 16:18:31 -05:00
}
2023-02-27 11:50:28 -05:00
else
2023-02-01 16:18:31 -05:00
{
// Setup the job server and job server queue.
var jobServer = HostContext . GetService < IJobServer > ( ) ;
VssCredentials jobServerCredential = VssUtil . GetVssCredential ( systemConnection ) ;
Uri jobServerUrl = systemConnection . Url ;
2019-10-10 00:52:42 -04:00
2023-02-01 16:18:31 -05:00
Trace . Info ( $"Creating job server with URL: {jobServerUrl}" ) ;
// jobServerQueue is the throttling reporter.
_jobServerQueue = HostContext . GetService < IJobServerQueue > ( ) ;
2023-08-31 17:35:50 -04:00
var delegatingHandlers = new List < DelegatingHandler > ( ) { new ThrottlingReportHandler ( _jobServerQueue ) } ;
message . Variables . TryGetValue ( "Actions.EnableHttpRedirects" , out VariableValue enableHttpRedirects ) ;
if ( StringUtil . ConvertToBoolean ( enableHttpRedirects ? . Value ) & &
! StringUtil . ConvertToBoolean ( Environment . GetEnvironmentVariable ( "GITHUB_ACTIONS_RUNNER_NO_HTTP_REDIRECTS" ) ) )
{
delegatingHandlers . Add ( new RedirectMessageHandler ( Trace ) ) ;
}
VssConnection jobConnection = VssUtil . CreateConnection ( jobServerUrl , jobServerCredential , delegatingHandlers ) ;
2023-02-01 16:18:31 -05:00
await jobServer . ConnectAsync ( jobConnection ) ;
2019-10-10 00:52:42 -04:00
2024-04-16 16:52:10 -04:00
_jobServerQueue . Start ( message ) ;
2023-02-01 16:18:31 -05:00
server = jobServer ;
}
2023-02-27 11:50:28 -05:00
2019-10-10 00:52:42 -04:00
HostContext . WritePerfCounter ( $"WorkerJobServerQueueStarted_{message.RequestId.ToString()}" ) ;
IExecutionContext jobContext = null ;
CancellationTokenRegistration ? runnerShutdownRegistration = null ;
2026-03-23 14:02:15 +00:00
IDapDebugger dapDebugger = null ;
2019-10-10 00:52:42 -04:00
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 ) ) ;
}
2023-05-10 16:24:02 +02:00
var issue = new Issue ( ) { Type = IssueType . Error , Message = errorMessage } ;
jobContext . AddIssue ( issue , ExecutionContextLogOptions . Default ) ;
2019-10-10 00:52:42 -04:00
} ) ;
// 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 ) ;
2023-02-01 16:18:31 -05:00
return await CompleteJobAsync ( server , jobContext , message , TaskResult . Failed ) ;
2019-10-10 00:52:42 -04:00
}
2020-07-19 19:05:47 -04:00
if ( jobContext . Global . WriteDebug )
2020-01-03 21:23:46 -05:00
{
jobContext . SetRunnerContext ( "debug" , "1" ) ;
}
2019-10-10 00:52:42 -04:00
jobContext . SetRunnerContext ( "os" , VarUtil . OS ) ;
2021-10-13 20:49:26 -07:00
jobContext . SetRunnerContext ( "arch" , VarUtil . OSArchitecture ) ;
2022-01-26 13:23:24 -05:00
jobContext . SetRunnerContext ( "name" , _runnerSettings . AgentName ) ;
2021-09-16 15:25:51 +02:00
2023-05-26 18:08:41 +01:00
if ( jobContext . Global . Variables . TryGetValue ( WellKnownDistributedTaskVariables . RunnerEnvironment , out var runnerEnvironment ) )
{
jobContext . SetRunnerContext ( "environment" , runnerEnvironment ) ;
}
2019-10-10 00:52:42 -04:00
string toolsDirectory = HostContext . GetDirectory ( WellKnownDirectory . Tools ) ;
Directory . CreateDirectory ( toolsDirectory ) ;
jobContext . SetRunnerContext ( "tool_cache" , toolsDirectory ) ;
// Setup TEMP directories
_tempDirectoryManager = HostContext . GetService < ITempDirectoryManager > ( ) ;
_tempDirectoryManager . InitializeTempDirectory ( jobContext ) ;
2026-03-23 14:02:15 +00:00
// Setup the debugger
2026-04-07 13:51:33 +01:00
if ( jobContext . Global . Debugger ? . Enabled = = true )
2026-03-23 14:02:15 +00:00
{
Trace . Info ( "Debugger enabled for this job run" ) ;
try
{
dapDebugger = HostContext . GetService < IDapDebugger > ( ) ;
await dapDebugger . StartAsync ( jobContext ) ;
}
catch ( Exception ex )
{
Trace . Error ( $"Failed to start DAP debugger: {ex.Message}" ) ;
AddDebuggerConnectionTelemetry ( jobContext , $"Failed: {ex.Message}" ) ;
jobContext . Error ( "Failed to start debugger." ) ;
return await CompleteJobAsync ( server , jobContext , message , TaskResult . Failed ) ;
}
}
2019-10-10 00:52:42 -04:00
// 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 )
{
2022-02-23 15:48:24 +01:00
// set the job to cancelled
2019-10-10 00:52:42 -04:00
// don't log error issue to job ExecutionContext, since server owns the job level issue
2022-02-23 15:48:24 +01:00
Trace . Error ( $"Job is cancelled during initialize." ) ;
2019-10-10 00:52:42 -04:00
Trace . Error ( $"Caught exception: {ex}" ) ;
2023-02-01 16:18:31 -05:00
return await CompleteJobAsync ( server , jobContext , message , TaskResult . Canceled ) ;
2019-10-10 00:52:42 -04:00
}
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}" ) ;
2023-02-01 16:18:31 -05:00
return await CompleteJobAsync ( server , jobContext , message , TaskResult . Failed ) ;
2019-10-10 00:52:42 -04:00
}
// 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()}" ) ;
2021-09-09 21:55:15 -04:00
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 ) ) ;
}
2026-03-23 14:02:15 +00:00
// Wait for DAP debugger client connection and handshake after "Set up job"
// so the job page shows the setup step before we block on the debugger
if ( dapDebugger ! = null )
{
try
{
await dapDebugger . WaitUntilReadyAsync ( ) ;
AddDebuggerConnectionTelemetry ( jobContext , "Connected" ) ;
}
catch ( OperationCanceledException ) when ( jobRequestCancellationToken . IsCancellationRequested )
{
Trace . Info ( "Job was cancelled before debugger client connected." ) ;
AddDebuggerConnectionTelemetry ( jobContext , "Canceled" ) ;
jobContext . Error ( "Job was cancelled before debugger client connected." ) ;
return await CompleteJobAsync ( server , jobContext , message , TaskResult . Canceled ) ;
}
catch ( Exception ex )
{
Trace . Error ( $"DAP debugger failed to become ready: {ex.Message}" ) ;
AddDebuggerConnectionTelemetry ( jobContext , $"Failed: {ex.Message}" ) ;
// If debugging was requested but the debugger is not available, fail the job
jobContext . Error ( "The debugger failed to start or no debugger client connected in time." ) ;
return await CompleteJobAsync ( server , jobContext , message , TaskResult . Failed ) ;
}
}
2019-10-10 00:52:42 -04:00
// Run all job steps
Trace . Info ( "Run all job steps." ) ;
var stepsRunner = HostContext . GetService < IStepsRunner > ( ) ;
try
{
foreach ( var step in jobSteps )
{
2020-07-29 16:19:04 -04:00
jobContext . JobSteps . Enqueue ( step ) ;
2019-10-10 00:52:42 -04:00
}
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 ) ;
2023-02-01 16:18:31 -05:00
return await CompleteJobAsync ( server , jobContext , message , TaskResult . Failed ) ;
2019-10-10 00:52:42 -04:00
}
finally
{
Trace . Info ( "Finalize job." ) ;
2023-07-17 08:36:20 -04:00
await jobExtension . FinalizeJob ( jobContext , message , jobStartTimeUtc ) ;
2019-10-10 00:52:42 -04:00
}
Trace . Info ( $"Job result after all job steps finish: {jobContext.Result ?? TaskResult.Succeeded}" ) ;
Trace . Info ( "Completing the job execution context." ) ;
2023-02-01 16:18:31 -05:00
return await CompleteJobAsync ( server , jobContext , message ) ;
2019-10-10 00:52:42 -04:00
}
finally
{
if ( runnerShutdownRegistration ! = null )
{
runnerShutdownRegistration . Value . Dispose ( ) ;
runnerShutdownRegistration = null ;
}
2026-03-23 14:02:15 +00:00
if ( dapDebugger ! = null )
{
await dapDebugger . OnJobCompletedAsync ( ) ;
}
2019-10-10 00:52:42 -04:00
await ShutdownQueue ( throwOnFailure : false ) ;
}
}
2023-02-01 16:18:31 -05:00
private async Task < TaskResult > CompleteJobAsync ( IRunnerService server , IExecutionContext jobContext , Pipelines . AgentJobRequestMessage message , TaskResult ? taskResult = null )
{
if ( server is IRunServer runServer )
{
return await CompleteJobAsync ( runServer , jobContext , message , taskResult ) ;
}
else if ( server is IJobServer jobServer )
{
return await CompleteJobAsync ( jobServer , jobContext , message , taskResult ) ;
}
else
{
throw new NotSupportedException ( ) ;
}
}
private async Task < TaskResult > CompleteJobAsync ( IRunServer runServer , IExecutionContext jobContext , Pipelines . AgentJobRequestMessage message , TaskResult ? taskResult = null )
{
jobContext . Debug ( $"Finishing: {message.JobDisplayName}" ) ;
TaskResult result = jobContext . Complete ( taskResult ) ;
2024-03-14 14:12:08 +01:00
2024-11-07 21:00:03 -05:00
var jobQueueTelemetry = await ShutdownQueue ( throwOnFailure : false ) ;
// include any job telemetry from the background upload process.
if ( jobQueueTelemetry ? . Count > 0 )
{
jobContext . Global . JobTelemetry . AddRange ( jobQueueTelemetry ) ;
}
2024-02-02 15:37:45 -05:00
2023-02-01 16:18:31 -05:00
// Make sure to clean temp after file upload since they may be pending fileupload still use the TEMP dir.
_tempDirectoryManager ? . CleanupTempDirectory ( ) ;
// Load any upgrade telemetry
LoadFromTelemetryFile ( jobContext . Global . JobTelemetry ) ;
// Make sure we don't submit secrets as telemetry
MaskTelemetrySecrets ( jobContext . Global . JobTelemetry ) ;
2023-06-16 00:13:08 -04:00
// Get environment url
string environmentUrl = null ;
if ( jobContext . ActionsEnvironment ? . Url is StringToken urlStringToken )
{
environmentUrl = urlStringToken . Value ;
}
2024-11-07 21:00:03 -05:00
// Get telemetry
IList < Telemetry > telemetry = null ;
if ( jobContext . Global . JobTelemetry . Count > 0 )
{
telemetry = jobContext . Global . JobTelemetry . Select ( x = > new Telemetry { Type = x . Type . ToString ( ) , Message = x . Message , } ) . ToList ( ) ;
}
2023-02-01 16:18:31 -05:00
Trace . Info ( $"Raising job completed against run service" ) ;
var completeJobRetryLimit = 5 ;
var exceptions = new List < Exception > ( ) ;
while ( completeJobRetryLimit - - > 0 )
{
try
{
2025-10-13 16:21:32 -04:00
await runServer . CompleteJobAsync ( message . Plan . PlanId , message . JobId , result , jobContext . JobOutputs , jobContext . Global . StepsResult , jobContext . Global . JobAnnotations , environmentUrl , telemetry , billingOwnerId : message . BillingOwnerId , infrastructureFailureCategory : jobContext . Global . InfrastructureFailureCategory , default ) ;
2023-02-01 16:18:31 -05:00
return result ;
}
2025-04-11 08:34:17 -05:00
catch ( VssUnauthorizedException ex )
2025-02-04 10:07:42 -06:00
{
Trace . Error ( $"Catch exception while attempting to complete job {message.JobId}, job request {message.RequestId}." ) ;
Trace . Error ( ex ) ;
exceptions . Add ( ex ) ;
break ;
}
2025-04-11 08:34:17 -05:00
catch ( TaskOrchestrationJobNotFoundException ex )
2025-02-04 10:07:42 -06:00
{
Trace . Error ( $"Catch exception while attempting to complete job {message.JobId}, job request {message.RequestId}." ) ;
Trace . Error ( ex ) ;
exceptions . Add ( ex ) ;
break ;
}
2023-02-01 16:18:31 -05:00
catch ( Exception ex )
{
Trace . Error ( $"Catch exception while attempting to complete job {message.JobId}, job request {message.RequestId}." ) ;
Trace . Error ( ex ) ;
exceptions . Add ( ex ) ;
}
// delay 5 seconds before next retry.
await Task . Delay ( TimeSpan . FromSeconds ( 5 ) ) ;
}
// rethrow exceptions from all attempts.
throw new AggregateException ( exceptions ) ;
}
2019-10-10 00:52:42 -04:00
private async Task < TaskResult > CompleteJobAsync ( IJobServer jobServer , IExecutionContext jobContext , Pipelines . AgentJobRequestMessage message , TaskResult ? taskResult = null )
{
jobContext . Debug ( $"Finishing: {message.JobDisplayName}" ) ;
TaskResult result = jobContext . Complete ( taskResult ) ;
2022-01-26 13:23:24 -05:00
if ( _runnerSettings . DisableUpdate = = true )
{
2024-11-07 21:00:03 -05:00
await WarningOutdatedRunnerAsync ( jobContext , message , result ) ;
2022-01-26 13:23:24 -05:00
}
2019-10-10 00:52:42 -04:00
try
{
2023-10-10 14:24:25 -04:00
var jobQueueTelemetry = await ShutdownQueue ( throwOnFailure : true ) ;
// include any job telemetry from the background upload process.
2024-11-07 21:00:03 -05:00
if ( jobQueueTelemetry ? . Count > 0 )
2023-10-10 14:24:25 -04:00
{
jobContext . Global . JobTelemetry . AddRange ( jobQueueTelemetry ) ;
}
2019-10-10 00:52:42 -04:00
}
catch ( Exception ex )
{
Trace . Error ( $"Caught exception from {nameof(JobServerQueue)}.{nameof(_jobServerQueue.ShutdownAsync)}" ) ;
Trace . Error ( "This indicate a failure during publish output variables. Fail the job to prevent unexpected job outputs." ) ;
Trace . Error ( ex ) ;
result = TaskResultUtil . MergeTaskResults ( result , TaskResult . Failed ) ;
}
// Clean TEMP after finish process jobserverqueue, since there might be a pending fileupload still use the TEMP dir.
_tempDirectoryManager ? . CleanupTempDirectory ( ) ;
2020-07-19 19:05:47 -04:00
if ( ! jobContext . Global . Features . HasFlag ( PlanFeatures . JobCompletedPlanEvent ) )
2019-10-10 00:52:42 -04:00
{
Trace . Info ( $"Skip raise job completed event call from worker because Plan version is {message.Plan.Version}" ) ;
return result ;
}
2021-09-29 15:49:31 -04:00
// Load any upgrade telemetry
2022-02-11 16:18:41 -05:00
LoadFromTelemetryFile ( jobContext . Global . JobTelemetry ) ;
2021-09-29 15:49:31 -04:00
2021-09-20 14:44:50 +02:00
// Make sure we don't submit secrets as telemetry
2022-02-11 16:18:41 -05:00
MaskTelemetrySecrets ( jobContext . Global . JobTelemetry ) ;
2021-09-20 14:44:50 +02:00
2022-02-16 12:18:21 -05:00
Trace . Info ( $"Raising job completed event" ) ;
2022-02-11 16:18:41 -05:00
var jobCompletedEvent = new JobCompletedEvent ( message . RequestId , message . JobId , result , jobContext . JobOutputs , jobContext . ActionsEnvironment , jobContext . Global . StepsTelemetry , jobContext . Global . JobTelemetry ) ;
2019-10-10 00:52:42 -04:00
var completeJobRetryLimit = 5 ;
var exceptions = new List < Exception > ( ) ;
while ( completeJobRetryLimit - - > 0 )
{
try
{
await jobServer . RaisePlanEventAsync ( message . Plan . ScopeIdentifier , message . Plan . PlanType , message . Plan . PlanId , jobCompletedEvent , default ( CancellationToken ) ) ;
return result ;
}
catch ( TaskOrchestrationPlanNotFoundException ex )
{
Trace . Error ( $"TaskOrchestrationPlanNotFoundException received, while attempting to raise JobCompletedEvent for job {message.JobId}." ) ;
Trace . Error ( ex ) ;
return TaskResult . Failed ;
}
catch ( TaskOrchestrationPlanSecurityException ex )
{
Trace . Error ( $"TaskOrchestrationPlanSecurityException received, while attempting to raise JobCompletedEvent for job {message.JobId}." ) ;
Trace . Error ( ex ) ;
return TaskResult . Failed ;
}
2020-05-21 11:09:50 -04:00
catch ( TaskOrchestrationPlanTerminatedException ex )
{
Trace . Error ( $"TaskOrchestrationPlanTerminatedException received, while attempting to raise JobCompletedEvent for job {message.JobId}." ) ;
Trace . Error ( ex ) ;
return TaskResult . Failed ;
}
2019-10-10 00:52:42 -04:00
catch ( Exception ex )
{
Trace . Error ( $"Catch exception while attempting to raise JobCompletedEvent for job {message.JobId}, job request {message.RequestId}." ) ;
Trace . Error ( ex ) ;
exceptions . Add ( ex ) ;
}
// delay 5 seconds before next retry.
await Task . Delay ( TimeSpan . FromSeconds ( 5 ) ) ;
}
// rethrow exceptions from all attempts.
throw new AggregateException ( exceptions ) ;
}
2026-03-23 14:02:15 +00:00
private static void AddDebuggerConnectionTelemetry ( IExecutionContext jobContext , string result )
{
jobContext . Global . JobTelemetry . Add ( new JobTelemetry
{
Type = JobTelemetryType . General ,
Message = $"{DebuggerConnectionTelemetryPrefix}: {result}"
} ) ;
}
2021-09-20 14:44:50 +02:00
private void MaskTelemetrySecrets ( List < JobTelemetry > jobTelemetry )
{
foreach ( var telemetryItem in jobTelemetry )
{
telemetryItem . Message = HostContext . SecretMasker . MaskSecrets ( telemetryItem . Message ) ;
}
}
2021-09-29 15:49:31 -04:00
private void LoadFromTelemetryFile ( List < JobTelemetry > jobTelemetry )
{
try
{
var telemetryFilePath = HostContext . GetConfigFile ( WellKnownConfigFile . Telemetry ) ;
if ( File . Exists ( telemetryFilePath ) )
{
var telemetryData = File . ReadAllText ( telemetryFilePath , Encoding . UTF8 ) ;
var telemetry = new JobTelemetry
{
Message = $"Runner File Telemetry:\n{telemetryData}" ,
Type = JobTelemetryType . General
} ;
jobTelemetry . Add ( telemetry ) ;
IOUtil . DeleteFile ( telemetryFilePath ) ;
}
}
catch ( Exception e )
{
Trace . Error ( "Error when trying to load telemetry from telemetry file" ) ;
Trace . Error ( e ) ;
}
}
2023-10-10 14:24:25 -04:00
private async Task < IList < JobTelemetry > > ShutdownQueue ( bool throwOnFailure )
2019-10-10 00:52:42 -04:00
{
if ( _jobServerQueue ! = null )
{
try
{
Trace . Info ( "Shutting down the job server queue." ) ;
await _jobServerQueue . ShutdownAsync ( ) ;
2023-10-10 14:24:25 -04:00
return _jobServerQueue . JobTelemetries ;
2019-10-10 00:52:42 -04:00
}
catch ( Exception ex ) when ( ! throwOnFailure )
{
Trace . Error ( $"Caught exception from {nameof(JobServerQueue)}.{nameof(_jobServerQueue.ShutdownAsync)}" ) ;
Trace . Error ( ex ) ;
}
finally
{
_jobServerQueue = null ; // Prevent multiple attempts.
}
}
2023-10-10 14:24:25 -04:00
return Array . Empty < JobTelemetry > ( ) ;
2019-10-10 00:52:42 -04:00
}
2024-11-07 21:00:03 -05:00
private async Task WarningOutdatedRunnerAsync ( IExecutionContext jobContext , Pipelines . AgentJobRequestMessage message , TaskResult result )
{
try
{
var currentVersion = new PackageVersion ( BuildConstants . RunnerPackage . Version ) ;
ServiceEndpoint systemConnection = message . Resources . Endpoints . Single ( x = > string . Equals ( x . Name , WellKnownServiceEndpointNames . SystemVssConnection , StringComparison . OrdinalIgnoreCase ) ) ;
VssCredentials serverCredential = VssUtil . GetVssCredential ( systemConnection ) ;
var runnerServer = HostContext . GetService < IRunnerServer > ( ) ;
await runnerServer . ConnectAsync ( systemConnection . Url , serverCredential ) ;
var serverPackages = await runnerServer . GetPackagesAsync ( "agent" , BuildConstants . RunnerPackage . PackageName , 5 , includeToken : false , cancellationToken : CancellationToken . None ) ;
if ( serverPackages . Count > 0 )
{
serverPackages = serverPackages . OrderByDescending ( x = > x . Version ) . ToList ( ) ;
Trace . Info ( $"Newer packages {StringUtil.ConvertToJson(serverPackages.Select(x => x.Version.ToString()))}" ) ;
var warnOnFailedJob = false ; // any minor/patch version behind.
var warnOnOldRunnerVersion = false ; // >= 2 minor version behind
if ( serverPackages . Any ( x = > x . Version . CompareTo ( currentVersion ) > 0 ) )
{
Trace . Info ( $"Current runner version {currentVersion} is behind the latest runner version {serverPackages[0].Version}." ) ;
warnOnFailedJob = true ;
}
if ( serverPackages . Where ( x = > x . Version . Major = = currentVersion . Major & & x . Version . Minor > currentVersion . Minor ) . Count ( ) > 1 )
{
Trace . Info ( $"Current runner version {currentVersion} is way behind the latest runner version {serverPackages[0].Version}." ) ;
warnOnOldRunnerVersion = true ;
}
if ( result = = TaskResult . Failed & & warnOnFailedJob )
{
2025-03-14 17:03:13 -04:00
jobContext . Warning ( $"This job failure may be caused by using an out of date version of GitHub runner on your self-hosted runner. You are currently using GitHub runner version {currentVersion}. Please update to the latest version {serverPackages[0].Version}" ) ;
2024-11-07 21:00:03 -05:00
}
else if ( warnOnOldRunnerVersion )
{
jobContext . Warning ( $"This self-hosted runner is currently using runner version {currentVersion}. This version is out of date. Please update to the latest version {serverPackages[0].Version}" ) ;
}
}
}
catch ( Exception ex )
{
// Ignore any error since suggest runner update is best effort.
Trace . Error ( $"Caught exception during runner version check: {ex}" ) ;
}
}
2019-10-10 00:52:42 -04:00
}
}