public async Task <int> ExecuteCommand(CommandSettings command) { try { VssUtil.InitializeVssClientSettings(HostContext.UserAgent, HostContext.WebProxy); _inConfigStage = true; _completedCommand.Reset(); _term.CancelKeyPress += CtrlCHandler; //register a SIGTERM handler HostContext.Unloading += Runner_Unloading; // TODO Unit test to cover this logic Trace.Info(nameof(ExecuteCommand)); var configManager = HostContext.GetService <IConfigurationManager>(); // command is not required, if no command it just starts if configured // TODO: Invalid config prints usage if (command.Help) { PrintUsage(command); return(Constants.Runner.ReturnCode.Success); } if (command.Version) { _term.WriteLine(BuildConstants.RunnerPackage.Version); return(Constants.Runner.ReturnCode.Success); } if (command.Commit) { _term.WriteLine(BuildConstants.Source.CommitHash); return(Constants.Runner.ReturnCode.Success); } // Configure runner prompt for args if not supplied // Unattended configure mode will not prompt for args if not supplied and error on any missing or invalid value. if (command.Configure) { try { await configManager.ConfigureAsync(command); return(Constants.Runner.ReturnCode.Success); } catch (Exception ex) { Trace.Error(ex); _term.WriteError(ex.Message); return(Constants.Runner.ReturnCode.TerminatedError); } } // remove config files, remove service, and exit if (command.Remove) { try { await configManager.UnconfigureAsync(command); return(Constants.Runner.ReturnCode.Success); } catch (Exception ex) { Trace.Error(ex); _term.WriteError(ex.Message); return(Constants.Runner.ReturnCode.TerminatedError); } } _inConfigStage = false; // warmup runner process (JIT/CLR) // In scenarios where the runner is single use (used and then thrown away), the system provisioning the runner can call `Runner.Listener --warmup` before the machine is made available to the pool for use. // this will optimizes the runner process startup time. if (command.Warmup) { var binDir = HostContext.GetDirectory(WellKnownDirectory.Bin); foreach (var assemblyFile in Directory.EnumerateFiles(binDir, "*.dll")) { try { Trace.Info($"Load assembly: {assemblyFile}."); var assembly = Assembly.LoadFrom(assemblyFile); var types = assembly.GetTypes(); foreach (Type loadedType in types) { try { Trace.Info($"Load methods: {loadedType.FullName}."); var methods = loadedType.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); foreach (var method in methods) { if (!method.IsAbstract && !method.ContainsGenericParameters) { Trace.Verbose($"Prepare method: {method.Name}."); RuntimeHelpers.PrepareMethod(method.MethodHandle); } } } catch (Exception ex) { Trace.Error(ex); } } } catch (Exception ex) { Trace.Error(ex); } } return(Constants.Runner.ReturnCode.Success); } RunnerSettings settings = configManager.LoadSettings(); var store = HostContext.GetService <IConfigurationStore>(); bool configuredAsService = store.IsServiceConfigured(); // Run runner if (command.Run) // this line is current break machine provisioner. { // Error if runner not configured. if (!configManager.IsConfigured()) { _term.WriteError("Runner is not configured."); PrintUsage(command); return(Constants.Runner.ReturnCode.TerminatedError); } Trace.Verbose($"Configured as service: '{configuredAsService}'"); //Get the startup type of the runner i.e., autostartup, service, manual StartupType startType; var startupTypeAsString = command.GetStartupType(); if (string.IsNullOrEmpty(startupTypeAsString) && configuredAsService) { // We need try our best to make the startup type accurate // The problem is coming from runner autoupgrade, which result an old version service host binary but a newer version runner binary // At that time the servicehost won't pass --startuptype to Runner.Listener while the runner is actually running as service. // We will guess the startup type only when the runner is configured as service and the guess will based on whether STDOUT/STDERR/STDIN been redirect or not Trace.Info($"Try determine runner startup type base on console redirects."); startType = (Console.IsErrorRedirected && Console.IsInputRedirected && Console.IsOutputRedirected) ? StartupType.Service : StartupType.Manual; } else { if (!Enum.TryParse(startupTypeAsString, true, out startType)) { Trace.Info($"Could not parse the argument value '{startupTypeAsString}' for StartupType. Defaulting to {StartupType.Manual}"); startType = StartupType.Manual; } } Trace.Info($"Set runner startup type - {startType}"); HostContext.StartupType = startType; // Run the runner interactively or as service return(await RunAsync(settings, command.RunOnce)); } else { PrintUsage(command); return(Constants.Runner.ReturnCode.Success); } } finally { _term.CancelKeyPress -= CtrlCHandler; HostContext.Unloading -= Runner_Unloading; _completedCommand.Set(); } }
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); bool cancel = false; while (!cancel) { // 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."); await Task.WhenAny(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 message was received from the channel. channelMessage = await channelTask; switch (channelMessage.MessageType) { case MessageType.CancelRequest: Trace.Info("Cancellation/Shutdown message received."); cancel = true; jobRequestCancellationToken.Cancel(); // Expire the host cancellation token. break; case MessageType.AgentShutdown: Trace.Info("Cancellation/Shutdown message received."); cancel = true; HostContext.ShutdownAgent(ShutdownReason.UserCancelled); break; case MessageType.OperatingSystemShutdown: Trace.Info("Cancellation/Shutdown message received."); cancel = true; HostContext.ShutdownAgent(ShutdownReason.OperatingSystemShutdown); break; case MessageType.JobMetadataUpdate: Trace.Info("Metadata update message received."); var metadataMessage = JsonUtility.FromString <JobMetadataMessage>(channelMessage.Body); jobRunner.UpdateMetadata(metadataMessage); break; default: throw new ArgumentOutOfRangeException(nameof(channelMessage.MessageType), channelMessage.MessageType, nameof(channelMessage.MessageType)); } } // Await the job. return(TaskResultUtil.TranslateToReturnCode(await jobRunnerTask)); } }
public async Task <int> RunAsync(string pipeIn, string pipeOut) { try { // Setup way to handle SIGTERM/unloading signals _completedCommand.Reset(); HostContext.Unloading += Worker_Unloading; // Validate args. ArgUtil.NotNullOrEmpty(pipeIn, nameof(pipeIn)); ArgUtil.NotNullOrEmpty(pipeOut, nameof(pipeOut)); VssUtil.InitializeVssClientSettings(HostContext.UserAgents, HostContext.WebProxy); var jobRunner = HostContext.CreateService <IJobRunner>(); using (var channel = HostContext.CreateService <IProcessChannel>()) using (var jobRequestCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(HostContext.RunnerShutdownToken)) 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 = StringUtil.ConvertFromJson <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(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.RunnerShutdown: HostContext.ShutdownRunner(ShutdownReason.UserCancelled); break; case MessageType.OperatingSystemShutdown: HostContext.ShutdownRunner(ShutdownReason.OperatingSystemShutdown); break; default: throw new ArgumentOutOfRangeException(nameof(channelMessage.MessageType), channelMessage.MessageType, nameof(channelMessage.MessageType)); } // Await the job. return(TaskResultUtil.TranslateToReturnCode(await jobRunnerTask)); } } finally { HostContext.Unloading -= Worker_Unloading; _completedCommand.Set(); } }
public async Task <int> ExecuteCommand(CommandSettings command) { try { var agentWebProxy = HostContext.GetService <IVstsAgentWebProxy>(); var agentCertManager = HostContext.GetService <IAgentCertificateManager>(); VssUtil.InitializeVssClientSettings(HostContext.UserAgent, agentWebProxy.WebProxy, agentCertManager.VssClientCertificateManager); _inConfigStage = true; _completedCommand.Reset(); _term.CancelKeyPress += CtrlCHandler; //register a SIGTERM handler HostContext.Unloading += Agent_Unloading; // TODO Unit test to cover this logic Trace.Info(nameof(ExecuteCommand)); var configManager = HostContext.GetService <IConfigurationManager>(); // command is not required, if no command it just starts if configured // TODO: Invalid config prints usage if (command.Help) { PrintUsage(command); return(Constants.Agent.ReturnCode.Success); } if (command.Version) { _term.WriteLine(Constants.Agent.Version); return(Constants.Agent.ReturnCode.Success); } if (command.Commit) { _term.WriteLine(BuildConstants.Source.CommitHash); return(Constants.Agent.ReturnCode.Success); } // Configure agent prompt for args if not supplied // Unattend configure mode will not prompt for args if not supplied and error on any missing or invalid value. if (command.Configure) { try { await configManager.ConfigureAsync(command); return(Constants.Agent.ReturnCode.Success); } catch (Exception ex) { Trace.Error(ex); _term.WriteError(ex.Message); return(Constants.Agent.ReturnCode.TerminatedError); } } // remove config files, remove service, and exit if (command.Remove) { try { await configManager.UnconfigureAsync(command); return(Constants.Agent.ReturnCode.Success); } catch (Exception ex) { Trace.Error(ex); _term.WriteError(ex.Message); return(Constants.Agent.ReturnCode.TerminatedError); } } _inConfigStage = false; AgentSettings settings = configManager.LoadSettings(); var store = HostContext.GetService <IConfigurationStore>(); bool configuredAsService = store.IsServiceConfigured(); // Run agent //if (command.Run) // this line is current break machine provisioner. //{ // Error if agent not configured. if (!configManager.IsConfigured()) { _term.WriteError(StringUtil.Loc("AgentIsNotConfigured")); PrintUsage(command); return(Constants.Agent.ReturnCode.TerminatedError); } Trace.Verbose($"Configured as service: '{configuredAsService}'"); //Get the startup type of the agent i.e., autostartup, service, manual StartupType startType; var startupTypeAsString = command.GetStartupType(); if (string.IsNullOrEmpty(startupTypeAsString) && configuredAsService) { // We need try our best to make the startup type accurate // The problem is coming from agent autoupgrade, which result an old version service host binary but a newer version agent binary // At that time the servicehost won't pass --startuptype to agent.listener while the agent is actually running as service. // We will guess the startup type only when the agent is configured as service and the guess will based on whether STDOUT/STDERR/STDIN been redirect or not Trace.Info($"Try determine agent startup type base on console redirects."); startType = (Console.IsErrorRedirected && Console.IsInputRedirected && Console.IsOutputRedirected) ? StartupType.Service : StartupType.Manual; } else { if (!Enum.TryParse(startupTypeAsString, true, out startType)) { Trace.Info($"Could not parse the argument value '{startupTypeAsString}' for StartupType. Defaulting to {StartupType.Manual}"); startType = StartupType.Manual; } } Trace.Info($"Set agent startup type - {startType}"); HostContext.StartupType = startType; #if OS_WINDOWS if (store.IsAutoLogonConfigured()) { if (HostContext.StartupType != StartupType.Service) { Trace.Info($"Autologon is configured on the machine, dumping all the autologon related registry settings"); var autoLogonRegManager = HostContext.GetService <IAutoLogonRegistryManager>(); autoLogonRegManager.DumpAutoLogonRegistrySettings(); } else { Trace.Info($"Autologon is configured on the machine but current Agent.Listner.exe is launched from the windows service"); } } #endif // Run the agent interactively or as service return(await RunAsync(settings)); } finally { _term.CancelKeyPress -= CtrlCHandler; HostContext.Unloading -= Agent_Unloading; _completedCommand.Set(); } }