public async Task <int> RunAsync(string pipeIn, string pipeOut) { // Validate args. ArgUtil.NotNullOrEmpty(pipeIn, nameof(pipeIn)); ArgUtil.NotNullOrEmpty(pipeOut, nameof(pipeOut)); var agentWebProxy = HostContext.GetService <IVstsAgentWebProxy>(); var agentCertManager = HostContext.GetService <IAgentCertificateManager>(); VssUtil.InitializeVssClientSettings(HostContext.UserAgent, agentWebProxy.WebProxy, agentCertManager.VssClientCertificateManager); var jobRunner = HostContext.CreateService <IJobRunner>(); using (var channel = HostContext.CreateService <IProcessChannel>()) using (var jobRequestCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(HostContext.AgentShutdownToken)) using (var channelTokenSource = new CancellationTokenSource()) { // Start the channel. channel.StartClient(pipeIn, pipeOut); // Wait for up to 30 seconds for a message from the channel. HostContext.WritePerfCounter("WorkerWaitingForJobMessage"); Trace.Info("Waiting to receive the job message from the channel."); WorkerMessage channelMessage; using (var csChannelMessage = new CancellationTokenSource(_workerStartTimeout)) { channelMessage = await channel.ReceiveAsync(csChannelMessage.Token); } // Deserialize the job message. Trace.Info("Message received."); ArgUtil.Equal(MessageType.NewJobRequest, channelMessage.MessageType, nameof(channelMessage.MessageType)); ArgUtil.NotNullOrEmpty(channelMessage.Body, nameof(channelMessage.Body)); var jobMessage = JsonUtility.FromString <Pipelines.AgentJobRequestMessage>(channelMessage.Body); ArgUtil.NotNull(jobMessage, nameof(jobMessage)); HostContext.WritePerfCounter($"WorkerJobMessageReceived_{jobMessage.RequestId.ToString()}"); // Initialize the secret masker and set the thread culture. InitializeSecretMasker(jobMessage); SetCulture(jobMessage); // Start the job. Trace.Info($"Job message:{Environment.NewLine} {StringUtil.ConvertToJson(WorkerUtilities.ScrubPiiData(jobMessage))}"); Task <TaskResult> jobRunnerTask = jobRunner.RunAsync(jobMessage, jobRequestCancellationToken.Token); // Start listening for a cancel message from the channel. Trace.Info("Listening for cancel message from the channel."); Task <WorkerMessage> channelTask = channel.ReceiveAsync(channelTokenSource.Token); // Wait for one of the tasks to complete. Trace.Info("Waiting for the job to complete or for a cancel message from the channel."); Task.WaitAny(jobRunnerTask, channelTask); // Handle if the job completed. if (jobRunnerTask.IsCompleted) { Trace.Info("Job completed."); channelTokenSource.Cancel(); // Cancel waiting for a message from the channel. return(TaskResultUtil.TranslateToReturnCode(await jobRunnerTask)); } // Otherwise a cancel message was received from the channel. Trace.Info("Cancellation/Shutdown message received."); channelMessage = await channelTask; switch (channelMessage.MessageType) { case MessageType.CancelRequest: jobRequestCancellationToken.Cancel(); // Expire the host cancellation token. break; case MessageType.AgentShutdown: HostContext.ShutdownAgent(ShutdownReason.UserCancelled); break; case MessageType.OperatingSystemShutdown: HostContext.ShutdownAgent(ShutdownReason.OperatingSystemShutdown); break; default: throw new ArgumentOutOfRangeException(nameof(channelMessage.MessageType), channelMessage.MessageType, nameof(channelMessage.MessageType)); } // Await the job. return(TaskResultUtil.TranslateToReturnCode(await jobRunnerTask)); } }
public bool TryProcessCommand(IExecutionContext context, string input) { ArgUtil.NotNull(context, nameof(context)); if (string.IsNullOrEmpty(input)) { return(false); } // TryParse input to Command Command command; var unescapePercents = AgentKnobs.DecodePercents.GetValue(context).AsBoolean(); if (!Command.TryParse(input, unescapePercents, out command)) { // if parse fail but input contains ##vso, print warning with DOC link if (input.IndexOf("##vso") >= 0) { context.Warning(StringUtil.Loc("CommandKeywordDetected", input)); } return(false); } IWorkerCommandExtension extension = null; if (_invokePluginInternalCommand && string.Equals(command.Area, _pluginInternalCommandExtensions.CommandArea, StringComparison.OrdinalIgnoreCase)) { extension = _pluginInternalCommandExtensions; } if (extension != null || _commandExtensions.TryGetValue(command.Area, out extension)) { if (!extension.SupportedHostTypes.HasFlag(context.Variables.System_HostType)) { context.Error(StringUtil.Loc("CommandNotSupported", command.Area, context.Variables.System_HostType)); context.CommandResult = TaskResult.Failed; return(false); } // process logging command in serialize oreder. lock (_commandSerializeLock) { try { extension.ProcessCommand(context, command); } catch (SocketException ex) { #pragma warning disable CA2000 // Dispose objects before losing scope ExceptionsUtil.HandleSocketException(ex, WorkerUtilities.GetVssConnection(context).Uri.ToString(), context.Error); #pragma warning restore CA2000 // Dispose objects before losing scope context.CommandResult = TaskResult.Failed; } catch (Exception ex) { context.Error(StringUtil.Loc("CommandProcessFailed", input)); context.Error(ex); context.CommandResult = TaskResult.Failed; } finally { // trace the ##vso command as long as the command is not a ##vso[task.debug] command. if (!(string.Equals(command.Area, "task", StringComparison.OrdinalIgnoreCase) && string.Equals(command.Event, "debug", StringComparison.OrdinalIgnoreCase))) { context.Debug($"Processed: {input}"); } } } } else { context.Warning(StringUtil.Loc("CommandNotFound", command.Area)); } // Only if we've successfully parsed do we show this warning if (AgentKnobs.DecodePercents.GetValue(context).AsString() == "" && input.Contains("%AZP25")) { context.Warning("%AZP25 detected in ##vso command. In March 2021, the agent command parser will be updated to unescape this to %. To opt out of this behavior, set a job level variable DECODE_PERCENTS to false. Setting to true will force this behavior immediately. More information can be found at https://github.com/microsoft/azure-pipelines-agent/blob/master/docs/design/percentEncoding.md"); } return(true); }