Skip to content

Commit

Permalink
Disable BackgroundJobService when only IRecurringJobs are registered (#…
Browse files Browse the repository at this point in the history
…121)

* adhering to analyzer suggestions

* Do not initiate job polling if only IRecurringJobs are registered
  • Loading branch information
NielsPilgaard authored Feb 27, 2024
1 parent 3b59e96 commit 6caa8a4
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 11 deletions.
21 changes: 17 additions & 4 deletions src/Pilgaard.BackgroundJobs/BackgroundJobService.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Diagnostics.Metrics;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Pilgaard.BackgroundJobs;

Expand Down Expand Up @@ -28,9 +29,10 @@ internal sealed class BackgroundJobService : IBackgroundJobService
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger<BackgroundJobService> _logger;
private readonly IBackgroundJobScheduler _backgroundJobScheduler;
private readonly BackgroundJobServiceOptions _backgroundJobServiceOptions;

private event Func<object, EventArgs, BackgroundJobRegistration, CancellationToken, Task>? RecurringJobTimerTriggered;
private static readonly List<IDisposable> _recurringJobTimers = new();
private static readonly List<IDisposable> _recurringJobTimers = [];

private static readonly Meter _meter = new(
name: typeof(BackgroundJobService).Assembly.GetName().Name!,
Expand All @@ -49,14 +51,17 @@ internal sealed class BackgroundJobService : IBackgroundJobService
/// <param name="scopeFactory">The factory used when constructing background jobs.</param>
/// <param name="logger">The logger.</param>
/// <param name="backgroundJobScheduler">The background job scheduler used to retrieve jobs when they should be run.</param>
/// <param name="backgroundJobServiceOptions">The options holding all <see cref="BackgroundJobRegistration"/>s, used to determine whether the <see cref="BackgroundJobService"/> should begin polling for occurrences or not.</param>
public BackgroundJobService(
IServiceScopeFactory scopeFactory,
ILogger<BackgroundJobService> logger,
IBackgroundJobScheduler backgroundJobScheduler)
IBackgroundJobScheduler backgroundJobScheduler,
IOptions<BackgroundJobServiceOptions> backgroundJobServiceOptions)
{
_scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_backgroundJobScheduler = backgroundJobScheduler ?? throw new ArgumentNullException(nameof(backgroundJobScheduler));
_backgroundJobServiceOptions = backgroundJobServiceOptions.Value;
}

/// <summary>
Expand All @@ -65,19 +70,27 @@ public BackgroundJobService(
/// <param name="cancellationToken">A <see cref="CancellationToken"/>
/// which can be used to cancel the background jobs.</param>
/// <returns>
/// A <see cref="Task"/> which will complete when all background jobs have been run, and there are no more occurrences of any of them.
/// A <see cref="Task"/> which will complete when all background jobs have been run, and there are no more occurrences of them.
/// </returns>
public async Task RunJobsAsync(CancellationToken cancellationToken = default)
{
ScheduleRecurringJobs(cancellationToken);

if (_backgroundJobServiceOptions
.Registrations
.Count(registration => registration.IsRecurringJob is false) is 0)
{
_logger.LogInformation("No {OneTimeJob} or {CronJob} have been registered.",
nameof(IOneTimeJob), nameof(ICronJob));
return;
}

while (!cancellationToken.IsCancellationRequested)
{
_logger.LogDebug("Scheduling background jobs.");

var backgroundJobsToRun = _backgroundJobScheduler
.GetBackgroundJobsAsync(cancellationToken)
.WithCancellation(cancellationToken)
.ConfigureAwait(false);

await foreach (var registration in backgroundJobsToRun)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public async Task return_background_jobs_when_they_should_be_run()

try
{
await foreach (var backgroundJob in backgroundJobs.WithCancellation(cts.Token))
await foreach (var backgroundJob in backgroundJobs)
{
var now = DateTime.UtcNow;
now.Second.Should().Be(startTime.AddSeconds(index++).Second);
Expand Down Expand Up @@ -116,7 +116,7 @@ public async Task be_able_to_return_all_types_of_background_job()
_testOutput.WriteLine($"[{backgroundJobType}]: {occurrence}");
}

distinctBackgroundJobs.Count.Should().Be(3);
distinctBackgroundJobs.Should().HaveCount(3);
}

[Fact]
Expand All @@ -131,7 +131,7 @@ public async Task throw_an_argument_exception_when_background_jobs_have_the_same
await using var serviceProvider = _services.BuildServiceProvider();

// Act && Assert
Assert.Throws<ArgumentException>(() => serviceProvider.GetRequiredService<IBackgroundJobScheduler>());
Assert.Throws<ArgumentException>(serviceProvider.GetRequiredService<IBackgroundJobScheduler>);
}

[Fact]
Expand Down
25 changes: 21 additions & 4 deletions tests/Pilgaard.BackgroundJobs.Tests/BackgroundJobServiceTests.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
using Cronos;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;

namespace Pilgaard.BackgroundJobs.Tests;
public class backgroundjobservice_should
{
private readonly ITestOutputHelper _testOutput;
private readonly IServiceCollection _services;

public backgroundjobservice_should(ITestOutputHelper testOutput)
public backgroundjobservice_should()
{
_testOutput = testOutput;
_services = new ServiceCollection().AddLogging();
}

Expand Down Expand Up @@ -53,4 +50,24 @@ public async Task run_background_jobs()
output3.Should().NotBeNull();
output4.Should().NotBeNull();
}

[Fact]
public async Task return_early_when_only_recurring_jobs_are_registered()
{
// Arrange
_services.AddBackgroundJobs()
.AddJob("FastRecurringJob", () => { }, TimeSpan.FromSeconds(1))
.AddJob("FastRecurringJobWithInitialDelay", () => { }, TimeSpan.FromSeconds(1), TimeSpan.Zero);

await using var serviceProvider = _services.BuildServiceProvider();

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));

var sut = serviceProvider.GetRequiredService<IBackgroundJobService>();

// Act
await sut.RunJobsAsync(cts.Token);

// The assertion is that RunJobsAsync does not keep running, because it returns early
}
}

0 comments on commit 6caa8a4

Please sign in to comment.