Skip to content

Update dependency Quartz.AspNetCore to 3.18.0#1742

Open
renovate[bot] wants to merge 1 commit intomainfrom
renovate/quartznet-monorepo
Open

Update dependency Quartz.AspNetCore to 3.18.0#1742
renovate[bot] wants to merge 1 commit intomainfrom
renovate/quartznet-monorepo

Conversation

@renovate
Copy link
Copy Markdown
Contributor

@renovate renovate bot commented Mar 1, 2026

This PR contains the following updates:

Package Change Age Confidence
Quartz.AspNetCore (source) 3.15.13.18.0 age confidence

Release Notes

quartznet/quartznet (Quartz.AspNetCore)

v3.18.0

Quartz.NET 3.18.0 is a major feature release with 8 new capabilities, performance improvements, and important bug fixes.

New Features
RFC 5545 RRULE recurrence trigger

Quartz.NET now supports scheduling with iCalendar recurrence rules (RFC 5545 RRULE), enabling complex patterns that cannot be expressed with cron expressions — such as "2nd Monday of every month" or "last weekday of March each year."

A custom lightweight RRULE engine (~1.5K LOC) handles all frequencies (YEARLY through SECONDLY), all BY* rules (BYDAY, BYMONTHDAY, BYSETPOS, etc.), COUNT, UNTIL, INTERVAL, and WKST. No new external dependencies. No database schema changes — uses the existing SIMPROP_TRIGGERS table.

ITrigger trigger = TriggerBuilder.Create()
    .WithIdentity("myTrigger")
    .WithRecurrenceSchedule("FREQ=MONTHLY;BYDAY=2MO", b => b
        .InTimeZone(TimeZoneInfo.FindSystemTimeZoneById("America/New_York")))
    .StartNow()
    .Build();
RRULE Pattern
FREQ=MONTHLY;BYDAY=2MO Every 2nd Monday of the month
FREQ=WEEKLY;INTERVAL=2;BYDAY=MO,WE,FR Every other week on Mon/Wed/Fri
FREQ=YEARLY;BYMONTH=3;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1 Last weekday of March each year
FREQ=MONTHLY;BYMONTHDAY=-1 Last day of every month
FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR Every weekday

