yaroslav/audition
GitHub: yaroslav/audition
Audition 是一款 Ruby Ractor 兼容性检测与修复工具,通过静态分析和动态探测判断代码能否在 Ractor 中运行并给出修正方案。
Stars: 9 | Forks: 0
# Audition
将它指向一个 Ruby 脚本、一个 gem、一个 Rack 应用或一个 Rails 根目录,它会告诉你该代码是否能在 Ractors 中运行,为什么不能,以及如何修复。与 linter 不同,audition 并不停留在对你的源码进行模式匹配:它还会将目标加载到一个沙箱子进程中,并在实际的活动对象图上观察真实的 `Ractor::IsolationError`。
[](https://github.com/yaroslav/audition/releases)
[](https://rubydoc.info/gems/audition)
- **三种探测,一个结论。** 包含基于文件的 Prism AST 检查,在 [rubydex](https://github.com/Shopify/rubydex) 图谱上进行的全程序语义分析(类级别的状态可跨文件和重新打开进行解析),以及对实际目标的动态 Ractor 内执行。
- **解释原因,而非仅仅标记。** 每个发现都包含一个 `why`(它违反了 Ractor 模型的哪条规则)和一个 `fix`(应该改写成什么)。
- **类似 RuboCop 的 `--fix`,分为两个级别。** 安全修正:为字符串常量添加 `.freeze`,对可变、浅冻结的容器和 Proc 常量使用 `Ractor.make_shareable(...)`,以及在启动时提升方法体内的 `require`。`--fix-unsafe` 增加了影响语义的重写:插入魔法注释,将类缓存改为 `Ractor.store_if_absent`,将 `autoload` 改为 `require`,以及将一次写入的 globals/类变量改为冻结的常量。`--dry-run` 以 diff 形式预览所有更改。
- **感知依赖。** 运行时发现会通过 `const_source_location` 归因于其源码;当你自己的代码很干净但依赖项不干净时,结论将呈现为明确的 `blocked` 状态,因此 `globalid` 不会因为 ActiveSupport 的状态而背锅。
- **终端原生输出。** 支持颜色、字形和 OSC 8 超链接;`path:line` 在支持的终端中可点击。支持用于 CI 的 JSON 输出。
```
$ audition worker.rb
* audition 0.1.0 ruby 4.0.6 · script at .
worker.rb
x raises inside a Ractor: Ractor::IsolationError: can not
access global variable $jobs from non-main Ractor
why: The script ran fine on the main Ractor but failed under
Ractor.new; the static findings usually pinpoint the line.
x worker.rb:1 write to global variable $jobs
why: Non-main Ractors cannot access global variables; this
raises Ractor::IsolationError the moment the line executes
in a Ractor (verified on Ruby 4.0).
fix: Pass the value into the Ractor explicitly
(Ractor.new(value) { |v| ... }) or over a Ractor::Port; for
per-Ractor state use Ractor.current[:key].
x worker.rb:4 read of global variable $jobs
...
dynamic probes
x script probe failed (details above)
summary: 3 errors
verdict: x not ractor-ready
$ echo $?
1
```
以及整个 bundle 的视图:
```
$ audition Gemfile.lock --static-only
╭───────────────┬─────────┬───────────┬────────┬──────────┬─────────╮
│ gem │ version │ verdict │ errors │ warnings │ fixable │
├───────────────┼─────────┼───────────┼────────┼──────────┼─────────┤
│ activesupport │ 8.1.0 │ not ready │ 157 │ 97 │ 77 │
│ i18n │ 1.14.7 │ not ready │ 48 │ 40 │ 45 │
│ mail │ 2.9.1 │ not ready │ 27 │ 4 │ 13 │
│ rack │ 3.2.6 │ not ready │ 23 │ 45 │ 60 │
│ ... │ │ │ │ │ │
╰───────────────┴─────────┴───────────┴────────┴──────────┴─────────╯
0 of 11 gems ractor-ready
```
**严格要求 Ruby 4.0 或更高版本**:该工具的目标是现代 Ractor API(`Ractor::Port`、`Ractor#value`、主 Ractor `require` 代理)及其经过验证的语义。
## 目录
- [安装](#installation)
- [用法](#usage)
- [渐进式采用](#adopting-incrementally)
- [它能检测到什么](#what-it-catches)
- [实战笔记](#field-notes)
- [扩展](#extending)
- [开发](#development)
- [许可证](#license)
## 安装
```
gem install audition
```
或者在 Gemfile 中:
```
gem "audition", require: false
```
## 用法
```
audition worker.rb # a script: static + run inside Ractor
audition my_gem # an installed gem, by name
audition path/to/gem-checkout # a gem working copy (*.gemspec)
audition path/to/rack-app # a config.ru directory
audition path/to/rails-root # a Rails application
audition lib # any directory, static-only
audition Gemfile.lock # sweep every gem in the bundle
audition path/to/app --deps # same, from the app root
```
有用的 flags:
| Flag | 效果 |
| --- | --- |
| `--deps` | 逐个扫描目标的 Gemfile.lock gem |
| `--write-baseline` / `--no-baseline` | 记录 / 忽略已知发现 |
| `--fix` | 应用安全修正,然后重新检查 |
| `--fix-unsafe` | 同时应用影响语义的修正 |
| `--dry-run` | 搭配 fix flag 使用:预览编辑,不进行任何修改 |
| `--format json` | 用于 CI 的机器可读报告 |
| `--format github` | 在 PR diff 上显示 GitHub Actions 批注 |
| `--compare old.json` | 与先前报告的差异:已修复/已引入 |
| `--static-only` / `--dynamic-only` | 选择一个探测层 |
| `--fail-on warning` | 更严格的 CI 门禁(默认:error) |
| `--capabilities` | 当前 Ruby 在 Ractors 中允许的操作列表 |
| `--timeout 60` | 动态探测预算(以秒为单位) |
| `--plain` | 无颜色或超链接(也可通过 NO_COLOR、管道触发) |
退出码:`0` 干净,`1` 存在达到或超过 `--fail-on` 阈值的发现(或动态探测失败),`2` 用法错误。
## 渐进式采用
没有人能在一个 commit 中将 150 个发现减少到零。有三个工具可以让门禁从第一天起就发挥作用:
**Baseline。** 记录今天的发现,然后仅在有新发现时让 CI 失败:
```
audition . --write-baseline # writes .audition-baseline.json
audition . # exit 0; summary shows "N baselined"
```
记录会存储每个文件中每项检查的计数,因此行号偏移永远不会使其失效。`--no-baseline` 会再次显示所有内容。
**内联 pragma。** 静默单行,rubocop 风格:
```
$legacy_flag = true # audition:disable global-variables
risky_call # audition:disable
```
**项目配置。** 位于目标根目录的 `.audition.yml`(CLI flags 始终具有最高优先级):
```
fail_on: warning
timeout: 60
exclude:
- legacy/**
- db/schema.rb
checks:
disable:
- at-exit
```
## 它能检测到什么
静态检查,具有 file:line 级别的精度:
- **全局变量**,带有经过验证的白名单:`$stdout`、`$~`、`$!`、`$VERBOSE` 写入及相关操作仍然是合法的。
- **类变量**,在 rubydex 图谱上解析。
- **类级别的实例变量**,在类体、`def self.` 和 `class << self` 之间统一,且跨文件;经典的 `@cache ||= {}` 缓存。
- **非深度可共享的常量**:裸可变字面量、插值字符串以及微妙的浅冻结(`[[1], [2]].freeze` 仍然会报错;audition 会解释原因)。遵循 `# frozen_string_literal:` 和 `# shareable_constant_value:` 魔法注释。
- **常量中的同步原语和 Procs**(Mutex、Queue、lambdas)。
- **运行时 require 和 autoload**(通过主 Ractor 代理序列化所有 Ractors)。
- **捕获了外部局部变量的 `Ractor.new` 代码块**(创建时的 ArgumentError),通过 Prism 精确的作用域深度进行解析。
- **有敌意或已移除的 API**:`Ractor.yield`/`take`(在 4.0 中已移除)、ActiveSupport 的 `class_attribute`/`cattr_*`/`mattr_*`、`include Singleton`、`fork`、`ObjectSpace._id2ref`、ENV 变更。
动态检查,在活动的 object graph 上:
- 在真实的 Ractor 内运行脚本(通过 `load`,它不会被代理)并报告实际的异常。
- require 一个库,然后用 `Ractor.shareable?` 扫描它引入的每个常量,并检查每个类和模块中的类级别 ivars 和类变量,同时通过 `const_source_location` 进行归因。
- 启动 `config.ru` 并完全在 Ractor 内部响应一个 GET / 请求,即 Ractor Web 服务器的单 worker 模型;然后从 4 个 Ractors x 25 个请求对其进行压测,以发现在并发情况下才会出现的失败。
- 启动 Rails(`config/environment.rb`),eager-load 所有文件,并扫描应用程序的 namespace。
## 实战笔记
对流行 gems 运行 audition 的发现(2026 年 7 月,Ruby 4.0.6):
- **rack 3.2**:`Rack::Builder.parse_file` 完全无法在 Ractor 内运行;`Rack::BUILDER_TOPLEVEL_BINDING` 持有一个不可共享的 Binding。audition 自身的 rack 探测会使用 `Rack::Builder.new` + `instance_eval` 重建应用来绕过这个问题。
- **mail 2.9**:28 个硬性发现,包括 `@@maximum_amount`、`@@autoloads`,以及未冻结的表常量如 `FIELDS_MAP`。
- **globalid 1.3**:自身仅有 6 个发现;报告的其余部分是 ActiveSupport 的状态,在摘要中被归因为依赖错误。
## 扩展
检查是通过一个小巧的声明式 DSL 编写的,并且可以从 gem 外部注册:
```
class NoSleep < Audition::Static::Checks::Base
check_name "no-sleep"
explain :sleepy,
severity: :warning,
message: "sleep inside potential Ractor code",
why: "Blocking one Ractor blocks its whole OS thread.",
fix: "Prefer Ractor::Port#receive with a timeout."
on :call_node do |node|
flag(node, :sleepy) if node.name == :sleep && !node.receiver
end
end
Audition::Static::Checks.register(NoSleep)
```
`on` 生成 Prism visitor 并始终继续遍历;`explain` 条目是一个带有 `%{placeholders}` 的消息目录。
## 开发
```
bundle install
bundle exec rake spec # RSpec suite
bundle exec rake standard # standardrb lint
lefthook install # pre-commit lint hook
bundle exec exe/audition --capabilities
```
静态扫描在大型目标上会进行 Ractor 并行处理(每个核心一个 worker,为主 Ractor 减去一个);audition 自身的 `lib/` 可以通过 `audition lib` 干净地通过检查。
`docs/design.md` 中的设计说明包含了经过经验验证的 Ruby 4.0 Ractor 语义表,各项检查正是根据该校准的。
## 协助
Claude Fable 5.
## 许可证
MIT。详见 LICENSE.txt。
标签:Ractor并发模型, Ruby, 云安全监控, 代码自动修复, 代码质量检测, 批量扫描, 知识库, 静态分析