protected override async Task ExecuteAsync(CancellationToken appStoppingToken) { // Exactly the same as TestJobMonitoring except the job is JobFac-aware and needs a payload try { Console.WriteLine("Getting job factory proxy."); var factory = jobFacServices.GetJobFactory(); var options = new FactoryStartOptions { DefinitionId = "Sample.JobFac.aware" // we could add the payload here but it's easier to use the StartJob overload // since FactoryStartOptions stores alternate argument lists and startup // payloads in a dictionary (which is useful for multi-job sequences) }; string payload = "35,Hello world!"; Console.WriteLine($"Starting sample job Sample.JobFac.aware with payload: {payload}"); var jobKey = await factory.StartJob(options, startupPayload : payload); // Everything below is identical to TestJobMonitoring Console.WriteLine($"Job instance key: {jobKey}"); var timeout = DateTimeOffset.UtcNow.AddSeconds(90); bool done = false; IJobExternalProcess job = null; while (!done && DateTimeOffset.UtcNow < timeout) { appStoppingToken.ThrowIfCancellationRequested(); Console.WriteLine("Pausing 10 seconds then reading status."); await Task.Delay(10000); if (job == null) { job = jobFacServices.GetExternalProcessJob(jobKey); } if (job == null) { Console.WriteLine("Failed to obtain job proxy."); done = true; break; } var status = await job.GetStatus(); Console.WriteLine($"Status {status.RunStatus} last updated {status.LastUpdated.ToLocalTime()}"); done = status.HasExited; } } catch (Exception ex) { Console.WriteLine($"\n\nException:\n{ex}"); } finally { appLifetime.StopApplication(); } }
// TODO respond to appStoppingToken cancellation protected override async Task ExecuteAsync(CancellationToken appStoppingToken) { IJobExternalProcess jobService = null; try { logger.LogTrace($"Runner starting, job instance {Program.JobInstanceKey}"); jobService = jobFacServices.GetExternalProcessJob(Program.JobInstanceKey); if (jobService == null) { logger.LogError("Runner was unable to obtain a reference to the Job service"); return; } await jobService.UpdateRunStatus(RunStatus.StartRequested); var jobDef = await jobService.GetDefinition(); await RunJob(jobService, jobDef); } catch (Exception ex) { logger.LogError($"ExecuteAsync {ex}"); await jobService.UpdateExitMessage(RunStatus.Unknown, -1, $"JobFac.Services.Runner exception {ex}"); } finally { logger.LogInformation($"Runner ending, calling StopApplication after log-flush delay"); await Task.Delay(10000); appLifetime.StopApplication(); } }
protected override async Task InitializingAsync(CancellationToken cancelInitToken) { IJobExternalProcess service = null; try { var args = options.CommandLineArgs.Length > 1 ? options.CommandLineArgs[1..^ 1] : new string[0];
protected override async Task ExecuteAsync(CancellationToken appStoppingToken) { // Exactly the same as TestJobPayload except the job isn't JobFac-aware try { Console.WriteLine("Getting job factory proxy."); var factory = jobFacServices.GetJobFactory(); var options = new FactoryStartOptions { DefinitionId = "Sample.JobFac.unaware" }; Console.WriteLine("Starting sample job: Sample.JobFac.unaware"); var jobKey = await factory.StartJob(options); Console.WriteLine($"Job instance key: {jobKey}"); var timeout = DateTimeOffset.UtcNow.AddSeconds(90); bool done = false; IJobExternalProcess job = null; while (!done && DateTimeOffset.UtcNow < timeout) { appStoppingToken.ThrowIfCancellationRequested(); Console.WriteLine("Pausing 10 seconds then reading status."); await Task.Delay(10000); if (job == null) { job = jobFacServices.GetExternalProcessJob(jobKey); } if (job == null) { Console.WriteLine("Failed to obtain job proxy."); done = true; break; } var status = await job.GetStatus(); Console.WriteLine($"Status {status.RunStatus} last updated {status.LastUpdated.ToLocalTime()}"); done = status.HasExited; } } catch (Exception ex) { Console.WriteLine($"\n\nException:\n{ex}"); } finally { appLifetime.StopApplication(); } }
public JobFacAwareProcessContext( IHostApplicationLifetime hostApplicationLifetime, string instanceId, string[] args, string payload, IJobExternalProcess jobService, IJobFacServiceProvider provider) { appLifetime = hostApplicationLifetime; JobInstanceId = instanceId; CommandLineArgs = args; StartupPayload = payload; JobService = jobService; JobServiceProvider = provider; }
private async Task RunJob(IJobExternalProcess jobService, JobDefinition <DefinitionExternalProcess> jobDef) { logger.LogTrace($"RunJob starting"); var tokenSource = new CancellationTokenSource(); var token = tokenSource.Token; StringBuilder dbStdOut = new StringBuilder(); StringBuilder dbStdErr = new StringBuilder(); StreamWriter fileStdOut = null; StreamWriter fileStdErr = null; var jobProps = jobDef.JobTypeProperties; var proc = new Process(); proc.StartInfo.FileName = jobProps.ExecutablePathname; proc.StartInfo.WorkingDirectory = jobProps.WorkingDirectory; proc.StartInfo.UseShellExecute = false; proc.StartInfo.CreateNoWindow = true; // for JobProc-aware jobs, first argument is the job instance key proc.StartInfo.Arguments = (jobProps.IsJobFacAware) ? $"{Program.JobInstanceKey} {jobProps.Arguments}" : jobProps.Arguments; try { if (jobProps.CaptureStdOut != JobStreamHandling.None) { proc.StartInfo.RedirectStandardOutput = true; if (jobProps.CaptureStdOut == JobStreamHandling.Database) { proc.OutputDataReceived += (s, e) => { if (e?.Data != null) { dbStdOut.AppendLine(e.Data); } } } ; if (jobProps.CaptureStdOut.IsFileBased()) { var pathname = jobProps.CaptureStdOut == JobStreamHandling.TimestampedFile ? jobProps.StdOutPathname.Replace("*", Formatting.FilenameTimestampUtcNow) : jobProps.StdOutPathname; fileStdOut = new StreamWriter(pathname, jobProps.CaptureStdOut == JobStreamHandling.AppendFile); proc.OutputDataReceived += (s, e) => { if (e?.Data != null) { fileStdOut.WriteLineAsync(e.Data); } }; } } if (jobProps.CaptureStdErr != JobStreamHandling.None) { proc.StartInfo.RedirectStandardError = true; if (jobProps.CaptureStdErr == JobStreamHandling.Database) { proc.ErrorDataReceived += (s, e) => { if (e?.Data != null) { dbStdErr.AppendLine(e.Data); } } } ; if (jobProps.CaptureStdErr.IsFileBased()) { var pathname = jobProps.CaptureStdErr == JobStreamHandling.TimestampedFile ? jobProps.StdErrPathname.Replace("*", Formatting.FilenameTimestampUtcNow) : jobProps.StdErrPathname; fileStdErr = new StreamWriter(pathname, jobProps.CaptureStdErr == JobStreamHandling.AppendFile); proc.ErrorDataReceived += (s, e) => { if (e?.Data != null) { fileStdErr.WriteLineAsync(e.Data); } }; } } logger.LogTrace($"RunJob calling Process.Start"); try { // Although Start returns a boolean, it isn't useful to us. The documentation // says it returns false if a process is reused, which could happen if Process // was used with the Windows shell to launch a document handler such as Word. // That isn't a supported use-case. However, it can throw exceptions when, for // example, the provided ExecutablePathname is invalid. proc.Start(); } catch (Exception ex) { await jobService.UpdateExitMessage(RunStatus.StartFailed, -1, $"Job failed to start, exception {ex}"); return; } // These must be as close to proc.Start as possible to minimize the possibility of lost output if (jobProps.CaptureStdOut != JobStreamHandling.None) { proc.BeginOutputReadLine(); } if (jobProps.CaptureStdErr != JobStreamHandling.None) { proc.BeginErrorReadLine(); } await jobService.UpdateRunStatus(RunStatus.Running); // TODO implement maximum run-time token cancellation logger.LogTrace($"RunJob awaiting process exit or kill command"); await Task.WhenAny ( WaitForProcessExitAsync(proc, token), MonitorKillCommandNamedPipe(token) ).ConfigureAwait(false); logger.LogTrace($"RunJob await exited, Process.HasExited? {proc.HasExited}"); if (!proc.HasExited) { proc.Kill(true); // still fires process-exit event (WaitForExitAsync still running) await jobService.UpdateExitMessage(RunStatus.Stopped, -1, "Stop command received, process killed"); } else { // when true, job is JobProc-aware and should have called UpdateExitMessage if (!jobProps.IsJobFacAware) { var finalStatus = (proc.ExitCode < 0) ? RunStatus.Failed : RunStatus.Ended; await jobService.UpdateExitMessage(finalStatus, proc.ExitCode, string.Empty); } // TODO play it safe and retrieve status and verify JobProc-aware job actually set an exit message? } logger.LogTrace($"RunJob cancelling token"); tokenSource.Cancel(); // The Process WaitForExit call without a timeout value is the // only way to asynchronously wait for the streams to drain. if (jobProps.CaptureStdOut != JobStreamHandling.None || jobProps.CaptureStdErr != JobStreamHandling.None) { proc.WaitForExit(); } } catch (Exception ex) { logger.LogError($"RunJob caught exception {ex}"); } finally { logger.LogTrace($"RunJob finalizing"); tokenSource?.Cancel(); proc?.Close(); tokenSource?.Dispose(); proc?.Dispose(); fileStdOut?.Close(); fileStdErr?.Close(); fileStdOut?.Dispose(); fileStdErr?.Dispose(); } if (jobProps.CaptureStdOut == JobStreamHandling.Database || jobProps.CaptureStdErr == JobStreamHandling.Database) { await jobService.WriteCapturedOutput(Program.JobInstanceKey, dbStdOut.ToString(), dbStdErr.ToString()); } logger.LogTrace($"RunJob exiting"); }