深入 Ruby E08:后台任务与消息处理:重试怎样改变副作用
执行成功,确认却丢失了 任务要求把累计值增加 7。处理器完成加法,确认消息随后丢失;发送方无法确定结果,于是再次执行任务。没有幂等保护时,两次尝试把累计值变成 14,即使第二次正常返回,也不能据此认定业务只执行过一次。 本篇用 Ruby 3.4.11 的 Queue、Mutex 和 Thread 建立本地替身。它真实记录执行次数、累计值和事件顺序,故障由固定条件注入,不依赖网络偶发失败。完整源码位于 examples/ruby/labs/E08/,附件为 本篇实验源码。 前置内容是异常、测试和并发。实验不连接 Redis,也不安装任务框架;队列、幂等记录和失败事件都仅存在于当前进程内。目标是先明确协议需要保证什么,再讨论框架能力是否覆盖这些要求。 返回值、确认和副作用是三个事件 一次处理可以分成收到任务、执行副作用、确认完成三段。失败发生在不同位置,对重试的要求不同:副作用前失败通常尚未改变目标;副作用后但确认前失败,则结果可能已经存在。 sequenceDiagram participant W as Worker participant S as EffectStor...
深入 Ruby E07:受限规则语言:从 block DSL 到独立解析器
同一条筛选规则,不同的执行权限 任务筛选可以写成 Ruby block,也可以由外部文本描述。两者看起来都在回答“保留哪些任务”,但执行权限不同。block 能修改捕获的数组、调用文件接口或发起网络操作;外部规则只需要比较优先级、状态与标签,没有理由得到整个 Ruby 运行时的能力。 本篇建立一个独立的小语言。示例规则为: 1status == "done" or priority >= 4 and tag == "ruby" 输入三项任务后,结果应包含 ID 1、2。这个结果来自 and 比 or 绑定更紧:已完成任务直接入选,未完成任务必须同时满足优先级和标签。括号可以覆盖默认优先级,not 只否定它后面的一个更紧密表达式。 前置内容是对象协议、DSL、模式匹配和输入边界。实现固定在 Ruby 3.4.11,不添加 gem;完整代码为 examples/ruby/labs/E07/rules.rb 与 run.rb,附件为 本篇实验源码。 先规定语言能够表达什么 实验只接受优先级、状态和标签三类谓词。优先级使用整数与五种比较运算,...
深入 Ruby E06:SQL 与事务:对象状态何时成为数据库事实
回滚之后,对象里的 done 仍然存在 一次任务更新在事务中把状态改为 done,随后抛出异常。数据库回滚到 todo,Ruby Hash 里的状态却仍是 done。换成 ActiveRecord 模型后,本篇实验也观察到相同差异:查询数据库得到 todo,原模型对象的属性仍为 done,调用 reload 后才重新对齐。 事务保护的状态范围因此需要明确。一个 Ruby 方法同时修改数据库、内存对象和外部系统,并不会因为套上事务 block 就获得整体撤销能力。先识别真正参与事务的资源,才能正确解释成功与失败。 本篇以前面的异常、依赖、测试和数据边界为基础。实验使用真实临时 SQLite 文件、两个独立连接和一个最小 ActiveRecord 模型;不需要 Rails 应用。完整源码位于 examples/ruby/labs/E06/,附件为 本篇实验源码。 固定的是驱动加载的数据库版本 基线为 CRuby 3.4.11、sqlite3 gem 2.7.3、SQLite 3.50.3、ActiveRecord 8.0.2。SQLite 版本来自连接执行 SELECT sqlite...
深入 Ruby E05:gem 维护与版本升级的兼容证据
支持范围要由运行结果支撑 先修:21–25、39。核心问题:怎样把 gem 的兼容声明转成可重复检查的证据。实验入口:examples/ruby/labs/E05/run.rb。验收:两个真实 Ruby 实现分别打包、临时安装、运行公开 API 与命令行,再验证本地依赖夹具升级前后的行为差异。版本边界:本机 CRuby 3.4.11 与 JRuby 10.0.5.0,宿主均为 macOS arm64;JRuby 使用 OpenJDK 21.0.10。 gemspec 里的 required_ruby_version 只是安装约束。写上大于等于某个版本,并不会自动证明库在这段范围内都能正确运行。兼容证据必须回答更具体的问题:哪个解释器实际启动过,加载的是源码目录还是安装后的包,公开方法是否保持约定,依赖升级改变了什么。 这个实验使用系列主工程 Taskbook,建立双实现、同一宿主平台的小矩阵。另有一个人工编写的本地依赖 gem,旧版本提供弃用方法,新版本删除该方法,用于演示升级前后如何保存正反差异。这个夹具不对应任何上游库的真实破坏性变更,也不向 RubyGems 发布。 矩阵...
深入 Ruby E04:C扩展与 FFI
文章卡 项目 本篇范围 先修 18–19、22、30、34–35:资源、打包、线程、GC 与实现路径 核心问题 跨语言资源由谁拥有,GC、GVL 与原生同步各保护什么? 实验入口 examples/ruby/labs/E04/run.rb,需要完整 CRuby 头文件、clang 与 make 验收 TypedData 参数/GC/幂等关闭;GVL 管道对照;FFI 真实调用与配对释放;C 原子更新 版本边界 CRuby 3.4.11、Fiddle 1.1.6、Apple clang 21、arm64 macOS;其他平台未实测 原生调用把哪些责任交给调用者 Ruby 对象离开作用域后,关联的 C 缓冲区是否会释放?答案取决于扩展怎样声明引用和所有权。GC 能识别 Ruby 对象,不会自动推断一个任意地址由谁分配、何时仍在使用、应该交给哪个释放函数。FFI 减少包装代码,也不会删除这些责任。 本章在 CRuby 3.4.11 中构建一个 TypedData 扩展,并通过 Fiddle 1.1.6 调用独立 C 动态库。实验只处理合成字节与本地管...
深入 Ruby E03:JRuby 与 JVM
文章卡 项目 本篇范围 先修 21、30、34–36:依赖、线程、GC 与执行路径 核心问题 同一语言契约在两种实现下是否一致,Java 互操作增加哪些边界? 实验入口 examples/ruby/labs/E03/run.rb,安装条件见同目录 INSTALL.md 验收 两进程契约相等;JVM 双线程屏障与关闭;Java 集合、十进制和异常断言 版本边界 CRuby 3.4.11、JRuby 10.0.5.0(Ruby 3.4.5)、OpenJDK 21.0.10;不声明全部应用兼容 相同脚本通过,兼容结论仍有边界 任务筛选脚本在 CRuby 与 JRuby 上都返回「写作」,是否意味着整个 Taskbook 可以直接切换解释器?这个结果只覆盖本次调用。参数绑定、异常、线程同步、依赖平台和运行时扩展各自需要证据。迁移判断应从应用依赖的契约开始,再检查实现特有的能力。 本章固定 CRuby 3.4.11、JRuby 10.0.5.0 和 OpenJDK 21.0.10。JRuby 的固定版本发布说明声明目标为 Ruby 3.4 兼容;实际程序...
深入 Ruby E02:RSpec、契约测试与可重放的生成式测试
测试框架变化,业务契约保持相同 先修:14、23、25。核心问题:更换测试组织方式后,如何证明行为契约与反例检测仍然有效。实验入口:examples/ruby/labs/E02/run.rb。验收:两套真实协议正例通过、边界变异失败,保留一百组生成测试的种子与收缩记录。版本边界:CRuby 3.4.11、Minitest 5.25.5、RSpec 3.13.2,不声称穷尽输入空间。 同一条业务规则,可以写成 Minitest 的测试方法,也可以写成 RSpec 的 example。语法差异不应该导致两套测试验证不同的东西。Taskbook 的筛选协议提供了一个小型对照:优先级等于阈值的任务必须保留,非法阈值必须拒绝,校验后的可变字符串不能与输入共享。 实验同时运行真实 Taskbook 实现与一个故意写错的边界变异。正确实现必须被两套测试接受,错误实现必须被两套测试拒绝。这比比较断言语法更接近迁移测试框架时真正需要的证据:原有行为约束有没有丢失。 环境固定 CRuby 3.4.11、Minitest 5.25.5 与 RSpec 聚合 gem 3.13.2。RSpec 的 cor...
深入 Ruby E01:Rails 与 ActiveSupport 的语言边界
一个请求经过了哪些 Ruby 机制 先修:13、17、20、23、28。核心问题:常量加载、回调与核心类扩展如何成为 Rails 请求行为。实验入口:examples/ruby/labs/E01/run.rb。验收:自动加载前后状态、回调轨迹及二百、四百零一、四百二十二响应均通过断言。版本边界:CRuby 3.4.11,Rails 所选组件 8.1.4,仅进程内请求测试。 Rails 控制器看起来只有几个方法,实际请求还依赖常量加载、继承、类方法注册的回调、参数对象和响应编码。把这些行为全部归给 Ruby,离开框架后就会遇到方法不存在、文件没有加载、回调没有执行等问题。这个实验把同一份 Taskbook 领域代码接到真实 Rails 应用上,用请求结果区分语言能力和框架约定。 实验固定 CRuby 3.4.11,Rails 的 railties、Action Pack、ActiveSupport 均为 8.1.4。只安装请求实验需要的组件,不加载 Active Record,也没有数据库、后台任务或前端构建。具体间接依赖保存在独立 Gemfile.lock;框架补丁版本取自 Ra...
深入 Ruby 39:综合交付与可复现使用
系列导航 导读 · 上一篇:38:输入与安全边界 · 下一篇:E01:Rails 与 ActiveSupport 的语言边界 · 完整源码包 交付物必须离开源码目录工作 在源码目录执行测试通过,只说明当前目录、加载路径和已有依赖共同支持这次运行。gem 安装后使用的是打包清单中的文件,命令行入口由 RubyGems 生成,当前目录也可能完全不同。最后一次验收需要模拟实际使用者从包安装后的路径。 Taskbook 主线提供任务校验、筛选、统计、JSON/CSV 导入导出、命令行入口和一个 Rack 形式的 HTTP 应用。本章把它们连接为独立使用流程:构建本地包,在新目录安装,通过命令行完成数据往返,再从已安装代码处理真实回环 HTTP 请求。整个过程只涉及本地合成任务,不发布 gem。 这一步不替代单元测试和类型检查。单元测试负责细粒度契约,类型工具检查其声明覆盖范围,安装实验负责打包与加载路径,网络实验负责传输边界。各层失败需要对应修复,不能用“能打包”覆盖业务失败,也不能用“HTTP 200”替代数据正确性。 冻结运行环境与文件清单 从 examples/ruby 目录运行:...
深入 Ruby 38:输入与安全边界
系列导航 导读 · 上一篇:37:Ruby 惯用法与必要优化 · 下一篇:39:综合交付与可复现使用 · 完整源码包 外部输入只能选择已授权操作 Ruby 能根据字符串调用方法、执行代码、加载对象和创建进程。这些能力用于内部扩展很方便,但若外部输入可以直接决定目标,就可能把一个“导入任务”接口扩展成任意文件访问或任意代码执行。安全设计需要先列出允许的操作,再把输入约束到这组操作。 Taskbook 的正常输入是任务数据,包含标题、优先级、标签和状态。它不需要用户提交 Ruby 类名、任意方法名或 shell 片段。拒绝这些额外解释能力,可以缩小整个输入面的复杂度,同时让错误处理保持可预测。 本章实验只使用临时目录与合成数据,分别验证文件名越界、命令参数解释、动态调用白名单和 YAML 类标签拒绝。测试证明这些具体边界生效,没有声称代码构成可执行任意敌对程序的沙箱。 文件路径先定义可接受名称 从仓库根目录运行: 1ruby examples/ruby/labs/38/run.rb 实验只接受由小写字母、数字、下划线和连字符组成、以 json 结尾的单层文件名: 12raise Ar...

