Skip to main content

IRecurringJob Interface

A no-payload job that runs on a cron schedule. Use this for periodic maintenance tasks like cache cleanup, report generation, or health checks.

Namespace: Zeridion.Flare · Assembly: Zeridion.Flare.dll

public interface IRecurringJob
{
Task ExecuteAsync(JobContext context);
}

ExecuteAsync

Task ExecuteAsync(JobContext context);
ParameterTypeDescription
contextJobContextExecution context with job metadata, cancellation, and logging.

The contract is the same as IJob<T>.ExecuteAsync — completion means success, exceptions mean failure, and the CancellationToken on the context should be passed to all async operations.

The key difference from IJob<T> is that recurring jobs have no payload. All configuration comes from the class itself and its attributes.

Cron schedule

Every IRecurringJob must have a [JobConfig] attribute with a CronSchedule property. The schedule uses standard 5-field cron format (parsed by the Cronos library):

┌───────────── minute (0–59)
│ ┌───────────── hour (0–23)
│ │ ┌───────────── day of month (1–31)
│ │ │ ┌───────────── month (1–12)
│ │ │ │ ┌───────────── day of week (0–6, Sun=0)
│ │ │ │ │
* * * * *

Common patterns:

ExpressionMeaning
* * * * *Every minute
*/15 * * * *Every 15 minutes
0 * * * *Every hour, on the hour
0 3 * * *Daily at 3:00 AM UTC
0 0 * * 1Every Monday at midnight UTC
0 0 1 * *First day of every month at midnight

Auto-registration

When the worker starts, the SDK scans assemblies for IRecurringJob implementations and sends their schedules to the Zeridion Flare API as part of POST /v1/workers/register (in the recurring_schedules array). The schedule ID is derived from the fully-qualified type name.

You do not need to call any registration method manually — AddZeridionFlare handles discovery, and the FlareWorkerService sends the registrations on startup.

Missed run handling

If the CronScheduler evaluates a recurring job and finds that one or more scheduled runs were missed (for example, the scheduler was offline), it creates a single catch-up job for the most recent missed run. It does not backfill every missed interval.

Dependency injection

Recurring job classes are registered in the DI container as their concrete type (transient). Constructor injection works normally:

[JobConfig(CronSchedule = "0 3 * * *", TimeoutSeconds = 300)]
public class CleanupExpiredSessions : IRecurringJob
{
private readonly ISessionStore _sessions;

public CleanupExpiredSessions(ISessionStore sessions) => _sessions = sessions;

public async Task ExecuteAsync(JobContext ctx)
{
var removed = await _sessions.RemoveExpiredAsync(ctx.CancellationToken);
ctx.Logger.LogInformation("Removed {Count} expired sessions", removed);
}
}

Usage example

A recurring job that generates a daily summary report:

[JobConfig(CronSchedule = "0 6 * * *", Queue = "reports", MaxAttempts = 3)]
public class DailySummaryReport : IRecurringJob
{
private readonly IReportService _reports;
private readonly IEmailService _email;

public DailySummaryReport(IReportService reports, IEmailService email)
{
_reports = reports;
_email = email;
}

public async Task ExecuteAsync(JobContext ctx)
{
ctx.Logger.LogInformation("Generating daily summary for {Date}",
DateTimeOffset.UtcNow.Date);

var report = await _reports.GenerateDailySummaryAsync(ctx.CancellationToken);
await _email.SendReportAsync("team@example.com", report, ctx.CancellationToken);
}
}

See also