Update dependency Quartz.AspNetCore to v4 - #1373
Open
renovate[bot] wants to merge 1 commit into
Open
Conversation
renovate
Bot
force-pushed
the
renovate/major-quartznet-monorepo
branch
from
September 9, 2026 03:11
cfb06c6 to
82c6548
Compare
renovate
Bot
force-pushed
the
renovate/major-quartznet-monorepo
branch
from
September 13, 2026 13:28
82c6548 to
125ee2e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
3.14.0→4.1.0Release Notes
quartznet/quartznet (Quartz.AspNetCore)
v4.1.0Quartz.NET 4.1 is an additive minor: every public change is a new type, a new member on a type we own, or a default interface member; the schema is 4.0's; a 4.0 application upgrades by changing the version. The headline is that a scheduler can now be added to, removed from and restarted in a running container — the one item the 4.0 roadmap deferred as "a 4.1 concern" — and around it the additive follow-ups filed during the 4.0 release candidates, an API reference for 4.x, and an honest Wolverine page now that Wolverine has a cron of its own.
Highlights
ISchedulerRuntime(it extendsISchedulerRegistry, as the 4.0 guide promised) builds a scheduler from a recipe into a container of its own that resolves the application's services, jobs and options from the application's container, binds it where the HTTP API, the dashboard and the health checks already look, drains it before it is replaced, and refuses the cases that cannot be made safe: a name the container registered, a bound name, a recipe that supplies an instance part, a restart whose drain gave up.ISchedulerFactory.LookupSchedulernow builds a registered-but-unstarted scheduler on lookup. A restart builds the next generation first (a configuration error leaves the old one running), drains the old one withinSchedulerRestartOptions.DrainTimeout(30 s by default) and only then initialises the new store — because the store's recovery sweep is per scheduler name, not per instance — and a drain that gives up leaves the name shut down and reported with no status until the nextRestartfinishes the job. A recipe that hands over a job store, thread pool, job factory or instance-id generator as an object is refused before anything is built, since a shut-down instance cannot be re-initialised. (#3338; add/remove #3725, restart #3734)MON/2parses and means what2/2means (a step through the week, not 3.x's fortnight), andMON-FRI/2is2-6/2where 4.0 silently read it asMON-FRI. (#3732, #3733 [85214e4…e29f7c4283]). The century walk ofCronCalendar.GetNextIncludedTimeUtc(#3690) shipped in 4.0.1.POST …/triggers/{group}/{name}/update-detailsedits a trigger in place (description, priority, job data, calendar, misfire instruction with its family, preferred node, execution group, retry policy; a field absent from the body is left alone and anullclears it), andHttpScheduler.UpdateTriggerDetailsno longer throws —ContextandListenerManagerare the two members a wire cannot carry (#3681, #3735).takesays in the OpenAPI document why it is a string: a page size, orallfor everything up toMaxPageSize(#3682, #3736).GetStatus(ct)andGetSchedulerInstanceId(ct)are the asynchronous twins of the twoISchedulerproperties a wire cannot answer without blocking — default interface members that answer the property locally, one round trip onHttpScheduler, and on the container's deferred scheduler they build it on first use where the properties throw (#3684, #3738).QuartzHttpApiOptions.IsJobTypeAllowedand its dashboard twin let an operator name which job types a request may schedule — a predicate over the type name as the request spelled it, never a resolved type; a refused name is a403naming the type and a warning in the log, and the default is unchanged (#3685, #3741).quartz_jobs.json, in theQuartz:Schedulesection and in the XML file (<preferred-node>;*pins it to whichever node fires it first), and the XML format catches up with<execution-group>and<retry-policy>. The schema stays 2.0: the elements are optional and a 3.x file loads unchanged. The freeze on the XML format is redrawn as a freeze on trigger kinds, not settings. (#3683, #3739)AddQuartzDashboard()besideAddQuartzHttpClient("acme", …)now fronts the scheduler in the other process honestly: it is listed with the new originRemote, every page drives it, the History page and the misfire tile read the history the target's process keeps, and Live Logs says what a wire does not carry yet. Underneath: the scheduler repository no longer reads a remote scheduler'sStatuswhile holding its lock — one unreachable target used to stall every scheduler lookup in the process, the HTTP API's own included, for the client's timeout — the registry asks all schedulers concurrently under one two-second budget and reportsUnknownfor the ones that did not answer, the pages page instead of asking for everything, and a secondAddQuartzHttpClientunder the same name is refused rather than silently winning. Execution history is now Quartz's rather than the dashboard's:IExecutionHistoryStorein core with an in-memory default bounded by age and count, a recorderAddQuartzExecutionHistory()installs against every scheduler, threeGET …/schedulers/{name}/history/…routes the HTTP API serves, and a read-only reader of them thatAddQuartzHttpClientregisters per target.QuartzHttpApiOptions.ReadOnlyturns the whole mutating surface of the API off in one switch — mutation is a property of the route, so the two bulk fetches stay reads. The design for the rest — live events over the wire, an audit of operator actions, store-attached and dial-out targets, a fleet pane — is recorded on #3387 and filed as 4.2 issues (#3768–#3775). (#3387, #3776)Informationline,9007, naming the caller (HttpContext.User.Identity.Name, or(anonymous)), the operation, the scheduler and the route — until now aPOST …/shutdownthat worked was recorded nowhere. The dashboard's Action Log entries and their9100/9101lines now say where an action landed: the scheduler's origin, the node behind it, and whether the action was node-local (start, stand-by, shutdown and interrupting a firing) — and interrupting a firing from Currently Executing is recorded at all, which it was not. (#3769, #3784)opts.Schedules.ScheduleRecurring, over Cronos), and the page now puts the two side by side in one table, says plainly when Wolverine's own schedule is the right answer, and keeps the recipe for what a Quartz trigger adds — with a seventh example part running Wolverine's schedule beside Quartz's. (#3719, #3737)Public API — additive only
ISchedulerRuntime : ISchedulerRegistry—Add,Remove,RestartSchedulerAddOptions,SchedulerRestartOptions(readonly record structs),SchedulerRestartExceptionIScheduler.GetStatus(ct),IScheduler.GetSchedulerInstanceId(ct)— default interface membersHttpScheduler.GetStatus/GetSchedulerInstanceIdoverridesQuartzHttpApiOptions.IsJobTypeAllowed,QuartzDashboardOptions.IsJobTypeAllowedSchedulerOrigin.Remote;SchedulerRegistration.SchedulerInstanceId(init)QuartzHttpApiOptions.ReadOnly403problem details; the two bulk fetches stay readsQuartzHttpApiOptions.EventStreamHeartbeatIntervalIExecutionHistoryStore,ExecutionHistoryEntry,MisfireHistoryEntry,ExecutionHistoryQuery,MisfireHistoryQuery,ExecutionHistoryOptions,AddQuartzExecutionHistory()IDashboardHistoryStorekeeps working as its adapter in both directionsNothing was removed or reshaped (
v4.0.1..v4.1.0baseline diff: 96 added lines, 0 removed). The one dependency change:Quartznow depends onMicrosoft.Extensions.Logging.Abstractionsrather thanMicrosoft.Extensions.Logging; an application that tookLoggerFactoryorAddLoggingtransitively from Quartz adds the package itself (#3730, #3745).Behaviour changes worth noting
MON/2parses (4.0 refused it; 3.x read it as every second week) andMON-FRI/2fires on three days, not five — neither can be reported to you, both parse; the migration guide's 4.0 → 4.1 section has the audit query. AQuartzSchedulerBuilderwith a provider registered but no factory refuses to build (see #3730).GetNextInvalidTimeAfterreturnsnullwhere it used to return a valid instant (4.0.1). A shut-down container scheduler injected asISchedulerre-points to the generationISchedulerRuntime.Restartbuilt. Mapping the HTTP API now records execution history in memory, bounded (24 h, 2,000 rows per scheduler);ExecutionHistoryOptions.MaxEntriesPerScheduler = 0records nothing.AddQuartzHttpClientthrows on a scheduler name it already registered. A 4.0 client parsingGET …/schedulersfrom a 4.1 host that fronts a proxy sees the origin"Remote"it does not know — upgrade the client.AddQuartzDashboard()no longer registersDashboardLiveEventsPlugin; a reverse proxy needs to forward{DashboardPath}/hubonly for SignalR clients of your own, not for the pages. Log events9100and9101end with(origin …, node …)— a pipeline parsing the old template needs the new one.Also
CheckinInterval+CheckinMisfireThresholdhas passed since the row it last wrote (15 s on the defaults), and the cluster manager's sleep after a failed check-in was floored atDbRetryInterval, also 15 s, so one database blip during a check-in had a live node recovered by its peers: its next row went out at 22.5 s, 7.5 s after they had stopped trusting it. Inherited from Java Quartz. A failed check-in is now retried inside what is left of the window — half of it each time, never later thanDbRetryInterval— and only once the window has closed does the ordinary back-off apply; the retries are timed from the last check-in that reached the database, not from the stamp a failed read of the state table leaves. Defaults are unchanged, so the 15-second failover latency stays.CheckinMisfireThresholdpast the timer ceiling is now refused at startup, likeCheckinInterval. (#3777, #3778; 3.22 carries the same change on 3.x)DbRetryIntervalas before.Shutdown(waitForJobsToComplete: false)closed the thread pool to new work before the scheduler loop had finished its iteration, so a firing the loop had already committed to the store (fired-trigger row written, trigger advanced) was refused by the pool and never ran — onlyRequestRecoverywould ever have replayed it — and a completion reported after the store had closed was aborted, leaving the triggerBLOCKEDfor a peer's cluster recovery to settle. The loop is now halted and awaited before anything it depends on is torn down, and an unwaited shutdown gives the executions it dispatched two seconds to report their completions before the store closes; the waited form drains as before. On the fixture: 160 of 160 firings with a node leaving mid-run without waiting. (#3746, #3754)Shutdown(waitForJobsToComplete: false)— the hosted service's default — can take up to two seconds longer when a job is in flight.AddJob/AddTrigger. Declared content is applied at creation, and a trigger the store already held was applied as a reschedule that deleted every fired-trigger row of the key — beforeStart(), so the recovery sweep found nothing to recover.ReplaceTriggernow deletes the trigger row only; unscheduling still takes the fired rows with it, and the replacement of a trigger whose job disallows concurrent execution is storedBLOCKEDbehind an execution still in flight rather thanWAITINGbeside it. The clustered case had a second defect: a node's first check-in handed its own state row to recovery, and the deferral grace period judged that row by its old timestamp — the node's own record is never deferred now. A SQLite reproduction of the restart runs without Docker. (#3759)TRIGGER_ACCESS— returns nothing and throws nothing, so a cluster stopped scheduling in silence.DbLockHandlernow logs event3716once per acquisition that outlivesAdoJobStoreOptions.LockWaitWarningThreshold(30 s by default;nullreports nothing), every acquisition is timed onquartz.jobstore.lock.wait.duration, andquartz.jobStore.commandTimeout— which 3.22 adds — translates toAdoJobStoreOptions.CommandTimeoutwhere it used to be accepted and silently dropped. Neither ends the wait; a command timeout does, and the troubleshooting page says how to tell the case apart in each database and which server-side settings free the lock. (#3764, #3766)DbRetryInterval— also 15 s — so a check-in that failed at 7.5 s was next attempted after the peers had already recovered the node: fired rows deleted, recovering jobs fired again,[DisallowConcurrentExecution]no longer holding. Inherited from Java Quartz rather than a regression. While the window is open a failed check-in is retried inside it, half of what is left each time; only once it has closed does the ordinary back-off apply. Defaults and the documented 15-second failover latency are unchanged, andthreshold >= DbRetryIntervalis no longer a relation an operator has to know about. The 3.x configuration reference'sclusterCheckinIntervaldefault (7500, not 15000) and the missingclusterCheckinMisfireThresholdrow were fixed on the way. (a5b2197)redisintegration leg now runs two clustered nodes over one PostgreSQL store withRedisLockHandleras the only mutual exclusion, 250 firings across a[DisallowConcurrentExecution]job and an ordinary one, and asserts no firing doubled, none was lost, the non-concurrent job never overlapped and both nodes fired; a second fixture shuts one node down mid-run and checks the survivor and the closed multiplexer. 4.0.0 listedQuartz.Extensions.Redisas the least exercised package; it no longer is. (#3722, #3744)quartz.jobStore.*key fails startup instead of being ignored. 4.0 read the keys it knew into typed options and did nothing with the rest, soquartz.jobStore.dbRetryIntrevalstarted the scheduler with the default in force and said nothing — which is also howquartz.jobStore.commandTimeoutwent missing before it was bridged. A persistent store now refuses a key under that prefix that nothing reads, by name, where a misconfiguration already fails:Unknown configuration property 'quartz.jobStore.dbRetryIntreval'. It is not a setting of the ADO.NET job store ….quartz.checkConfiguration = falseallows it through, as for every other unknown key; a store of your own keeps failing in the binder that writes leftover keys onto it, as 3.x did. (#3767)quartz.jobStore.*typo that 4.0 tolerated stops the scheduler from starting.Quartz.Cronpackage was sized (#3720): mechanical at ~5,000 lines onnet8.0;net10.0, and not shipped — no consumer would take it today. The report is on the issue.Quartzdepends on the logging abstractions, not the logging package — Quartz writes log events and never builds the pipeline that carries them, soAddQuartzregistersILogger<>and noILoggerFactoryat all; wherever it needs a factory it asks the container and falls back toLogProvider. Nine tests pin that the order ofAddQuartzandAddLoggingdoes not matter and that a host's providers are never dropped. AQuartzSchedulerBuilderhanded a bareILoggerProviderwithoutAddLoggingnow refuses atBuild()with the fix in the message, where it used to log through a factory Quartz no longer supplies. (#3730, #3745)v4.0.1Quartz.NET 4.0.1 is a maintenance release about getting to 4.0: nothing about how a trigger fires changed, the public API is untouched (the baselines did not move), and the schema is 4.0's. Five days after 4.0.0 an audit of every public dependency-bot pull request that touched a Quartz package found two reasons an upgrade never got as far as compiling, both of them ours, both fixable in a patch — one gap in the migration guide for F# — and, found by the rc.1 security gate and moved forward, a cron calendar that could take a century to answer.
What changed
Microsoft.Extensions.*dependency at10.0.11, the newest patch on the day it was built, so a project pinned at 10.0.9 or 10.0.10 hitNU1605"detected package downgrade" before a line of source compiled. Every floor is now the lowest version of its major that the code compiles against and that carries no advisory:10.0.0for the framework extensions,13.0.2for Newtonsoft.Json (13.0.1 reflects overTimeOnlymember by member and a job data map loses its seconds — a test said so),1.15.3for OpenTelemetry.Extensions.Hosting (the first version whoseOpenTelemetry.Apicarries no advisory),3.0.0for StackExchange.Redis and7.0.0for TimeZoneConverter. The library is built against those floors, so the claim is checked on every build, and a test refuses a floor that creeps up. (#3717,c9ecf89)Quartz(Quartz.Extensions.DependencyInjection,Quartz.Extensions.Hosting,Quartz.Serialization.SystemTextJson, andQuartz.Serialization.Json, whose successor isQuartz.Serialization.Newtonsoft) had no 4.0.0, so a bot that groups Quartz with any of them resolved the group to the newest version every member has — 3.20.1 — and closed the 4.0.0 pull request it had already opened as superseded, with green checks. From 4.0.1 those four ids carry an empty package at every 4.x version: no assembly, one dependency on the replacement, a readme that says to remove the reference. The migration guide's instruction stands — remove them — but the upgrade is now offered.Quartz.OpenTracingandQuartz.OpenTelemetry.Instrumentationdeliberately have no such package: neither has a 4.x replacement, and an empty one would hide that. (#3717,c28f213)CronCalendar.GetNextIncludedTimeUtcno longer walks a century one second at a time — it askedCronExpression.GetNextInvalidTimeAfterfor the end of an excluded run, and that method stepped through the run one second per full cron computation; for an expression that excludes everything (* * * * * ?) the walk ran to the give-up year, roughly three billion computations, on a public member. The next non-matching instant is now read off the expression's own field sets — second, minute, hour, day, month, year — with each skip verified against the time zone's clock so a repeated fall-back hour is never stepped over. An expression that fires every second answersnull, and the calendar turns that into aSchedulerExceptionnaming the expression instead of hanging. The unchanged next-fire-time path measures the same as before; the changed method is 76–86 % faster on the benchmark corpus. (#3690,a125809,4d60904)GetNextInvalidTimeAfterreturnsnullwhere it used to return a valid instant after giving up, and aCronCalendarwhose expression excludes every instant now fails fast withSchedulerException.FS0856onIJob.Execute's arity,FS0041onScheduleJoboverload resolution,Async.AwaitTaskwith aValueTask,FS0039forStdSchedulerFactory), each quoted with its fix, and every sample is copied from a compiled example project the build keeps honest. (#3718,1b97b49,aff4fea)Upgrading
From 4.0.0:
dotnet add package Quartz --version 4.0.1; nothing else. From 3.x: the 4.x migration guide is unchanged in substance; if your bot has been landing 3.20.1 "upgrades", this is the release that lets it offer 4.x.Full changelog: quartznet/quartznet@v4.0.0...v4.0.1
v4.0.0Quartz.NET 4.0 targets
net10.0, is asynchronous and container-built throughout, and trims a public surface that had accumulated for a decade. It is a major version with extensive breaking changes and a mandatory schema migration. This page is the short form; the detail lives in the docs:Highlights
net10.0only — nonetstandard2.0build, no Full Framework, no.configsupport.Quartzpackage;StdSchedulerFactory,quartz.configdiscovery and the process-globalSchedulerRepository.Instance/DBConnectionManager.Instanceare gone. Flatquartz.*keys still work, translated to typed options by the one component that understands them, and a misspelled key is refused with a message rather than ignored. Options are validated at startup, so a bad value failsHost.Build()with every failure listed.IJob.Executetakes aCancellationToken,IJobFactoryhands out aJobScoperather than a bare instance, every publicTaskbecameValueTask, and every asynchronous member ends with a cancellation token.QueryJobs/QueryTriggersreturn aPagedResult<T>whose rows already carry what a listing needs, so a dashboard over a large schema no longer pays for the whole schema. The old call shapes remain as extension methods.TriggerState.Executingsays whether a trigger's job is running anywhere in the cluster; fire instances, cluster nodes, execution groups, misfires and history are listings that say which node they came from; the health check notices a node whose own check-in has stopped.ISchedulerclient over it (the replacement for .NET Remoting, which is gone), a dashboard, typed job input (IJob<TInput>;UsingInput(input)on a registration,ScheduleJob<TJob, TInput>(input, at)for a one-off), a retry policy a trigger carries — persisted, cluster-safe, never burning a repeat count — job execution middleware, a[JobTimeout]attribute, execution groups with per-node or cluster-wide limits, node affinity, and a firing that links back to the trace that scheduled it.0 15 10 1 * *is the 1st, not every day); a time the clocks skip fires when the gap ends; the parser refuses what it used to quietly reinterpret (1-5W,L-3in day-of-week,MON,FRI#3,MON/2, steps of zero); the five-field Unix form and the@daily-style macros work everywhere an expression is read.CalendarIntervalTriggerwithPreserveHourOfDayAcrossDaylightSavingssteps in local wall-clock time and no longer drifts in zones whose delta is not a whole hour; calendars mean the local day even where midnight itself moves. Review any schedule that crosses a transition.Quartz.Aspire:builder.AddQuartzPersistentStore("quartz")turns an Aspire connection name into a configured persistent store with its telemetry and health check, with noAspire.*dependency; a store can provision its own schema as it starts (ProvisionSchema()), safe under a cluster racing to start.MapQuartzHttpApi()andMapQuartzDashboard()refuse to start unless the endpoints carry authorization or an explicitAllowAnonymous(); a scheduler can be authorized on its own name, and every call the dashboard makes is authorized where it is made.ActivitySource("Quartz")andMeter("Quartz")(the names are public constants), nine instruments, spans on every store the same way, and every log line carrying a stable event id — 278 of them, catalogued on a generated page.QuartzdeclaresIsAotCompatible; a canary is published natively and run against a real store on three operating systems on every pull request; the remaining string-named paths are recorded and each has a documented alternative (#3341).RAMJobStore(numbers below).JobRunShellare internal, and non-public types are sealed. If something you relied on is gone, say so in an issue; these can be reopened.Breaking changes, the ten to know before the guide
net10.0only.Quartz.Extensions.DependencyInjection,Quartz.Extensions.HostingandQuartz.Serialization.SystemTextJsonare part ofQuartz;Quartz.Serialization.JsonisQuartz.Serialization.Newtonsoft;Quartz.OpenTracinghas no 4.x release, andOpenTelemetry.Instrumentation.Quartzproduces nothing on 4.x — subscribe to the source and meter directly. The first error a mixed project produces is CS0433 (a type in bothQuartz4 and a 3.x satellite): remove the three references.StdSchedulerFactory,DirectSchedulerFactoryandquartz.configare gone;AddQuartz(…)orQuartzSchedulerBuilder.Create(q => …)build a scheduler.Task→ValueTaskon nearly every member, and aCancellationTokenon every asynchronous one.Quartz.SpiisQuartz.Extensibility,Quartz.Simplmerged intoQuartz.Impl, the Newtonsoft types left the core namespaces. A string naming an old namespace still resolves, with a warning.*Supportbase classes are gone (every member has a default body); a listener with a 3.x signature is refused at registration.TimeOnlyandDateOnlyreplaceTimeOfDay;TimeProviderreplacesSystemTime; the semaphores are lock handlers.?in either day field) now parses, and two shapes fire on a different day than Cronos would — a bare digit in day-of-week (Quartz numbers Sunday1) and both day fields restricted (Quartz fires on the union). The guide's second audit finds them.Everything else — every renamed member, every sealed type, every removed constant — is in the guide, with the name you would have typed.
Fixes worth knowing about
The full list is spread over the six pre-release notes below. These change what a running cluster does without saying so, and most of them are as old as 3.x:
ResumeAllcould unpause real trigger groups: it deleted the all-groups sentinel with aLIKE, and the sentinel's four underscores are wildcards.TimeProviderthe scheduler was given (#3456).DailyCalendarcould crawl for minutes to answer months late (#3457, #3466).[DisallowConcurrentExecution]siblings behind it (#3502); a firing whose listener notification failed was listed as executing forever; a trigger with nothing left to fire was left behind when its last firing was abandoned (#3507).ACQUIREDfor good (#3673, on every 3.x version too).RAMJobStorenotified its listeners inside its own lock, so a listener that touched the store deadlocked (#3472).MySQLDelegateforced an index the misfire sweep could not use.ITypeLoaderof its own (#3705). Stopping the host twice at once — whichhost.StopAsync()duringRunAsync()does — no longer throws out ofRunAsync(#3701).Numbers
Measured on one shared machine with other work running, the same harness on both branches (
src/Quartz.Benchmark; the 3.20 half is in the tree underbaseline-3x/):MaxConcurrencyRAMJobStoreRAMJobStoreThe persistent-store fire path is batched and counts 1.27 commits per firing at the database. Two clustered nodes ran a mixed workload for thirty minutes on PostgreSQL and on SQL Server — every trigger family, a serial job behind an overlap detector, a retry policy, a job timeout, induced misfires, a node killed mid-run and recovered — with peak serial concurrency 1, exactly one recovery, nothing left behind and a flat heap, on the beta.1 build and again on the 4.0.0 commit itself. The upgrade from a running 3.20 cluster was rehearsed by hand through its mixed-version window on both engines, and the offline upgrade over rows a released 3.20 wrote runs on every dialect on every pull request.
Known limitations
Each is documented on the page that owns it; this is the list, not the explanation.
SendMailJobreads SMTP credentials out of job data when nothing is registered (Quartz.Jobs).MaxConcurrencydefaults to 10 on the shared thread pool (configuration reference).HttpScheduler.StatusandSchedulerInstanceIdblock on an HTTP round trip (HTTP client).OpenTelemetry.Instrumentation.Quartzproduces nothing on 4.x, and 4.x warns at start if it is present (OpenTelemetry).TransactionScope(job stores).Quartz.Extensions.Redis(one unit and one container fixture);Quartz.Aspireunder an AppHost was run by hand, not in CI.Upgrading a running deployment
The runbook is short and every step links the page that owns it. In one breath: retarget to
net10.0and drop the merged packages; on 3.x, turn binary blobs off and run the cron audit; runschema_30_to_40_upgrade_<database>.sqlwhile 3.x nodes are still up; configure and start the 4.0 nodes one at a time (routeAddCalendarthrough the 3.x nodes until the last one is gone); runschema_30_to_40_indexes_<database>.sqlonce it is; pause any job group you relied on being paused (3.x never persisted one). A 4.0 node against a schema you have not migrated refuses to start and names the column and the script.If you ran a 4.0 pre-release
Everything that changed between one pre-release and the next is one appendix in the guide, build by build: If you ran a 4.0 pre-release. 4.0.0 is the rc.2 commit: between the two tags, nothing changed but this page and the site.
Thanks
Task→ValueTaskacross the whole surface (#1964), Microsoft.Extensions.Logging in place of LibLog (#1480), the move ontoTimeProvider(#2286), a job's type recorded so a process that cannot load it can still work on it (#1610) — the groundwork for #3705 — and most of the cron work 4.0 is built on: both day fields restricting together (#1980),LandLWin day-of-month and an offset from the last weekday (#1956, #1975), the guards that refuse what used to be quietly reinterpreted (#1955, #2836), and the parser's rewrite and its allocation cuts (#2508, #2003).RAMJobStore(#1379, #1393, #1403),JobRunShell(#1388), listener notification (#1385, #1586, #1600, #1768) — for configuration values verified rather than assumed (#1408), and for sealing what nobody derives from (#1470).HttpSchedulerclient (#1803, #1815, #1831, #1845), which 4.0's HTTP API carries forward.DbDataSourcesupport (#2438, #2453) — which is how a trimmed application configures a store — asynchronous database transactions (#2456), and OpenTelemetry that records exceptions (#2699).QuartzRandom.Next(#1937) and the connection manager's dictionary (#1888).IJobDetailfinally works.UseJobStore<T>seam.DailyCalendar's checks; @Skimmenthal13 (#2486) forNOVandDECparsing as themselves; @philr (#2039) for extended properties surviving a change of trigger type; @OronDF343 (#2060) for blob reads on SQL Server.UsePersistentStore<T>; @GhostlyRaven (#2386, #2522) forIHostedLifecycleService; @bdovaz (#2796) for the registration overloads that take anIServiceProvider; @williamdenton (#2335) for disposing a job's scope asynchronously; @BraedonWooding (#1853) for listener registration; @saklig (#1571) for[DisallowConcurrentExecution]on the configurator; @asherber (#1592) forTryGet*and @perringaiden (#2606) forIReadOnlyDictionaryonJobDataMap; @tonyqus (#1613) for property access in place of reflection; @adamsitnik (#2312) for lessBinaryFormatter; @AmirShitrit (#2792) for the scheduler's ownTimeProviderreaching its waits; @unageek (#2788) forInterruptreaching every job it identified; @alanblack (#2004) for the interrupt monitor reading the merged map; @dima-zhemkov (#1995) for what the diagnostic listener is handed.mainwhile 4.0 was being written, and everyone who commented on the 4.0 roadmap (#988) over the years it stayed open.Full changes
Configuration
📅 Schedule: (in timezone Asia/Shanghai)
🚦 Automerge: Enabled.
♻ 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.
This PR was generated by Mend Renovate. View the repository job log.