From 81049a5275a94043d7839dc51309fca92f260f89 Mon Sep 17 00:00:00 2001
From: William Sossamon <3278433+WillSoss@users.noreply.github.com>
Date: Sat, 17 Feb 2024 10:15:11 -0600
Subject: [PATCH] Use the generic host and application lifetime to handle
sigint/sigterm events
---
src/Diagnostics/Program.cs | 5 +-
src/Runly/Hosting/GetAction.cs | 96 ++--
src/Runly/Hosting/HostedAction.cs | 70 +++
src/Runly/Hosting/IHostAction.cs | 24 -
src/Runly/Hosting/ListAction.cs | 74 +--
src/Runly/Hosting/RunAction.cs | 513 +++++++++---------
src/Runly/JobHost.cs | 33 +-
src/Runly/Runly.csproj | 4 +-
src/Runly/ServiceExtensions.cs | 18 +-
test/Runly.Tests.Cli/Program.cs | 5 +-
test/Runly.Tests/Jobs.cs | 26 +-
test/Runly.Tests/Runly.Tests.csproj | 6 +-
.../Applying_config_overrides.cs | 32 +-
.../Scenarios/Running/Running_a_job.cs | 18 +-
14 files changed, 508 insertions(+), 416 deletions(-)
create mode 100644 src/Runly/Hosting/HostedAction.cs
delete mode 100644 src/Runly/Hosting/IHostAction.cs
diff --git a/src/Diagnostics/Program.cs b/src/Diagnostics/Program.cs
index bcf1a2a..5c33da9 100644
--- a/src/Diagnostics/Program.cs
+++ b/src/Diagnostics/Program.cs
@@ -1,4 +1,5 @@
-using System.Threading.Tasks;
+using Microsoft.Extensions.Hosting;
+using System.Threading.Tasks;
namespace Runly.Diagnostics
{
@@ -8,7 +9,7 @@ static Task Main(string[] args)
{
return JobHost.CreateDefaultBuilder(args)
.Build()
- .RunJobAsync();
+ .RunAsync();
}
}
}
diff --git a/src/Runly/Hosting/GetAction.cs b/src/Runly/Hosting/GetAction.cs
index 9d3e136..dbd6380 100644
--- a/src/Runly/Hosting/GetAction.cs
+++ b/src/Runly/Hosting/GetAction.cs
@@ -1,71 +1,81 @@
-using System;
+using Microsoft.Extensions.Hosting;
+using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Runly.Hosting
{
- class GetAction : IHostAction
+ internal class GetAction : HostedAction
{
- readonly bool verbose;
- string filePath;
- readonly string jobType;
- readonly JobCache cache;
+ private readonly bool _verbose;
+ private string _filePath;
+ private readonly string _jobType;
+ private readonly JobCache _cache;
+ private readonly IHostApplicationLifetime _applciationLifetime;
- internal GetAction(bool verbose, string jobType, string filePath, JobCache cache)
+ internal GetAction(bool verbose, string jobType, string filePath, JobCache cache, IHostApplicationLifetime applicationLifetime)
{
- this.verbose = verbose;
- this.jobType = jobType;
- this.filePath = filePath;
- this.cache = cache;
+ _verbose = verbose;
+ _jobType = jobType;
+ _filePath = filePath;
+ _cache = cache;
+ _applciationLifetime = applicationLifetime;
}
- public async Task RunAsync(CancellationToken cancel)
+ protected override async Task RunAsync(CancellationToken cancel)
{
- TextWriter writer = null;
- JobInfo job;
-
try
{
- job = cache.Get(jobType);
- }
- catch (TypeNotFoundException)
- {
- Console.WriteLine($"Could not find the job type '{jobType}'.");
- return;
- }
+ TextWriter writer = null;
+ JobInfo job;
- var config = cache.GetDefaultConfig(job);
+ try
+ {
+ job = _cache.Get(_jobType);
+ }
+ catch (TypeNotFoundException)
+ {
+ Console.WriteLine($"Could not find the job type '{_jobType}'.");
+ return;
+ }
- try
- {
+ var config = _cache.GetDefaultConfig(job);
- if (filePath == null)
+ try
{
- writer = Console.Out;
+
+ if (_filePath == null)
+ {
+ writer = Console.Out;
+ }
+ else
+ {
+ // If path is an existing directory, such as ".", add a file name
+ if (Directory.Exists(_filePath))
+ _filePath = Path.Combine(_filePath, job.JobType.Name + ".json");
+
+ writer = new StreamWriter(File.Open(_filePath, FileMode.Create));
+ }
+
+ await writer.WriteAsync(_verbose ? ConfigWriter.ToJson(config) : ConfigWriter.ToReducedJson(config));
}
- else
+ finally
{
- // If path is an existing directory, such as ".", add a file name
- if (Directory.Exists(filePath))
- filePath = Path.Combine(filePath, job.JobType.Name + ".json");
-
- writer = new StreamWriter(File.Open(filePath, FileMode.Create));
+ if (_filePath != null && writer != null)
+ {
+ await writer.FlushAsync();
+ writer.Dispose();
+ }
}
-
- await writer.WriteAsync(verbose ? ConfigWriter.ToJson(config) : ConfigWriter.ToReducedJson(config));
+
+ if (_filePath != null)
+ Console.WriteLine($"Default config for {job.JobType.FullName} saved to {Path.GetFullPath(_filePath)}");
}
finally
{
- if (filePath != null && writer != null)
- {
- await writer.FlushAsync();
- writer.Dispose();
- }
+ _applciationLifetime?.StopApplication();
}
-
- if (filePath != null)
- Console.WriteLine($"Default config for {job.JobType.FullName} saved to {Path.GetFullPath(filePath)}");
}
}
}
diff --git a/src/Runly/Hosting/HostedAction.cs b/src/Runly/Hosting/HostedAction.cs
new file mode 100644
index 0000000..febbfcb
--- /dev/null
+++ b/src/Runly/Hosting/HostedAction.cs
@@ -0,0 +1,70 @@
+using Microsoft.Extensions.Hosting;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Runly.Hosting
+{
+ internal abstract class HostedAction : IHostedService
+ {
+ private Task _run;
+ private CancellationTokenSource _stoppingCts;
+
+ ///
+ /// Triggered when the application host is ready to start the service.
+ ///
+ /// Indicates that the start process has been aborted.
+ /// A that represents the asynchronous Start operation.
+ public virtual Task StartAsync(CancellationToken cancellationToken)
+ {
+ // Create linked token to allow cancelling executing task from provided token
+ _stoppingCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+
+ // Store the task we're executing
+ _run = RunAsync(_stoppingCts.Token);
+
+ // If the task is completed then return it, this will bubble cancellation and failure to the caller
+ if (_run.IsCompleted)
+ {
+ return _run;
+ }
+
+ // Otherwise it's running
+ return Task.CompletedTask;
+ }
+
+ ///
+ /// Triggered when the application host is performing a graceful shutdown.
+ ///
+ /// Indicates that the shutdown process should no longer be graceful.
+ /// A that represents the asynchronous Stop operation.
+ public virtual async Task StopAsync(CancellationToken cancellationToken)
+ {
+ // Stop called without start
+ if (_run == null)
+ return;
+
+ try
+ {
+ // Signal cancellation to the executing method
+ _stoppingCts!.Cancel();
+ }
+ finally
+ {
+ // Wait until the task completes or the stop token triggers
+ var tcs = new TaskCompletionSource