Skip to content

[deps] Platform: Update quartznet monorepo to v4 - #8735

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/major-quartznet-monorepo
Open

renovate[bot] wants to merge 1 commit into
mainfrom
renovate/major-quartznet-monorepo

Conversation

@renovate

@renovate renovate Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence
Quartz (source) [3.15.1] → [4.6.2] age confidence
Quartz.Extensions.DependencyInjection (source) [3.15.1] → [4.6.2] age confidence
Quartz.Extensions.Hosting (source) [3.15.1] → [4.6.2] age confidence

Release Notes

quartznet/quartznet (Quartz)

v4.6.2

One fix for the 4.6 line. There is no public API change, no schema change and no dependency change.

Fixed
  • Legacy time zone names resolve on a system without tzdata-legacy, such as Ubuntu 26.04. Without the TimeZoneConverter plugin, seven Windows ids (FLE, India, Argentina, Greenland, Myanmar, Nepal and US Eastern Standard Time) and tzdata's legacy IANA names, such as Asia/Calcutta and Europe/Kiev, threw TimeZoneNotFoundException there. For those seven Windows ids, .NET maps through CLDR to the legacy names, which only tzdata-legacy installs. Quartz now maps a legacy name to its canonical zone through tzdata 2026e's backward links, and resolves it under the id asked for. The id a store writes to TIME_ZONE_ID therefore does not depend on whether the host has tzdata-legacy. (#​4180, from #​4172)
Behaviour change
  • With UseTimeZoneConverter on such a host, 4.6.1 stored the canonical name (TimeZoneConverter 7.1.0 answers Europe/Kyiv for FLE Standard Time). 4.6.2 resolves the id before the plugin is asked, so it stores the id asked for. Rows already stored read back either way.

The same fix ships for 3.x in 3.22.9.

What's Changed
  • TimeZones.FindById resolves the tzdata legacy names, and the Windows ids CLDR maps to them, on a system without tzdata-legacy by @​lahma in #​4180
  • 4.6.2: legacy time zone names resolve without tzdata-legacy by @​lahma in #​4184

Full Changelog: quartznet/quartznet@v4.6.1...v4.6.2

v4.6.1

Fixes from 4.7's development for the 4.6 line. There is no public API change and no schema change. Quartz.Plugins.TimeZoneConverter now requires TimeZoneConverter 7.1.0; no other package's dependencies change.

Fixed
  • A start no longer resets a declared trigger's run state. Creating a scheduler re-applies every AddTrigger and ScheduleJob registration and every scheduling file through RescheduleJob. That reset TimesTriggered, so a finite WithRepeatCount or a recurrence rule's COUNT ran again from the beginning. It also dropped a retry pending at shutdown, and resumed a trigger an operator had paused on its own. While the declared schedule is unchanged, the count and the retry are now kept, and so is the pause, with its record. A changed schedule starts fresh, and an application's own RescheduleJob still ends an individual pause. (#​4147, from #​4128)
  • A [DisallowConcurrentExecution] job's completion reads only the triggers that misfired. Each completion read every waiting trigger of the job in full under TRIGGER_ACCESS to find the late ones, so draining a backlog read N rows on each of N completions, and scheduling calls waited behind the lock. A shipped delegate now reads only the job's misfired triggers, with the misfire handler's own predicate. In the #​4167 workload on SQL Server (400 queued triggers on one serial job, 50 ms body), the drain went from 138 s with 137 misfire events to 101 s with 37. (#​4169, from #​4167)
  • A declared job-data type with a number, a boolean or a nested object reads back. A value of a type declared through AddTypeInfoResolver was stored, and then its job failed to load with Failed to parse JobDataMap from json. It now reads back as a Dictionary<string, string> of its members: a number or a boolean as its JSON text ("3", "true"), a nested object as its JSON, a null as null. A map that 4.6.0 stored loads. (#​4161, from #​4156)
  • A recovered firing of an ungrouped trigger is limited under its trigger group. Under UseTriggerGroupWhenUnset, a firing replayed after cluster recovery, a shutdown hand-back or startup recovery was limited under RECOVERING_JOBS, so it ran beside its group's live firings whatever the group's limit. (#​4148, from #​4142)
  • Startup recovery of a replay keeps the firing it replays. A replay of a replay named the recovery trigger rather than the original firing, and lost the original group's execution limit. Cluster recovery already kept it. (#​4160)
  • A cron trigger's clone has a job data map of its own. CronTriggerImpl.Clone shared the original's map, so a write through either was seen by both. The other trigger types already copied it. (#​4160)
  • Quartz.Plugins.TimeZoneConverter resolves seven more Windows ids on a system without tzdata-legacy, such as Ubuntu 26.04: FLE, India, Argentina, Greenland, Myanmar, Nepal and US Eastern Standard Time. Their CLDR names are legacy links that only tzdata-legacy installs, and TimeZoneConverter 7.1.0 maps them to the canonical names. Without the plugin those ids still fail on such a system; #​4172 tracks that. (#​4171)
Behaviour changes
  • A declared collection, or a declared object with a collection member, is refused when the map is written. The JsonSerializationException names the entry, the type and the member. Such a value used to be stored and then fail to load. (#​4161)
  • The ADO store's completion no longer reports a late IgnoreMisfirePolicy trigger as misfired. This applies to the completion of a [DisallowConcurrentExecution], BufferOne or CancelPrevious firing, which also no longer writes such a trigger back. Acquisition fires it late, as the misfire handler and RAMJobStore already did. (#​4169)

The run-state and serial-completion fixes ship for 3.x in 3.22.8.

What's Changed
  • A start that re-applies declared triggers keeps their run state and an individual pause by @​lahma in #​4147
  • A recovered firing of an ungrouped trigger counts against its trigger group's execution limit, not RECOVERING_JOBS by @​lahma in #​4148
  • Small 4.7 fixes: a cron trigger clone's data map, a daily trigger's end-after-count zone, recovery markers, and the options table by @​lahma in #​4160
  • A job-data value of a declared type reads back as it was written by @​lahma in #​4161
  • A serial job's completion reads only the triggers that misfired, not its whole backlog by @​lahma in #​4169
  • The unit tests and the trimming canary run on Ubuntu 26.04: IANA test zones and TimeZoneConverter 7.1.0 by @​lahma in #​4171
  • 4.6.1: fixes from main for the 4.6 line by @​lahma in #​4177

Full Changelog: quartznet/quartznet@v4.6.0...v4.6.1

v4.6.0

Quartz.NET 4.6 is about clusters under load. A firing round of work that allows concurrent execution takes no TRIGGER_ACCESS lock, and a round reads each concern once whatever its size. An interrupt reaches the node running the firing, through the store. A node can serve only the execution groups it is configured for, and execution history can write behind. A trimmed or native publish now records no Quartz trim warning, and eight packages are proven under native AOT. An application on 4.5 compiles on 4.6 unchanged, unless it treats warnings as errors and has a cron literal that the new analyzer warning QZ0007 reports. The upgrade has three required database migrations, each one nullable column that can run while 4.5 nodes are up; see Upgrading from 4.5 to 4.6.

dotnet add package Quartz --version 4.6.0
Highlights
  • Lock-free firing rounds. On a clustered ADO.NET store, a round whose candidates all allow concurrent execution claims its triggers with guarded writes instead of taking TRIGGER_ACCESS. It is on by default, with an off switch. While any 4.5 node is live, every round takes the lock as before. SQL Server keeps the lock. Clustering
  • One read per concern. A clustered firing round reads each concern once, whatever its size, and a one-off's lock-free completion is one read and one write. Clustering
  • Interrupts reach every node. On a clustered store, Interrupt and InterruptFireInstance record a request for a firing on another node, and that node cancels the job's token after its next check-in. CancelPrevious now works across nodes too. Clustering
  • Execution-group routing. A node lists the execution groups it serves, or the ones it refuses, and acquires nothing else. Manual, startup and recovered firings keep their job's group. Execution groups
  • History can write behind. With ExecutionHistoryOptions.WriteBehindInterval set, a completion is queued and flushed with one status update per job per flush, at close to history-off throughput. Execution history
  • A trigger's own misfire threshold, and a scheduler-wide default instruction. WithMisfireThreshold lets one trigger tolerate a longer delay, or a shorter one, without moving the store's threshold for every other trigger. DefaultMisfireHandling sets what an unstated misfire instruction becomes, for every trigger type. Triggers
  • No Quartz trim warnings. The trim baseline is gone, and three feature switches turn the reflective paths off under PublishTrimmed and PublishAot. Quartz.Weasel.SQLite joins the packages proven native on every pull request. Trimming and native AOT
Clustering
  • Lock-free firing rounds (#​4123, closes #​4067).
    • AdoJobStoreOptions.LockFreeFiringRounds, default true; flat key quartz.jobStore.lockFreeFiringRounds. false restores 4.5's rounds.
    • Each node stamps its Quartz version in the new QRTZ_SCHEDULER_STATE.QUARTZ_VERSION, from the required migration 4.6/add_scheduler_version. While any 4.5 node is live, every round takes the lock (event 3517); once the last one ages out, rounds go lock-free (event 3518).
    • SQL Server keeps the lock. A round that needs it, such as one with a [DisallowConcurrentExecution] candidate, takes it.
    • A clustered round that loses a claim runs the following rounds locked (reason contention, event 3060) until the backlog clears or 2 s pass, so a drained backlog is no slower than under 4.5.
    • Measured on PostgreSQL: 3–24% more firings per second than 4.5 in the throughput census.
  • One read per concern (#​4086, closes #​4066).
  • Cluster-wide interrupt (#​4130, closes #​4071).
    • A request is a nullable INTERRUPT_REQUESTED_TIME on the firing's QRTZ_FIRED_TRIGGERS row, from migration 4.6/add_fired_trigger_interrupt. The node running the firing reads its requests after every check-in, so a request lands within one CheckinInterval, 7.5 seconds by default.
    • true from Interrupt means interrupted here or asked, not stopped.
    • A 4.5 node never reads the request; a 4.6 node says so once per such node (event 3520).
    • CancelPrevious asks a 4.6 peer instead of waiting as BufferOne. The two firings overlap for up to one check-in interval.
  • A clustered scheduler generates its instance id unless one was chosen (#​4105, closes #​4091). UseClustering() used to leave every node at NON_CLUSTERED.
Execution groups
  • A node serves the execution groups it is configured for (#​4088, closes #​4069).
  • Manual and startup firings carry their job's execution group (#​4109, closes #​4092).
  • A recovered trigger keeps its job's execution group (#​4084, closes #​4081).
  • Names in IConfiguration keep their case, and an execution-limit prefix such as tenant:* can be configured (#​4107, closes #​4093).
Execution history
  • Write-behind (#​4125, closes #​3979). ExecutionHistoryOptions.WriteBehindInterval queues each completion; one writer per store flushes every interval, or at 256 waiting runs, in one transaction with one QRTZ_JOB_STATUS update per job. In the census, history on ran at 276 firings per second, write-behind at 379, and history off at 388.
    • A crash loses the queue, normally at most one interval of runs. Reads of history and job status lag by up to one interval. Off by default.
  • Lookups by exact trigger key and fire instance id, on two indexes of their own (#​4094, closes #​4072). Optional migration 4.6/add_execution_history_lookup_indexes.
Triggers
  • A trigger carries its own misfire threshold (#​4131, closes #​4070).
    • TriggerBuilder<TJob>.WithMisfireThreshold(TimeSpan), ITrigger.MisfireThreshold: positive, whole milliseconds; null is the store's MisfireThreshold, as before.
    • Every surface carries it: [CronTrigger]/[SimpleTrigger] MisfireThreshold (checked by the new analyzer error QZ0008), the JSON and XML schedules, TriggerDetailsUpdate, the HTTP API's update-details and the dashboard's Edit details.
    • Both stores decide every misfire by it. The ADO.NET misfire scan stays one ordered index range on NEXT_FIRE_TIME.
    • Stored in the nullable QRTZ_TRIGGERS.MISFIRE_THRESHOLD, from the required migration 4.6/add_trigger_misfire_threshold. A 4.5 node applies the store's threshold, so a cluster honours per-trigger thresholds once every node is 4.6.
  • A scheduler-wide default misfire instruction (#​4131). QuartzSchedulerOptions.DefaultMisfireHandling (SmartPolicy, IgnoreMisfires, FireAndProceed, DoNothing; flat key quartz.scheduler.defaultMisfireHandling) is mapped to each trigger type's own instruction when a trigger whose instruction was never stated is stored. An explicit WithMisfireHandlingInstructionSmartPolicy() stays smart policy. The stored MISFIRE_INSTR is always explicit, so a 4.5 node reads the same number.
  • A trigger re-applied at startup keeps its previous fire time (#​4126, closes #​4122; also in 3.x).
Configuration and diagnostics
  • A part ConfigureAllQuartzSchedulers chooses after AddQuartz replaces the default, so AddQuartzPersistentStore works in either order (#​4121, closes #​4104). Thanks @​tzdevelopteam for the repro.
  • First-run errors name the culprit and the fix, and a generated job or trigger key in a persistent store warns (#​4113, closes #​4095).
  • QZ0007 flags a * seconds field under a restricted minute or hour, such as * */5 * ? * * (#​4115, closes #​4096). Thanks @​ybai08, whose parallel work in #​4111 reached the same rule.
  • The dashboard's cron preview uses the trigger's zone and calendar, and previews the expression being edited (#​4116, closes #​4099).
  • JobExecutionContextBuilder sets recovery, retry, refire, calendar and cancellation, and JobRunTime uses the TimeProvider (#​4119, closes #​4097).
Trimming and native AOT
  • Registered job types only, when trimmed (#​4087, closes #​4068). Feature switches Quartz.TypeNameResolution.IsSupported and Quartz.LegacyComponentKeys.IsSupported.
  • Job data without TypeConverter (#​4089, closes #​4082; #​4129). Feature switch Quartz.JobData.IsTypeConverterCoercionSupported (MSBuild QuartzJobDataTypeConverterSupport). DateOnly, TimeOnly, Half, Int128 and UInt128 convert on the switch-free path. The trim baseline goes to zero.
  • A trimmed SQLite store retries a busy database (#​4083). The trim baseline went from 21 to 17 entries on the way.
  • Quartz.Weasel.SQLite says IsAotCompatible (#​4129), and the canary proves a DbDataSource store natively. Eight packages are now proven.
Documentation
  • The recurring questions answered: cluster concurrency, why a trigger isn't firing, proxy prefixes, first run, reschedule events (#​4120, closes #​4098).
Thanks
What's Changed
Documentation
  • Docs answer the recurring questions: cluster concurrency, why a trigger isn't firing, proxy prefixes, first run, reschedule events by @​lahma in #​4120
Other changes
  • Main builds 4.6.0 by @​lahma in #​4064
  • A trimmed or native SQLite store retries a busy database, and the trim baseline records 17 entries instead of 21 by @​lahma in #​4083
  • A recovered trigger keeps its job's execution group by @​lahma in #​4084
  • A clustered firing round takes one read per concern, and a one-off completes in one read and one write by @​lahma in #​4086
  • A trimmed or native publish resolves only registered job types, and refuses the quartz.* component keys by @​lahma in #​4087
  • A node serves the execution groups it is configured for by @​lahma in #​4088
  • The Aspire example AppHost resolves MessagePack 2.5.307, past GHSA-qhrr-8q5h-9q3h by @​lahma in #​4090
  • A trimmed or native publish coerces job data without TypeConverter, and Quartz records no trim warning by @​lahma in #​4089
  • A clustered scheduler generates its instance id unless one was chosen by @​lahma in #​4105
  • Execution history, misfires and run statistics are looked up by exact trigger key and fire instance id, on indexes of their own by @​lahma in #​4094
  • A manual or startup firing carries its job's execution group by @​lahma in #​4109
  • Names in IConfiguration keep their case, and an execution-limit prefix can be configured by @​lahma in #​4107
  • First-run errors name the culprit and the fix, and a generated identity in a persistent store warns by @​lahma in #​4113
  • QZ0007 flags a * seconds field under a restricted minute or hour by @​lahma in #​4115
  • The dashboard's cron preview uses the trigger's zone and calendar, and previews the expression being edited by @​lahma in #​4116
  • JobExecutionContextBuilder sets recovery, retry, refire, calendar and cancellation, and JobRunTime uses the TimeProvider by @​lahma in #​4119
  • A part ConfigureAllQuartzSchedulers chooses after AddQuartz replaces the default, so AddQuartzPersistentStore works in either order by @​lahma in #​4121
  • Tests pass in any time zone, and two flaky tests settle by @​lahma in #​4124
  • Execution history can write behind: one status update per job per flush by @​lahma in #​4125
  • A trigger re-applied at startup keeps its previous fire time by @​lahma in #​4126
  • A firing round of concurrent-allowed work takes no TRIGGER_ACCESS lock by @​lahma in #​4123
  • Job data converts DateOnly, TimeOnly, Half and Int128 without TypeConverter, and the canary proves Quartz.Weasel.SQLite and a DbDataSource natively by @​lahma in #​4129
  • An interrupt reaches the node running the firing, through the store by @​lahma in #​4130
  • A trigger carries its own misfire threshold, and a scheduler a default misfire instruction by @​lahma in #​4131

Full Changelog: quartznet/quartznet@v4.5.1...v4.6.0

v4.5.1

Three packages take the fixed versions of dependencies named in Microsoft's October 13 security advisories. There is no public API change and no schema change, and no other package changes.

Fixed

  • Quartz.Dashboard.Agent requires Microsoft.AspNetCore.SignalR.Client 10.0.13. 4.5.0 required 10.0.0, and every SignalR client before 10.0.13 brings a Microsoft.AspNetCore.SignalR.Common covered by GHSA-334f-vvp4-7g6p (CVE-2026-70159). So an application referencing the agent got NU1903 at restore, and that is an error wherever warnings are errors. The fixed client asks for Microsoft.Extensions.DependencyInjection, Logging and Logging.Abstractions 10.0.13 or later, so an application referencing the agent now resolves those at least that high. The agent's own nuspec no longer repeats the framework-extension floors it inherits from Quartz. (#​4065)
  • Quartz.Weasel.Oracle and Quartz.Weasel.SqlServer require System.Security.Cryptography.Pkcs 9.0.21. Their database drivers, reached through Weasel, bring Pkcs 8.0.0 and 9.0.4, which GHSA-2fmv-rv2c-59x7 and GHSA-846f-f764-76mx cover. Neither package calls it. The direct reference lifts an application's graph to the fixed version, and can go once the drivers require a fixed one themselves. (#​4065)

The same SignalR fix ships for 3.x's Quartz.Dashboard in 3.22.6.

What's Changed

Other changes
  • The restore graph takes the October 13 security fixes for SignalR and the cryptography packages by @​lahma in #​4065

Full Changelog: quartznet/quartznet@v4.5.0...v4.5.1

v4.5.0

Quartz.NET 4.5 is one dashboard for a fleet. It fronts schedulers in other processes, on other machines and in other clusters, and tells apart workers that all run QuartzScheduler. Targets fronting one clustered store show as one row with its nodes. A new package, Quartz.Dashboard.Agent, lets a worker dial out to the dashboard, so the worker opens no inbound port. Six more packages are proven under native AOT on every pull request. An application on 4.4 compiles on 4.5 unchanged, and the database schema did not change.

dotnet add package Quartz --version 4.5.0

Highlights

  • A fleet, keyed target/name. Each AddQuartzHttpClient registration names its process with HttpClientOptions.Target, so w1/QuartzScheduler and w2/QuartzScheduler are two rows, beside store-attached windows and the local schedulers. Fleet
  • A cluster shows once. Targets that front one clustered store are detected and merged into one row, w1+w2+w3/QuartzScheduler, whose Cluster page starts, stands by, shuts down and interrupts each node. Fleet
  • Workers dial out: Quartz.Dashboard.Agent. UseDashboardAgent connects a scheduler to a dashboard over SignalR. The dashboard enables it with AcceptAgents. The worker needs no inbound port, no HTTP API and no web framework. Dashboard agent
  • Secure by default. The agent refuses every job type a dashboard names until IsJobTypeAllowed is set. The hub reads its token from the Authorization header only. A hub with neither tokens nor a policy fails at startup. Dashboard agent
  • Native AOT, proven per package. Quartz.Jobs, Quartz.Plugins, Quartz.Plugins.TimeZoneConverter, Quartz.HttpClient, Quartz.AspNetCore and Quartz.Dashboard.Agent now say IsAotCompatible. Each one is published as a native executable and run on Windows, Linux and macOS on every pull request. Trimming and native AOT

Fleet

  • Identity is target/name (#​4042, closes #​3774).
    • The new public SchedulerRef(SchedulerName, Target) is the key a dashboard addresses a scheduler by. Parse splits at the first /. A bare name is the key of a scheduler reached through no target.
    • HttpClientOptions.Target names the process. It becomes the client's service key and the first half of the key. / and + are refused.
    • The Schedulers page shows one row per key, with Target and Reached columns. The picker groups by target, and remembers the selection in the qz_scheduler cookie.
    • The HTTP API's {name} resolves to the untargeted entry only. A name held only by targets answers 404, naming the targets.
    • A local scheduler whose own name contains / keeps that name everywhere: in resolution, authorization, history and live logs.
  • A cluster behind several targets is one row (#​4042).
    • A fleet monitor asks every target which nodes it sees at QuartzDashboardOptions.ClusterDetectionInterval, one minute by default. Targets on one clustered store merge into a+b+c/name.
    • Membership is sticky: a node that stops answering stays in its cluster as unreachable, so the key changes on a topology change, never on an outage. Each round elects a preferred member from the targets that answered.
    • The Cluster page reaches each node through its member key. A cluster-wide lifecycle verb is refused, because a cluster has no single lifecycle.
    • Log events 9105–9110.
  • SchedulerRegistration gains Key, Members and LastSeenUtc. GET …/schedulers rows gain members and lastSeenUtc. SchedulerResource.Target lets an authorization policy see which process it is asked about.

Dashboard agent

  • New package Quartz.Dashboard.Agent (#​4044, closes #​3773).
    • Worker: UseDashboardAgent(o => { o.Endpoint = new("https://ops.example/quartz/agents"); o.Token = …; o.Target = "w1"; }).
    • Dashboard: AcceptAgents(a => a.Tokens.Primary = …).
    • The connection retries forever, from 1 s back to 30 s. The dashboard shows the worker Unknown, with when it was last seen, after three missed heartbeats (45 s by default). It forgets the worker after ForgetAfter, one hour by default. A restarted worker comes back on the same row.
    • History, live logs and every page work over the connection. History is read through the tunnel and the database schema is unchanged.
    • Only the SignalR client is referenced, with no Microsoft.AspNetCore.App.
  • What the agent accepts is decided on the worker.
    • ReadOnly, IsOperationAllowed, MaxConcurrentOperations (a bounded queue; overflow answers 503), MaxPageSize, and a message cap of 4 MiB by default.
    • IsJobTypeAllowed unset refuses every request that stores a job by type name, with 403. Triggering, pausing, rescheduling and deleting existing jobs all work.
    • A cancelled or timed-out operation is cancelled on the worker too, whether it is queued or running.
  • How the hub is authorized.
    • Bearer tokens in two slots, Primary and Secondary, for rotation. They are compared in constant time and read from the Authorization header only, never from a query string, because a URL ends up in access logs.
    • Alternatively, an AuthorizationPolicy.
    • A token-only hub stays reachable under a fail-closed fallback policy.
  • Log events 9111–9117 (dashboard) and 9300–9305 (agent).
  • One dashboard instance per deployment in agent mode: the agent registry lives in the process.

Trimming and native AOT

  • Six side packages say IsAotCompatible (#​4051, part of #​3341).
    • Quartz.Trimming.Canary runs Quartz.Jobs, Quartz.Plugins and Quartz.Plugins.TimeZoneConverter as a native executable.
    • Quartz.Trimming.Canary.Wire runs Quartz.AspNetCore, Quartz.HttpClient and Quartz.Dashboard.Agent the same way.
    • The build fails on any warning a package's baseline does not record. None of the six produces an IL3050.
    • VerifyReferenceAotCompatibility stops listing them.
    • Quartz.Jobs records no trim warning any more.
  • Quartz.Extensions.Redis, Quartz.Aspire and the Quartz.Weasel* packages stay trimmable without the claim, because each needs a server or a database that nothing can run natively here. Which packages say whether they can be trimmed

Scheduling and the dashboard

  • A freed execution-limit slot wakes the scheduler loop (#​4041, closes #​4033). A node parked until its next look used to wait out that look when a firing ended in a group held at its limit, or when a held pinned trigger became free. Now:
    • it looks again as soon as the slot frees;
    • while a held trigger is re-polled early, the look-ahead is bounded so the node acquires nothing that is due after its next look.
  • The Backfill dialog's preview says when Max slots would refuse the range, and how far the spacing spreads it (#​4038, closes #​4037). Thanks to @​dongfo, in a first contribution.

Behaviour changes

Before After What to do
IQuartzApiClient members took a bare scheduler name They take a key; a bare name is the key of a scheduler reached through no target Nothing for a 4.4 deployment. A replacement client parses keys with SchedulerRef.Parse
The HTTP API's {name} resolved to the first entry under the name It resolves to the untargeted entry only Nothing for a 4.4 deployment
AddQuartzHttpClient was keyed by the scheduler name It is keyed by Target when one is set If you set Target, resolve GetRequiredKeyedService<IScheduler>(target)
A second Target of the same name was impossible It throws SchedulerConfigException Give each process a Target of its own
AddQuartzDashboard() registered no scheduler repository It registers Quartz's shared services, so an agent-only dashboard needs no AddQuartz Nothing
The Schedulers page showed one row per name It shows one row per key, and one row per detected cluster Nothing

Upgrading

An application on 4.4 compiles on 4.5 unchanged. No migration to run: the schema is the same, and 4.4 and 4.5 nodes run side by side on one store. Every addition and behaviour change is in the migration guide's Upgrading from 4.4 to 4.5.

Public API

Additions only: +66 lines, none removed, and no interface gained a member.

Assembly Lines added
Quartz.Dashboard 24
Quartz.Dashboard.Agent (new) 23
Quartz 15
Quartz.HttpClient 3
Quartz.AspNetCore 1

Quartz.Dashboard now depends on Quartz.HttpClient, at the same version. The whole delta, member by member: git diff v4.4.0 v4.5.0 -- src/Quartz.Tests.Unit/Verify src/Quartz.Tests.AspNetCore/Verify.

Also

  • The docs build takes patched npm dependencies (#​4040). Test, analyzer and Aspire hosting packages were updated.
  • The comparison page describes 4.5 (#​4050).
  • Marten and Wolverine are held at 9.45 and 6.45 in the tests and examples while the Weasel floor holds at 9.39.0 (#​4056).

What's Changed

Other changes
  • Main builds 4.5.0, and the benchmark notes record what 4.4.0 measured by @​lahma in #​4035
  • The docs build takes the patched shell-quote, proxy-addr, compression, source-map-js, Vue, smol-toml, postcss-selector-parser and KaTeX by @​lahma in #​4040
  • A freed execution-limit slot wakes the scheduler loop, and a node looking again early for a held trigger acquires nothing due after its next look by @​lahma in #​4041
  • Call UpdatePreview on spacing and max slots change by @​dongfo in #​4038
  • The dashboard keys a scheduler by target/name and shows targets fronting one cluster as one row by @​lahma in #​4042
  • A scheduler dials out to a dashboard with Quartz.Dashboard.Agent and is shown and driven over that connection by @​lahma in #​4044
  • The comparison carries what 4.5 added by @​lahma in #​4050
  • Six side packages run as native executables on every pull request and say IsAotCompatible by @​lahma in #​4051
  • Dependabot holds Marten and Wolverine minor and patch updates while the Weasel floor holds by @​lahma in #​4056
  • The agent page and readme agree on the key and the trimming claim, and the CI legs get the time they now take by @​lahma in #​4057

New Contributors

Full Changelog: quartznet/quartznet@v4.4.0...v4.5.0

v4.4.0

Quartz.NET 4.4 is history you can run a business on. Each run says what it achieved: succeeded, failed,
cancelled or skipped, with a summary and metrics. The history keeps that per job, sweeps it by result and charts
it over time, and a health check degrades when a required job stops succeeding. Recovery respects rolling
deploys, and triggers already due fire in the transaction that acquires them: at 20 triggers a second on
PostgreSQL, 95–99 % of firings land within ±50 ms, up from 54 % on 4.3.0. Quartz.Weasel now manages the job store's schema on six databases, adding MySQL, Oracle and Firebird.

dotnet add package Quartz --version 4.4.0
Highlights
  • A run says what it achieved, and the history keeps it per job. context.Result, a status per job, retention
    by result, and a chart of runs over time. Execution history and outcomes
  • A health check per job. RequireSuccessWithin(jobKey, TimeSpan.FromHours(26)) degrades when a nightly job
    stops succeeding. Execution history and outcomes
  • A retry policy on the job class, and a scheduler-wide default. Also RunAtStartup, scheduling a trigger
    already paused, and pausing a set of keys with a reason in one call. Scheduling
  • Fire on acquire. One transaction per round instead of two: one-off throughput on PostgreSQL goes from 258–272 to
    420–429 firings a second. Reliability and clustering
  • Recovery that respects rolling deploys. A firing cancelled by a graceful shutdown can be replayed by a peer
    or the restarted node. Reliability and clustering
  • Weasel for six databases. Quartz.Weasel.MySQL, Quartz.Weasel.Oracle and Quartz.Weasel.Firebird join
    PostgreSQL, SQL Server and SQLite. Databases and Weasel
  • An HTTP client that reads a newer host, and HTTP API errors as problem details under native AOT.
    HTTP API and dashboard
Execution history and outcomes
  • Job outcomes (#​3971, closes #​3957). A job says what its run achieved:
    context.Result = JobRunReport.Skipped("…").With("released", 3).
    • Results: JobRunResult {Succeeded, Failed, Cancelled, Skipped}.
    • A summary and metrics go with it. Metrics are JSON, AOT-safe, capped at 4,000 characters (log events
      1059/1060).
    • Vetoes are recorded in the misfire feed (MisfireReason.Vetoed). Manual runs (TriggerJob) are marked.
    • Per-job JobRunStatus: last run, last success and failure, consecutive failures, counts.
    • Retention by result (RetentionByResult, MisfireRetention) and MaxEntriesPerJob.
    • How-to: Job Outcomes.
  • The database history keeps all of it (#​3980, closes #​3959; schema #​3972, closes #​3958).
    • Each run's row and its job's status commit in one transaction on the history connection.
    • The sweep is tiered by result, capped per job, and clears status rows of deleted jobs. One elected node
      sweeps per cluster.
    • The misfire feed is filtered by reason. A 4.2 row with no reason reads as Missed.
    • Known cost: runs of one job that complete at once take turns on its status row. On PostgreSQL, one hot job
      with history on went from 113–248 to 77–189 firings a second. Tracked in #​3979.
    • Fixed: the optional-table refusal named database/migrations/database/migrations/…. (#​3972)
  • **A run w

[!IMPORTANT]
✂ PR body was truncated to here.


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • "every 2nd week starting on the 2 week of the year before 4am on Monday"
  • Automerge
    • At any time (no schedule defined)

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

♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about these updates 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 requested review from a team and dani-garcia October 26, 2026 03:39
@bitwarden-bot bitwarden-bot changed the title [deps] Platform: Update quartznet monorepo to v4 [PM-45719] [deps] Platform: Update quartznet monorepo to v4 Oct 26, 2026
@bitwarden-bot

Copy link
Copy Markdown
Collaborator

Internal tracking:

@renovate renovate Bot changed the title [PM-45719] [deps] Platform: Update quartznet monorepo to v4 [deps] Platform: Update quartznet monorepo to v4 Oct 26, 2026
@renovate
renovate Bot force-pushed the renovate/major-quartznet-monorepo branch 3 times, most recently from 9e637d6 to 5e22191 Compare October 26, 2026 19:58
@renovate
renovate Bot force-pushed the renovate/major-quartznet-monorepo branch 2 times, most recently from 5778699 to d6db511 Compare November 2, 2026 22:07
@renovate
renovate Bot force-pushed the renovate/major-quartznet-monorepo branch from d6db511 to 73c7596 Compare November 3, 2026 00:05

This branch has not been deployed

No deployments
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.

1 participant