Skip to content

Commit

Permalink
Added ShutdownJob interface for methods executed after scheduler
Browse files Browse the repository at this point in the history
  • Loading branch information
Brunni committed Dec 16, 2020
1 parent 3bb2773 commit 448c5ab
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 1 deletion.
19 changes: 19 additions & 0 deletions AsyncScheduler/IShutdownJob.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Threading.Tasks;

namespace AsyncScheduler
{
/// <summary>
/// Interface which can be implemented by a Job,
/// so its shutdown method is executed, when scheduler is stopped by CancellationToken.
/// </summary>
public interface IShutdownJob : IJob
{

/// <summary>
/// Method is executed on shutdown of Scheduler/Job independent whether this job was started before or not.
/// It is allowed to throw an exception as they are caught.
/// </summary>
/// <returns>message for Log</returns>
Task<string> Shutdown();
}
}
49 changes: 49 additions & 0 deletions AsyncScheduler/Scheduler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,55 @@ public async Task Start(CancellationToken cancellationToken)
}
}
}

await ExecuteShutdownMethods();
}

private async Task ExecuteShutdownMethods()
{
var shutdownTasksWithErrorHandler = JobManager.Jobs
.Where(j => typeof(IShutdownJob).IsAssignableFrom(j.Value))
.Select(pair => new Tuple<string, Task<string>>(pair.Key, ExecuteShutdownJob(pair)))
.Select(t => t.Item2.ContinueWith(task => HandleShutdownFinished(t.Item1, task)));

await Task.WhenAll(shutdownTasksWithErrorHandler);
}

private async Task HandleShutdownFinished(string jobKey, Task<string> shutdownTask)
{
try
{
var result = await shutdownTask;
_logger.LogInformation("Shutdown job for {jobKey} finished with result: {result}", jobKey, result);
}
catch (Exception e)
{
_logger.LogWarning(e, "Unable to shutdown job {jobKey}", jobKey);
}
}

private async Task<string> ExecuteShutdownJob(KeyValuePair<string, Type> job)
{
using (_logger.BeginScope("job", job.Key))
{
var jobInstance = CreateJobInstance(job);
if (jobInstance == null)
{
_logger.LogWarning("Cannot create service! Skipping job {jobKey}", job.Key);
return "Job cannot be initialized";
}

try
{
var shutdown = ((IShutdownJob) jobInstance).Shutdown();
return await shutdown;
}
catch (Exception e)
{
_logger.LogWarning(e, "Unable to shutdown job {jobKey}", job.Key);
return "Shutdown of job not executed";
}
}
}

private void ExecuteQuickStarts(CancellationToken cancellationToken)
Expand Down
50 changes: 50 additions & 0 deletions AsyncSchedulerTest/ShutdownJobTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using AsyncScheduler;
using AsyncScheduler.JobStorage;
using AsyncScheduler.Schedules;
using AsyncSchedulerTest.TestData;
using AsyncSchedulerTest.TestUtils;
using FluentAssertions;
using Xunit;
using Xunit.Abstractions;

namespace AsyncSchedulerTest
{
public class ShutdownJobTest
{
private readonly Scheduler _scheduler;
private readonly ShutdownJob _shutdownJobInstancee;

public ShutdownJobTest(ITestOutputHelper testOutputHelper)
{
_shutdownJobInstancee = new ShutdownJob();
var serviceProvider = new TestActivator(null, _shutdownJobInstancee);
var jobManager = new JobManager(serviceProvider, new XUnitLogger<JobManager>(testOutputHelper),
new InMemoryStorage());
_scheduler = new Scheduler(serviceProvider, new XUnitLogger<Scheduler>(testOutputHelper),
new UtcSchedulerClock(), jobManager);
}

[Fact]
public async Task TestShutdown()
{
_scheduler.JobManager.AddJob<ShutdownJob, ScheduleNever>();
await RunScheduler(TimeSpan.FromSeconds(1));
_shutdownJobInstancee.ShutdownCount.Should().Be(1);
}

private async Task RunScheduler(TimeSpan schedulerTime)
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
var schedulerTask = _scheduler.Start(cancellationTokenSource.Token);
// ReSharper disable MethodSupportsCancellation
await Task.Delay(schedulerTime).ContinueWith((t) => cancellationTokenSource.Cancel());
var schedulerFinishTimeout = TimeSpan.FromSeconds(1);
await Task.WhenAny(schedulerTask, Task.Delay(schedulerFinishTimeout));
cancellationTokenSource.Cancel();
await schedulerTask;
}
}
}
22 changes: 22 additions & 0 deletions AsyncSchedulerTest/TestData/ShutdownJob.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Threading;
using System.Threading.Tasks;
using AsyncScheduler;

namespace AsyncSchedulerTest.TestData
{
public class ShutdownJob : IShutdownJob
{
public int ShutdownCount { get; private set; } = 0;

public async Task<object> Start(CancellationToken cancellationToken)
{
return await Task.FromResult("Done");
}

public Task<string> Shutdown()
{
ShutdownCount++;
return Task.FromResult("Shutdown executed");
}
}
}
9 changes: 8 additions & 1 deletion AsyncSchedulerTest/TestUtils/TestActivator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ namespace AsyncSchedulerTest.TestUtils
public class TestActivator : IServiceProvider
{
private readonly SimpleJob _simpleJobInstance;
private readonly ShutdownJob _shutdownJobInstance;

public TestActivator(SimpleJob simpleJobInstance = null)
public TestActivator(SimpleJob simpleJobInstance = null, ShutdownJob shutdownJobInstance = null)
{
_simpleJobInstance = simpleJobInstance;
_shutdownJobInstance = shutdownJobInstance;
}

public object GetService(Type serviceType)
Expand All @@ -25,6 +27,11 @@ public object GetService(Type serviceType)
{
return _simpleJobInstance;
}

if (serviceType == typeof(ShutdownJob) && _shutdownJobInstance != null)
{
return _shutdownJobInstance;
}
return Activator.CreateInstance(serviceType);
}
}
Expand Down

0 comments on commit 448c5ab

Please sign in to comment.