
任务调度后端【免费下载链接】quartznetQuartz Enterprise Scheduler .NET项目地址https://gitcode.com/gh_mirrors/qu/quartznet点击查看免费下载导读Quartz.NET 3.6.2 是一个典型的 fix to a fix release对修复的修复3.6.1 在修复DisallowConcurrentExecution()配置物化问题的同时向持久化 Job StoreADO JobStore引入了一处回归导致从数据库读取 Job 详情SqlSelectJobDetail时丢失IS_NONCONCURRENT列进而破坏了基于该标志的 Job 选择逻辑。本文以官方发布说明为骨架结合当前仓库源码完整还原该回归的产生背景、IS_NONCONCURRENT在持久化链路中的关键作用、3.6.2 的修复方式及其测试验证帮助使用持久化 Job Store 的读者理解升级 3.6.2 的必要性与原理。版本背景从 3.6.1 到 3.6.2 的 fix to a fix官方发布说明docs/_posts/2023-02-25-quartznet-3.6.2-released.md开篇即点明This is fix to a fix release, 3.6.1 introduced a regression to job selection logic when using persistent job store.即3.6.2 是 3.6.1 的修复版3.6.1 在使用持久化 Job Store 时引入了 Job 选择逻辑的回归。要理解这次回归需要先回顾 3.6.1 做了什么。3.6.1 的发布说明docs/_posts/2023-02-25-quartznet-3.6.1-released.md列出三项修复Add missing disallow concurrency materialization for jobs (#1923)——修复了使用 Job Builder 的DisallowConcurrentExecution()配置并发禁止、但 Job 类型本身没有标注DisallowConcurrentExecutionAttribute属性时该标志没有被正确物化持久化的问题Allow accessing the wrapped scoped job instance from job execution context (#1917)JobDiagnosticsWriter can throw error when writing context data (#1191)。也就是说3.6.1 补上了写入端的物化逻辑让DisallowConcurrentExecution()配置能被持久化到数据库但同一个修复过程中读取端的 SQL 语句SqlSelectJobDetail在改动列清单时漏掉了IS_NONCONCURRENT列形成了写进去却读不出来的不对称从而在持久化 Job Store 场景下破坏了 Job 选择逻辑。3.6.2 的唯一修复项正是针对这个缺口Fix SqlSelectJobDetail to include IS_NONCONCURRENT (#1927)回归的本质持久化 Job Store 的 Job 选择逻辑依赖IS_NONCONCURRENTQuartz.NET 的持久化 Job StoreAdoJobStore将 Job 与 Trigger 的元数据存放在数据库表中。其中QRTZ_JOB_DETAILSJOB_DETAILS表保存 Job 的属性快照包括两个行为标志列它们记录了 Job 在存储时刻的两个关键属性IS_NONCONCURRENT——对应IJobDetail.ConcurrentExecutionDisallowed即是否禁止并发执行来源通常是[DisallowConcurrentExecution]特性或JobBuilder.DisallowConcurrentExecution()IS_UPDATE_DATA——对应IJobDetail.PersistJobDataAfterExecution执行后是否持久化 JobDataMap 更新。这两个标志的列定义可见于各数据库的建表脚本例如 src/Quartz/Impl/AdoJobStore/Schema/create_sqlServer.sql 中QRTZ_JOB_DETAILS表包含IS_DURABLE、IS_NONCONCURRENT、IS_UPDATE_DATA、REQUESTS_RECOVERY等列QRTZ_FIRED_TRIGGERS已触发记录表中也冗余了IS_NONCONCURRENT、REQUESTS_RECOVERY列见同文件 L197-L201以便调度器在恢复、并发判定时不依赖 Job 程序集即可读取标志。仓库根目录下同样提供全量建表脚本 database/tables/tables_sqlServer.sql以及 MySQL、SQLite、Oracle、PostgreSQL、Firebird 各方言版本。为什么Job 选择逻辑需要这两个标志从源码看ConcurrentExecutionDisallowed至少在两个关键决策点被使用添加 Trigger 时的状态判定AdoJobStoreBase.AddTrigger需要依据IJobDetail.ConcurrentExecutionDisallowed决定新 Trigger 进入WAITING还是BLOCKED状态。源码注释src/Quartz/Impl/AdoJobStore/StdAdoConstants.cs明确指出这两个标志必须在没有 Job 的 CLR 类型的情况下也能回答否则在一个无法加载该 Job 程序集如集群中的其他节点的进程中回退到从类型反射读取属性会得到false导致非并发 Job 被误判为允许并发即 #3705 场景。Trigger 获取Acquire时的集群安全并发检查src/Quartz/Impl/AdoJobStore/AdoJobStoreBase.Acquisition.cs 中当job.ConcurrentExecutionDisallowed为真时会通过Delegate.IsJobCurrentlyExecuting查询FIRED_TRIGGERS表若该 Job 已在其他节点执行则直接拒绝本次触发——这是跨节点禁止并发的核心保障。若读取端丢掉了IS_NONCONCURRENT该检查将被跳过或错误判断。由此可见IS_NONCONCURRENT的缺失不是简单的少查一列而是会让持久化模式下禁止并发执行这一语义整体失效——这正是 3.6.1 回归被称为job selection logic regression的原因。修复对象SqlSelectJobDetail与共享列清单3.6.2 的修复落在StdAdoConstants.SqlSelectJobDetail上。当前仓库中该语句的定义src/Quartz/Impl/AdoJobStore/StdAdoConstants.cs为public static readonly string SqlSelectJobDetail Invariant($SELECT {JobDetailSelectColumns} FROM {TablePrefixSubst}{AdoConstants.TableJobDetails} WHERE {AdoConstants.ColumnSchedulerName} {SqlParameters.SchedulerName} AND {AdoConstants.ColumnJobName} {SqlParameters.JobName} AND {AdoConstants.ColumnJobGroup} {SqlParameters.JobGroup});关键的工程细节在于SqlSelectJobDetail与批量查询SqlSelectJobDetailsByKeysPrefix共享同一个列清单常量JobDetailSelectColumnssrc/Quartz/Impl/AdoJobStore/StdAdoConstants.csprivate const string JobDetailSelectColumns ${AdoConstants.ColumnJobName},{AdoConstants.ColumnJobGroup},{AdoConstants.ColumnDescription},{AdoConstants.ColumnJobClass},{AdoConstants.ColumnIsDurable},{AdoConstants.ColumnRequestsRecovery},{AdoConstants.ColumnJobDataMap},{AdoConstants.ColumnIsNonConcurrent},{AdoConstants.ColumnIsUpdateData};该清单现在完整包含IS_NONCONCURRENT与IS_UPDATE_DATAColumnIsNonConcurrent、ColumnIsUpdateData的常量定义见 src/Quartz/Impl/AdoJobStore/AdoConstants.cs。源码注释特别强调了两点约束单查询与批量查询共用列清单防止两条读取路径漂移drift apart——3.6.1 的回归正是这一类漂移的典型列顺序即序数ordinal契约JOB_DATA位于位置 6行读取器ReadJobDetail在CommandBehavior.SequentialAccess模式下按序读取列因此新列只能追加到清单末尾绝不能插入中间见 src/Quartz/Impl/AdoJobStore/StdAdoConstants.cs。读取链路从数据库行到IJobDetailSqlSelectJobDetail由StdAdoDelegate.SelectJobDetail执行src/Quartz/Impl/AdoJobStore/StdAdoDelegate.Jobs.cs执行时以SequentialAccess模式读取结果集并交给共享的ReadJobDetail方法src/Quartz/Impl/AdoJobStore/StdAdoDelegate.Jobs.cs构造IJobDetailjobBuilder.DisallowConcurrentExecution(GetBooleanFromDbValue(rs.GetValue(ordinals.IsNonConcurrent))) .PersistJobDataAfterExecution(GetBooleanFromDbValue(rs.GetValue(ordinals.IsUpdateData)));这里正是修复生效的落点从行的IS_NONCONCURRENT列直接读出布尔值通过JobBuilder.DisallowConcurrentExecution(bool)src/Quartz/JobBuilder.cs物化到 Job Detail 上而不是回退到反射读取类型上的特性。JobDetailRowOrdinalssrc/Quartz/Impl/AdoJobStore/StdAdoDelegate.Jobs.cs预先解析各列序数避免逐行重复GetOrdinal。与SelectJobDetail平行的还有另一条更轻量的读取路径StdAdoDelegate.SelectJobForTriggersrc/Quartz/Impl/AdoJobStore/StdAdoDelegate.Jobs.cs它执行SqlSelectJobForTrigger同样直接以DisallowConcurrentExecution(GetBooleanFromDbValue(rs[AdoConstants.ColumnIsNonConcurrent]))从行中读取标志注释明确写道列是 Job 在存储时刻属性的记录一个无法加载该类从而按类型反射会得到 false的进程不应被告知一个非并发 Job 允许并发#3705。AdoJobStoreBase的多处操作如 AdoJobStoreBase.Store.cs、L681、L812都依赖这条路径在不加载 Job 类型loadJobType: false的情况下拿到准确的标志值。一句话总结修复闭环写入端3.6.1#1923保证DisallowConcurrentExecution()配置被持久化到IS_NONCONCURRENT读取端3.6.2#1927保证SqlSelectJobDetail把该列重新读回IJobDetail.ConcurrentExecutionDisallowed。两个版本合在一起才构成完整的物化—读取链路。ConcurrentExecutionDisallowed在调度链路中的关键作用为了理解为什么这一列如此关键可以从调度执行的主链路看它的三个使用场景Trigger 状态机AdoJobStoreBase.AddTrigger依据ConcurrentExecutionDisallowed决定新 Trigger 是WAITING还是BLOCKED。标志丢失意味着非并发 Job 的 Trigger 不会被置为BLOCKED并发执行保护从源头失效。集群安全的获取检查如前述AdoJobStoreBase.Acquisition.cs 在获取 Trigger 时对禁止并发的 Job 查询FIRED_TRIGGERS发现已有实例在执行则拒绝本次触发从而跨节点保证同一时刻只有一个实例。这里读到的job正是来自SelectJobForTrigger/SelectJobDetail的行数据。执行完成/恢复路径AdoJobStoreBase.Completion.cs中 Job 执行完成后同样依据jobDetail.ConcurrentExecutionDisallowed决定是否解除同 Job 其他 Trigger 的BLOCKED状态src/Quartz/Impl/AdoJobStore/AdoJobStoreBase.Completion.cs。可见ConcurrentExecutionDisallowed贯穿添加 Trigger → 获取 Trigger → 执行完成的完整生命周期。任何一处读取到错误值都会让禁止并发执行这一 Job 配置在持久化部署尤其是集群中静默失效。测试验证列清单被测试锁定仓库的单元测试将SqlSelectJobDetail的列清单锁定为硬性契约。src/Quartz.Tests.Unit/Impl/AdoJobStore/StdAdoDelegateTest.cs 中构造的预期 SQL 明确包含REQUESTS_RECOVERY, JOB_DATA, IS_NONCONCURRENT, IS_UPDATE_DATA FROM QRTZ_JOB_DETAILS ...即当前测试期望的 SELECT 列集合正是修复后包含IS_NONCONCURRENT与IS_UPDATE_DATA的完整清单。此外持久化标志的从列读取而非反射语义还有专门的测试覆盖src/Quartz.Tests.Unit/Impl/AdoJobStore/SelectJobForTriggerFlagsSqliteTest.cs 断言ConcurrentExecutionDisallowed为真并注明IS_NONCONCURRENT是 Job 存储时特性的记录无需加载特性所在程序集即可读取src/Quartz.Tests.Unit/Impl/AdoJobStore/AdministrationNodeSqliteTest.cs 同样验证了该语义并在 L265-L268 的注释中警告若改为从类型推导标志会在一份无法看到 Job 类的 Job Detail 拷贝上错误清除IS_NONCONCURRENT——正是 3.6.1 回归要避免的静默损坏。升级建议与验证方法对于使用持久化 Job StoreADO JobStore无论单机还是集群的用户强烈建议从 3.6.1 升级到 3.6.2特别是通过JobBuilder.DisallowConcurrentExecution()而非[DisallowConcurrentExecution]特性配置并发禁止、且依赖持久化存储恢复 Job 元数据的场景。3.6.1 下这类 Job 在持久化读取后可能丢失禁止并发标志导致并发保护失效本修复不涉及数据库 Schema 变更IS_NONCONCURRENT列自 2.x 起就存在于各方言建表脚本中例如 src/Quartz/Impl/AdoJobStore/Schema/create_sqlite.sql因此无需执行迁移脚本升级程序集后重启即可生效升级后建议按仓库测试同等的思路做一次冒烟验证在持久化 Job Store 下创建一个通过JobBuilder.DisallowConcurrentExecution()配置、且类型上无特性标注的 Job触发一次执行后重启调度器再触发第二次确认两次执行不会并发重叠也可直接查阅 SelectJobForTriggerFlagsSqliteTest.cs 了解标志持久化—读取的断言方式。小结Quartz.NET 3.6.2 表面上只是一个为修复而修复的小版本但其背后是一堂关于持久化状态一致性的工程课Job 的行为标志ConcurrentExecutionDisallowed、PersistJobDataAfterExecution必须以数据库列作为唯一事实来源写入端与读取端必须共享同一列契约并且由测试锁定该契约——正如JobDetailSelectColumns共享常量与其注释所强调的那样。对于任何以持久化方式运行 Quartz.NET 的集群升级到 3.6.2 意味着禁止并发执行语义在完整生命周期内不再存在读写断点。赞分享任务调度后端【免费下载链接】quartznetQuartz Enterprise Scheduler .NET项目地址https://gitcode.com/gh_mirrors/qu/quartznet点击查看免费下载相关推荐Mosquitto 1.4.14 发布解读持久化数据保存回归的修复与规避升级指南Mosquitto 1.4.14 发布解读持久化数据保存回归的修复与规避升级指南 Mosquitto 1.4.14 是一次针对持久化功能的紧急 bugfix物联网消息队列后端Pillow 4.1.1 修复解读EXIF 中的 DPI 读取回归与 CPython 3.6 二进制兼容性Pillow 4.1.1 修复解读EXIF 中的 DPI 读取回归与 CPython 3.6 二进制兼容性 本篇技术指南围绕 Pillow 4.1.1 发布说图像处理计算机视觉RVC 变声教程从装环境到转出第一个 AI 音色5 步跑通RVC 变声教程从装环境到转出第一个 AI 音色5 步跑通 RVCRetrieval based Voice Conversion WebUI检索式语音人工智能AI 应用语音音频深度学习上一篇Docker-Android系统 requirements详解硬件与软件要求下一篇Tiny Care Terminal社区资源有用的插件和第三方工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考