Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spread the new year cheer all over IRuntimeRegistry #151

Merged
merged 7 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: .NET

on:
push:
branches: [ main, v4 ]
branches: [ main ]
pull_request:
branches: [ main, v4 ]
branches: [ main ]

env:
VSTEST_CONNECTION_TIMEOUT: 180
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ All notable changes to **NCronJob** will be documented in this file. The project

## [Unreleased]

### Added

- Expose typed version of `DisableJob()`. Added in [#151](https://github.com/NCronJob-Dev/NCronJob/issues/151), by [@nulltoken](https://github.com/nulltoken).
- Expose typed version of `EnableJob()`. Added in [#151](https://github.com/NCronJob-Dev/NCronJob/issues/151), by [@nulltoken](https://github.com/nulltoken).

### Fixed

- Make `RemoveJob<TJob>()` and `RemoveJob(Type)` remove all jobs of the given type. Fixed in [#151](https://github.com/NCronJob-Dev/NCronJob/issues/151), by [@nulltoken](https://github.com/nulltoken).
- Ensure `UpdateSchedule()` behavior is consistent. Fixed in [#151](https://github.com/NCronJob-Dev/NCronJob/issues/151), by [@nulltoken](https://github.com/nulltoken).

## [v4.0.2] - 2024-12-28

New `v4` release with some new features and improvements. Check the [`v4` migration guide](https://docs.ncronjob.dev/migration/v4/) for more information.
Expand Down
30 changes: 28 additions & 2 deletions docs/advanced/dynamic-job-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ var found = registry.TryGetSchedule("MyName", out string? cronExpression, out Ti
The cron expression and time zone can be `null` even if the job was found. This indicates that the job has no schedule (like dependent jobs).

## Disabling and enabling jobs
To disable a job, use the `DisableJob` method:
There are two ways to disable a job from the scheduler. By name or by type.

To disable a job by name:

```csharp
app.MapPut("/disable-job", (IRuntimeJobRegistry registry) =>
Expand All @@ -107,9 +109,23 @@ app.MapPut("/disable-job", (IRuntimeJobRegistry registry) =>
});
```

That will prevent one job named `MyName` from being scheduled.

In contrast disabling by type will disable all jobs of the given type (so zero to many jobs):

```csharp
app.MapPut("/disable-job", (IRuntimeJobRegistry registry) =>
{
registry.DisableJob<SampleJob>(); // Alternatively DisableJob(typeof(SampleJob))
return TypedResults.Ok();
});
```

If a job is disabled, it will not be scheduled anymore. Any planned job will be cancelled and the job will be removed from the scheduler.

To enable a job, use the `EnableJob` method:
Of course, it's also possible to enable back previously disabled jobs.

To enable a job by name:

```csharp
app.MapPut("/enable-job", (IRuntimeJobRegistry registry) =>
Expand All @@ -118,3 +134,13 @@ app.MapPut("/enable-job", (IRuntimeJobRegistry registry) =>
return TypedResults.Ok();
});
```

And similarly, to enable all jobs of the given type (so zero to many jobs):

```csharp
app.MapPut("/enable-job", (IRuntimeJobRegistry registry) =>
{
registry.EnableJob<SampleJob>(); // Alternatively EnableJob(typeof(SampleJob))
return TypedResults.Ok();
});
```
2 changes: 1 addition & 1 deletion src/NCronJob/Registry/IInstantJobRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ private void RunJob<TJob>(DateTimeOffset startDate, object? parameter = null, bo
{
using (logger.BeginScope("Triggering RunScheduledJob:"))
{
var jobDefinition = jobRegistry.FindJobDefinition(typeof(TJob));
var jobDefinition = jobRegistry.FindFirstJobDefinition(typeof(TJob));

if (jobDefinition is null)
{
Expand Down
15 changes: 13 additions & 2 deletions src/NCronJob/Registry/JobRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ private readonly Dictionary<JobDefinition, List<DependentJobRegistryEntry>> depe

public IReadOnlyCollection<JobDefinition> GetAllOneTimeJobs() => allJobs.Where(c => c.IsStartupJob).ToList();

public JobDefinition? FindJobDefinition(Type type)
public IReadOnlyCollection<JobDefinition> FindAllJobDefinition(Type type)
=> allJobs.Where(j => j.Type == type).ToList();

public JobDefinition? FindFirstJobDefinition(Type type)
nulltoken marked this conversation as resolved.
Show resolved Hide resolved
=> allJobs.FirstOrDefault(j => j.Type == type);

public JobDefinition? FindJobDefinition(string jobName)
Expand All @@ -43,7 +46,15 @@ public int GetJobTypeConcurrencyLimit(string jobTypeName)

public void RemoveByName(string jobName) => Remove(allJobs.FirstOrDefault(j => j.CustomName == jobName));

public void RemoveByType(Type type) => Remove(allJobs.FirstOrDefault(j => j.Type == type));
public void RemoveByType(Type type)
{
var jobDefinitions = FindAllJobDefinition(type);

foreach (var jobDefinition in jobDefinitions)
{
Remove(jobDefinition);
}
}

public JobDefinition AddDynamicJob(
Delegate jobDelegate,
Expand Down
115 changes: 103 additions & 12 deletions src/NCronJob/Registry/RuntimeJobRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ public interface IRuntimeJobRegistry
/// <summary>
/// Removes all jobs of the given type.
/// </summary>
/// <remarks>If the given job is not found, no exception is thrown.</remarks>
void RemoveJob<TJob>() where TJob : IJob;

/// <summary>
/// Removes all jobs of the given type.
/// </summary>
/// <remarks>If the given job is not found, no exception is thrown.</remarks>
void RemoveJob(Type type);

/// <summary>
Expand Down Expand Up @@ -80,7 +82,7 @@ public interface IRuntimeJobRegistry
IReadOnlyCollection<RecurringJobSchedule> GetAllRecurringJobs();

/// <summary>
/// This will enable a job that was previously disabled.
/// Enables a job that was previously disabled.
/// </summary>
/// <param name="jobName">The unique job name that identifies this job.</param>
/// <remarks>
Expand All @@ -90,14 +92,50 @@ public interface IRuntimeJobRegistry
void EnableJob(string jobName);

/// <summary>
/// This will disable a job that was previously enabled.
/// Enables all jobs of the given type that were previously disabled.
/// </summary>
/// <remarks>
/// If the job is already enabled, this method does nothing.
/// If the job is not found, an exception is thrown.
/// </remarks>
void EnableJob<TJob>() where TJob : IJob;

/// <summary>
/// Enables all jobs of the given type that were previously disabled.
/// </summary>
/// <remarks>
/// If the job is already enabled, this method does nothing.
/// If the job is not found, an exception is thrown.
/// </remarks>
void EnableJob(Type type);

/// <summary>
/// Disables a job that was previously enabled.
/// </summary>
/// <param name="jobName">The unique job name that identifies this job.</param>
/// <remarks>
/// If the job is already disabled, this method does nothing.
/// If the job is not found, an exception is thrown.
/// </remarks>
void DisableJob(string jobName);

/// <summary>
/// Disables all jobs of the given type.
/// </summary>
/// <remarks>
/// If the job is already disabled, this method does nothing.
/// If the job is not found, an exception is thrown.
/// </remarks>
void DisableJob<TJob>() where TJob : IJob;

/// <summary>
/// Disables all jobs of the given type.
/// </summary>
/// <remarks>
/// If the job is already disabled, this method does nothing.
/// If the job is not found, an exception is thrown.
/// </remarks>
void DisableJob(Type type);
}

/// <summary>
Expand All @@ -111,6 +149,9 @@ public sealed record RecurringJobSchedule(string? JobName, string CronExpression
/// <inheritdoc />
internal sealed class RuntimeJobRegistry : IRuntimeJobRegistry
{
// https://crontab.guru/#*_*_31_2_*
internal static readonly CronExpression TheThirtyFirstOfFebruary = CronExpression.Parse("* * 31 2 *");

private readonly IServiceCollection services;
private readonly JobRegistry jobRegistry;
private readonly JobWorker jobWorker;
Expand Down Expand Up @@ -185,6 +226,7 @@ public void UpdateSchedule(string jobName, string cronExpression, TimeZoneInfo?
var cron = NCronJobOptionBuilder.GetCronExpression(cronExpression);

job.CronExpression = cron;
job.UserDefinedCronExpression = cronExpression;
job.TimeZone = timeZoneInfo ?? TimeZoneInfo.Utc;

jobWorker.RescheduleJobWithJobName(job);
Expand Down Expand Up @@ -235,12 +277,16 @@ public void EnableJob(string jobName)
var job = jobRegistry.FindJobDefinition(jobName)
?? throw new InvalidOperationException($"Job with name '{jobName}' not found.");

if (job.CronExpression is not null)
{
job.CronExpression = CronExpression.Parse(job.UserDefinedCronExpression);
}
EnableJob(job, jobName);
}

jobWorker.RescheduleJobWithJobName(job);
/// <inheritdoc />
public void EnableJob<TJob>() where TJob : IJob => EnableJob(typeof(TJob));

/// <inheritdoc />
public void EnableJob(Type type)
{
ProcessAllJobDefinitionsOfType(type, j => EnableJob(j));
}

/// <inheritdoc />
Expand All @@ -249,13 +295,58 @@ public void DisableJob(string jobName)
var job = jobRegistry.FindJobDefinition(jobName)
?? throw new InvalidOperationException($"Job with name '{jobName}' not found.");

if (job.CronExpression is not null)
DisableJob(job, jobName);
}

/// <inheritdoc />
public void DisableJob<TJob>() where TJob : IJob => DisableJob(typeof(TJob));

/// <inheritdoc />
public void DisableJob(Type type)
{
ProcessAllJobDefinitionsOfType(type, j=> DisableJob(j));
}

private void ProcessAllJobDefinitionsOfType(Type type, Action<JobDefinition> processor)
{
var jobDefinitions = jobRegistry.FindAllJobDefinition(type);

foreach (var jobDefinition in jobDefinitions)
{
// https://crontab.guru/#*_*_31_2_*
// Scheduling on Feb, 31st is a sure way to never get it to run
job.CronExpression = CronExpression.Parse("* * 31 2 *");
processor(jobDefinition);
}
}

jobWorker.RescheduleJobWithJobName(job);
private void EnableJob(JobDefinition job, string? customName = null)
{
if (job.UserDefinedCronExpression is not null)
{
job.CronExpression = CronExpression.Parse(job.UserDefinedCronExpression);
}
else
{
job.CronExpression = null;
}

RescheduleJob(job, customName);
}

private void DisableJob(JobDefinition job, string? customName = null)
{
// Scheduling on Feb, 31st is a sure way to never get it to run
job.CronExpression = TheThirtyFirstOfFebruary;

RescheduleJob(job, customName);
}

private void RescheduleJob(JobDefinition job, string? customName = null)
{
if (customName is not null)
{
jobWorker.RescheduleJobWithJobName(job);
return;
}

jobWorker.RescheduleJobByType(job);
}
}
11 changes: 10 additions & 1 deletion src/NCronJob/Scheduler/JobWorker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ public void RemoveJobByName(string jobName)

public void RemoveJobType(Type type)
{
var fullName = registry.FindJobDefinition(type)?.JobFullName;
var fullName = registry.FindFirstJobDefinition(type)?.JobFullName;
if (fullName is null)
{
return;
Expand All @@ -223,6 +223,15 @@ public void RescheduleJobWithJobName(JobDefinition jobDefinition)
jobQueueManager.SignalJobQueue(jobDefinition.JobFullName);
}

public void RescheduleJobByType(JobDefinition jobDefinition)
{
ArgumentNullException.ThrowIfNull(jobDefinition);

jobQueueManager.RemoveQueue(jobDefinition.JobFullName);
ScheduleJob(jobDefinition);
jobQueueManager.SignalJobQueue(jobDefinition.JobFullName);
}

private void RemoveJobRunByName(string jobName)
{
var jobType = registry.FindJobDefinition(jobName);
Expand Down
2 changes: 1 addition & 1 deletion tests/NCronJob.Tests/NCronJobIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public async Task ExecuteAnInstantJobWithoutPreviousRegistration()
jobFinished.ShouldBeTrue();

var jobRegistry = provider.GetRequiredService<JobRegistry>();
jobRegistry.FindJobDefinition(typeof(SimpleJob)).ShouldBeNull();
jobRegistry.FindFirstJobDefinition(typeof(SimpleJob)).ShouldBeNull();
}

[Fact]
Expand Down
Loading
Loading