Skip to content

Commit

Permalink
Use the generic host and application lifetime to handle sigint/sigter…
Browse files Browse the repository at this point in the history
…m events
  • Loading branch information
WillSoss committed Feb 17, 2024
1 parent 5456795 commit 81049a5
Show file tree
Hide file tree
Showing 14 changed files with 508 additions and 416 deletions.
5 changes: 3 additions & 2 deletions src/Diagnostics/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using System.Threading.Tasks;

namespace Runly.Diagnostics
{
Expand All @@ -8,7 +9,7 @@ static Task Main(string[] args)
{
return JobHost.CreateDefaultBuilder(args)
.Build()
.RunJobAsync();
.RunAsync();
}
}
}
96 changes: 53 additions & 43 deletions src/Runly/Hosting/GetAction.cs
Original file line number Diff line number Diff line change
@@ -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)}");
}
}
}
70 changes: 70 additions & 0 deletions src/Runly/Hosting/HostedAction.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Triggered when the application host is ready to start the service.
/// </summary>
/// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous Start operation.</returns>
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;
}

/// <summary>
/// Triggered when the application host is performing a graceful shutdown.
/// </summary>
/// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous Stop operation.</returns>
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<object>();
using CancellationTokenRegistration registration = cancellationToken.Register(s => ((TaskCompletionSource<object>)s!).SetCanceled(), tcs);
// Do not await the _executeTask because cancelling it will throw an OperationCanceledException which we are explicitly ignoring
await Task.WhenAny(_run, tcs.Task).ConfigureAwait(false);
}

}

/// <inheritdoc />
public virtual void Dispose()
{
_stoppingCts?.Cancel();
}

protected abstract Task RunAsync(CancellationToken cancellation);
}
}
24 changes: 0 additions & 24 deletions src/Runly/Hosting/IHostAction.cs

This file was deleted.

74 changes: 42 additions & 32 deletions src/Runly/Hosting/ListAction.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections;
using System.IO;
using System.Linq;
Expand All @@ -8,38 +9,47 @@

namespace Runly.Hosting
{
class ListAction : IHostAction
internal class ListAction : HostedAction
{
readonly bool verbose;
readonly bool json;
readonly JobCache cache;
readonly JsonSchema schema;
private readonly bool _verbose;
private readonly bool _json;
private readonly JobCache _cache;
private readonly JsonSchema _schema;
private readonly IHostApplicationLifetime _applicationLifetime;

public ListAction(bool verbose, bool json, JobCache cache, JsonSchema schema)
public ListAction(bool verbose, bool json, JobCache cache, JsonSchema schema, IHostApplicationLifetime applicationLifetime)
{
this.verbose = verbose;
this.json = json;
this.cache = cache ?? throw new ArgumentNullException(nameof(cache));
this.schema = schema ?? throw new ArgumentNullException(nameof(schema));
_verbose = verbose;
_json = json;
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
_schema = schema ?? throw new ArgumentNullException(nameof(schema));
_applicationLifetime = applicationLifetime;
}

public async Task RunAsync(CancellationToken cancel)
protected override async Task RunAsync(CancellationToken cancel)
{
string clientVersion = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;

if (json)
try
{
WriteJson(Console.Out, clientVersion);
}
else
string clientVersion = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;

if (_json)
{
WriteJson(Console.Out, clientVersion);
}
else
{
WritePlainText(Console.Out, clientVersion);
}

// Ensure the entire output can be read by the node
Console.WriteLine();
await Console.Out.FlushAsync();
}
finally
{
WritePlainText(Console.Out, clientVersion);
_applicationLifetime?.StopApplication();
}

// Ensure the entire output can be read by the node
Console.WriteLine();
await Console.Out.FlushAsync();
}
}

void WriteJson(TextWriter writer, string clientVersion)
{
Expand All @@ -49,26 +59,26 @@ void WriteJson(TextWriter writer, string clientVersion)

IEnumerable GetJobJson()
{
if (verbose)
if (_verbose)
{
return cache.Jobs.OrderBy(i => i.JobType.FullName).Select(p => new
return _cache.Jobs.OrderBy(i => i.JobType.FullName).Select(p => new
{
JobType = p.JobType.FullName,
ConfigType = p.ConfigType.FullName,
DefaultConfig = cache.GetDefaultConfig(p.JobType.FullName),
DefaultConfig = _cache.GetDefaultConfig(p.JobType.FullName),
Assembly = p.JobType.Assembly.GetName().Name,
CanRun = p.IsValid,
Errors = p.Errors.ToString(),
Schema = schema.Generate(p.ConfigType)
Schema = _schema.Generate(p.ConfigType)
});
}

// Include the reduced config
return cache.Jobs.OrderBy(i => i.JobType.FullName).Select(p => new
return _cache.Jobs.OrderBy(i => i.JobType.FullName).Select(p => new
{
JobType = p.JobType.FullName,
ConfigType = p.ConfigType.FullName,
DefaultConfig = ConfigWriter.ToReducedJObject(cache.GetDefaultConfig(p.JobType.FullName)),
DefaultConfig = ConfigWriter.ToReducedJObject(_cache.GetDefaultConfig(p.JobType.FullName)),
Assembly = p.JobType.Assembly.GetName().Name,
CanRun = p.IsValid,
Errors = p.Errors.ToString()
Expand All @@ -80,7 +90,7 @@ void WritePlainText(TextWriter writer, string clientVersion)
writer.WriteLine($"Client Version: v{clientVersion}");
writer.WriteLine();

foreach (var job in cache.Jobs.OrderBy(i => i.JobType.FullName))
foreach (var job in _cache.Jobs.OrderBy(i => i.JobType.FullName))
{
writer.WriteLine(ConsoleFormat.DoubleLine);
writer.WriteLine($"Job:\t{job.JobType.FullName} [{job.JobType.Assembly.GetName().Name}]");
Expand All @@ -90,7 +100,7 @@ void WritePlainText(TextWriter writer, string clientVersion)

if (job.IsValid)
{
writer.WriteLine(verbose ? ConfigWriter.ToJson(cache.GetDefaultConfig(job)) : ConfigWriter.ToReducedJson(cache.GetDefaultConfig(job)));
writer.WriteLine(_verbose ? ConfigWriter.ToJson(_cache.GetDefaultConfig(job)) : ConfigWriter.ToReducedJson(_cache.GetDefaultConfig(job)));
writer.WriteLine();
}
else
Expand Down
Loading

0 comments on commit 81049a5

Please sign in to comment.