(#​2990) — Closes #​1259

Execution groups for per-node thread limits

Tag triggers with an execution group to limit how many threads a category of jobs can consume concurrently on each node. This prevents resource-intensive jobs from starving lightweight work.

services.AddQuartz(q =>
{
    q.UseExecutionLimits(limits =>
    {
        limits.ForGroup("batch-jobs", maxConcurrent: 2);
        limits.ForDefaultGroup(maxConcurrent: 10);
        limits.ForOtherGroups(maxConcurrent: 5);
    });

    q.AddTrigger(t => t
        .ForJob("heavyJob")
        .WithExecutionGroup("batch-jobs")
        .WithCronSchedule("0 0/5 * * * ?"));
});

Also configurable via properties (quartz.executionLimit.batch-jobs = 2) and runtime API (scheduler.SetExecutionLimits(...)). Includes optional EXECUTION_GROUP column for ADO.NET job stores with graceful fallback when absent, and Dashboard integration.

(#​3004) — Closes #​1175, #​830

Multiple named schedulers in Microsoft DI

Register multiple independent scheduler instances in a single DI container. Each named scheduler gets isolated options, jobs, triggers, listeners, and calendars.

services.AddQuartz("Scheduler1", q =>
{
    q.AddJob<EmailJob>(j => j.WithIdentity("email"));
    q.AddTrigger(t => t.ForJob("email").WithCronSchedule("0 0/5 * * * ?"));
});

services.AddQuartz("Scheduler2", q =>
{
    q.UsePersistentStore(s => { /* ... */ });
    q.AddJob<ReportJob>(j => j.WithIdentity("report"));
    q.AddTrigger(t => t.ForJob("report").WithCronSchedule("0 0 * * * ?"));
});

services.AddQuartzHostedService(o => o.WaitForJobsToComplete = true);

AddQuartzHostedService() automatically manages the lifecycle of all registered schedulers.

(#​3000) — Closes #​2109

JSON configuration and scheduling data

Configure Quartz.NET using hierarchical JSON in appsettings.json instead of flat property keys. Supports declarative job and trigger definitions, all 4 trigger types, and named schedulers via a Schedulers section.

{
  "Quartz": {
    "Scheduler": { "InstanceName": "My Scheduler" },
    "ThreadPool": { "MaxConcurrency": 10 },
    "Schedule": {
      "Jobs": [{ "Name": "myJob", "JobType": "MyApp.Jobs.MyJob, MyApp", "Durable": true }],
      "Triggers": [{ "Name": "myTrigger", "JobName": "myJob", "Cron": { "Expression": "0/30 * * * * ?" } }]
    }
  }
}
services.AddQuartz(Configuration.GetSection("Quartz"));

Also includes a standalone JsonSchedulingDataProcessorPlugin for quartz_jobs.json file support with hot-reload, mirroring XMLSchedulingDataProcessorPlugin.

(#​3012, #​3015, #​3017) — Closes #​1755

Redis-based distributed lock handler (new Quartz.Redis package)

New Quartz.Redis NuGet package providing RedisSemaphore — an ISemaphore implementation using Redis SET NX PX distributed locks instead of database row locks. This eliminates DB row lock contention and deadlocks in clustered setups while keeping job/trigger data in the relational database.

Uses two-tier locking: local SemaphoreSlim prevents redundant Redis round-trips within the same process, and a Lua script ensures atomic check-and-delete on release for safety.

services.AddQuartz(q =>
{
    q.UsePersistentStore(store =>
    {
        store.UseRedisLockHandler(redis =>
        {
            redis.RedisConfiguration = "redis-server:6379";
        });
    });
});

(#​2999) — Closes #​1625

Activity tracing for ADO.NET job store operations

28 IJobStore methods are now wrapped with System.Diagnostics.Activity spans, so database calls appear as children of named Quartz operations (e.g., Quartz.JobStore.AcquireNextTriggers) instead of orphaned root spans in your tracing system. Zero overhead when tracing is disabled.

[Quartz.JobStore.AcquireNextTriggers]
  └── [SELECT ... FROM TRIGGERS]
  └── [INSERT ... INTO FIRED_TRIGGERS]
[Quartz.JobStore.TriggersFired]
  └── [UPDATE ... TRIGGERS SET STATE=...]
[Quartz.Job.Execute]
  └── [user job work]

All new operations are automatically included in QuartzInstrumentationOptions.DefaultTracedOperations.

(#​3001) — Closes #​2721

UpdateTriggerDetails — update trigger metadata without rescheduling

New UpdateTriggerDetails method updates Description, Priority, JobDataMap, CalendarName, and MisfireInstruction on an existing trigger without resetting fire times, trigger state, or misfire context. Available via IScheduler.UpdateTriggerDetails() extension method.

(#​2988) — Closes #​844

Factory-based AddQuartz() with IServiceProvider access

New AddQuartz overloads accepting Action<IServiceCollectionQuartzConfigurator, IServiceProvider> allow resolving DI services during Quartz configuration — useful for obtaining connection strings, feature flags, or other configuration from DI-registered services.

services.AddQuartz((q, sp) =>
{
    var config = sp.GetRequiredService<DatabaseConfig>();
    q.UsePersistentStore(s =>
    {
        s.UseSqlServer(sql => sql.ConnectionString = config.ConnectionString);
    });
});

(#​3007) — Closes #​1617

Performance
Reduced DB round-trips in misfire recovery

Misfire recovery now uses a targeted UPDATE instead of routing each trigger through the full StoreTrigger path, reducing per-trigger DB round-trips from 7-12 down to 1-2 (~87% reduction). For a batch of 20 cron triggers, this drops from ~150 queries to ~20 queries — all under LockTriggerAccess. Calendar lookups are also cached across the batch.

(#​2993) — Closes #​758

Bug Fixes
  • Fix scheduler signal loss causing triggers stuck in WAITING stateSemaphoreSlim(0, 1) in QuartzSchedulerThread could silently drop scheduling signals via SemaphoreFullException, causing triggers to stay stuck in WAITING state until the next idle loop timeout. (#​3033) — Fixes #​3028
  • Fix SchedulerRepository preventing connections to multiple cluster nodesSchedulerRepository indexed by scheduler name only, preventing multiple remote proxies to different nodes in the same cluster from coexisting. Now supports instance-aware lookup. (#​2991) — Fixes #​388
  • Implement IsJobGroupPaused/IsTriggerGroupPaused in ADO.NET job store — These methods previously threw NotImplementedException in the persistent job store. (#​3030)
  • Fix Dashboard Live not working over HTTP — Dashboard live updates now work correctly over plain HTTP connections. (#​3032)
  • Fix dashboard plugins using static ServiceProvider — Dashboard plugins no longer rely on a static ServiceProvider reference, fixing issues with multiple host instances. (#​3035) — Fixes #​3026
Deprecations
  • DirtyFlagMap.Get() — use the indexer (map[key]) instead (#​2986)
  • DirtyFlagMap.Put() / PutAll() — use the indexer or collection initializer instead (#​2989)
What's Changed

Full Changelog: quartznet/quartznet@v3.17.1...v3.18.0

v3.17.1

Highlights

Jenkins-style H (hash) token for cron expressions

Quartz.NET now supports the H (hash) token in cron expressions, inspired by Jenkins. The H token resolves to a deterministic value based on the trigger's identity, spreading job execution times to avoid the thundering herd problem when many triggers share the same schedule.

Supported forms: H, H(min-max), H/step, H(min-max)/step

// Runs at a consistent but spread-out minute each hour
trigger = TriggerBuilder.Create()
    .WithIdentity("myTrigger", "myGroup")
    .WithCronSchedule("0 H * * * ?")
    .Build();

// Runs every 15 minutes, starting at a hash-determined offset
trigger = TriggerBuilder.Create()
    .WithIdentity("myTrigger", "myGroup")
    .WithCronSchedule("0 H/15 * * * ?")
    .Build();

The hash seed is automatically derived from the trigger's identity (name + group) when using TriggerBuilder, or can be provided explicitly. See the cron trigger documentation for full details.

Structured logging history plugins

New StructuredLoggingJobHistoryPlugin and StructuredLoggingTriggerHistoryPlugin provide first-class support for structured logging frameworks like Serilog and NLog. Unlike the existing logging plugins that use index-based format placeholders ({0}, {1}), these use named MEL-style message template parameters ({JobName}, {TriggerGroup}, etc.), making log output queryable in structured logging sinks and avoiding template cache memory leaks.

quartz.plugin.jobHistory.type = Quartz.Plugin.History.StructuredLoggingJobHistoryPlugin, Quartz.Plugins
quartz.plugin.triggerHistory.type = Quartz.Plugin.History.StructuredLoggingTriggerHistoryPlugin, Quartz.Plugins

Message templates are fully configurable via standard Quartz property configuration.

Bug Fixes

  • Fix triggers getting permanently stuck in ACQUIRED state — Triggers could become permanently stuck if the scheduler crashed or was killed between acquiring a trigger and firing it. (#​2980)
  • Fix StoreCalendar overriding paused trigger state — Storing a calendar with updateTriggers: true no longer resets paused triggers back to normal state. (#​2968)
  • Fix DoCheckin not retrying transient database errors — Cluster checkin now properly retries on transient database failures instead of silently failing. (#​2970)
  • Fix Dashboard /_blazor endpoint conflict — The Quartz Dashboard no longer conflicts with host applications that also use Blazor. (#​2975)
  • Fix spurious ERROR log on zombie transaction rollback — Rolling back a zombie transaction no longer logs a misleading ERROR. (#​2978)

Full Changelog: quartznet/quartznet@v3.17.0...v3.17.1

v3.17.0

This is a major bug-fix release with 40+ fixes spanning trigger state management, clustering reliability, DST handling, misfire accuracy, and scheduler lifecycle. An optional database schema migration improves misfire handling.

Highlights
Optional database migration: MISFIRE_ORIG_FIRE_TIME column

A new optional column MISFIRE_ORIG_FIRE_TIME on QRTZ_TRIGGERS enables correct ScheduledFireTimeUtc for misfired triggers when using "fire now" misfire policies. Without it, ScheduledFireTimeUtc equals FireTimeUtc for misfired triggers (the pre-existing behavior). RAMJobStore does not require this migration.

Migration script: database/schema_30_add_misfire_orig_fire_time.sql (covers SQL Server, PostgreSQL, MySQL, SQLite, Oracle, Firebird)

Clustering and concurrency fixes
  • Fix DisallowConcurrentExecution jobs running simultaneously in cluster (#​2697)
  • Fix false cluster recovery causing DisallowConcurrentExecution violation (#​2915)
  • Fix triggers stuck in BLOCKED state for DisallowConcurrentExecution jobs (#​2822)
  • Fix FIRED_TRIGGERS not cleaned up on job deletion mid-execution (#​1696)
  • Handle transient database exceptions (deadlocks) with automatic retry (#​2883, #​2952)
  • Fix PostgreSQL transaction abort error on lock contention (#​2884)
  • Fix SQLite "database is locked" errors with dedicated semaphore (#​2323)
  • Add MySQL FORCE INDEX hints for slow trigger acquisition queries (#​547)
Trigger state and fire time accuracy
  • Fix GetTriggerState returning Complete instead of Blocked for executing triggers (#​2255)
  • Fix RAMJobStore.TriggersFired skipping triggers causing wrong trigger/job execution (#​1386)
  • Fix ScheduledFireTimeUtc returning wrong value after misfire (#​2899)
  • Fix PreviousFireTimeUtc reset to null on restart with OverWriteExistingData=true (#​1834)
  • Fix SimpleTrigger first fire time wrong when created long before scheduling (#​2455)
  • Fix CronTrigger double-firing when rescheduled with old StartTimeUtc (#​2909)
  • Fix recovery trigger missing original JobData (#​2083)
  • Fix misfired blocked triggers not being deleted from database (#​1646)
  • Fix DoNothing misfire policy skipping fire times within threshold (#​2912)
DST and time handling
  • Fix DailyTimeIntervalTrigger extra fire during DST fall-back (#​2917)
  • Fix DailyTimeIntervalTrigger mutating StartTimeUtc during fire time computation (#​2906)
  • Fix DailyTimeIntervalTrigger RepeatCount to apply per day (#​1633)
  • Fix scheduler thread stuck after system clock jumps backward (#​1508)
  • Fix ComputeFireTimesBetween modifying trigger StartTimeUtc (#​2722)
Scheduler lifecycle and threading
  • Fix scheduler hang on shutdown: replace Monitor.Wait with async SemaphoreSlim (#​2877)
  • Fix shutdown deadlock (#​2830)
  • Release acquired triggers on shutdown instead of leaking them (#​2881)
  • Fix DedicatedThreadPool threads leaking on scheduler shutdown (#​1357)
  • Use dedicated thread for scheduler loop to prevent missed triggers under high CPU (#​781)
Job execution
  • Fix RefireImmediately with JobChainingJobListener firing chain prematurely (#​663)
  • Fix AsyncLocal flow from IJobFactory.NewJob to IJob.Execute (#​1528)
  • Add IJobDetail property to JobExecutionException (#​1442)
API and configuration
  • Fix idleWaitTime of zero silently ignored instead of throwing (#​1394)
  • Fix AddJob/AddTrigger ambiguous references without removing overloads (#​2795)
  • Fix PauseJobs/ResumeJob interaction bug in RAMJobStore (#​761)
  • Fix RemoteScheduler ignoring local quartz.scheduler.instanceName (#​313)
  • Restore DB trigger fields for custom (blob) triggers (#​2949)
Dashboard
  • Fix dashboard CSS 404 in API-only projects (#​2886)

What's Changed

Full Changelog: quartznet/quartznet@v3.16.1...v3.17.0

v3.16.1: Quartz.NET 3.16.1

This release hopefully fixes the hiccup we had with the versioning/packaging of the new Dashboard package.

What's Changed
  • Add missing packaging and publish for Quartz.Dashboard by @​lahma in #​2853
  • Fix CronTrigger incorrectly scheduling when end date is in the past by @​jafin in #​2856

Full Changelog: quartznet/quartznet@v3.16.0...v3.16.1

v3.16.0: Quartz.NET 3.16.0

On top of great community fixes, this release also brings the experimental Dashboard into action. You can enable it on NET 8 and later, see documentation for details.

What's Changed

  • Add tests for AddJob, AddTrigger, ScheduleJob and AddCalendar by @​bdovaz in #​2794
  • Use NET 10 SDK and update projects to use NET 10 by @​lahma in #​2800
  • Handle ObjectDisposedException when canceling jobs by @​lahma in #​2811
  • Fix trigger ERROR state during shutdown - release acquired triggers gracefully by @​lahma in #​2812
  • fix (CronExpression): Throw Exception for invalid cron expressions by @​jafin in #​2836
  • Convert solution file to slnx format by @​lahma in #​2840
  • Fix XML scheduling plugin to log errors and notify listeners on parsing failures by @​jafin in #​2839
  • Add IdleWaitTime property to SchedulerBuilder (#​2835) by @​lahma in #​2842
  • Fix CronExpression not always returning correct next schedule for weekdayonly and month flags by @​jafin in #​2838
  • fix(CalendarIntervalTriggerImpl): infinite loop bug in CalendarIntervalTriggerImpl by @​jafin in #​2837
  • Fix GetScheduler(name) returning null before default scheduler initialization by @​jafin in #​2845
  • Port DirectoryScanJob listener DI support to 3.x by @​lahma in #​2851
  • Backport Quartz.Dashboard to 3.x (in-process only) by @​lahma in #​2852

Full Changelog: quartznet/quartznet@v3.15.1...v3.16.0


Configuration

📅 Schedule: (in timezone Asia/Shanghai)

  • Branch creation
    • "before 5:00am,before 10am,before 3pm,before 8pm"
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate bot force-pushed the renovate/quartznet-monorepo branch from 8255ff6 to 22cf21b Compare March 4, 2026 22:11
@renovate renovate bot changed the title Update dependency Quartz.AspNetCore to 3.16.0 Update dependency Quartz.AspNetCore to 3.16.1 Mar 4, 2026
@renovate renovate bot force-pushed the renovate/quartznet-monorepo branch from 22cf21b to eeb6835 Compare March 29, 2026 21:20
@renovate renovate bot changed the title Update dependency Quartz.AspNetCore to 3.16.1 Update dependency Quartz.AspNetCore to 3.17.0 Mar 29, 2026
@renovate renovate bot changed the title Update dependency Quartz.AspNetCore to 3.17.0 Update dependency Quartz.AspNetCore to 3.17.1 Apr 3, 2026
@renovate renovate bot force-pushed the renovate/quartznet-monorepo branch from eeb6835 to 37f2743 Compare April 3, 2026 09:05
@renovate renovate bot force-pushed the renovate/quartznet-monorepo branch from 37f2743 to 996e34a Compare April 11, 2026 09:23
@renovate renovate bot changed the title Update dependency Quartz.AspNetCore to 3.17.1 Update dependency Quartz.AspNetCore to 3.18.0 Apr 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants