expect-pty 0.5.3 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +55 -1
- data/README.md +122 -130
- data/docs/API.md +113 -0
- data/docs/MIGRATION.md +68 -0
- data/lib/expect/cleanup.rb +34 -0
- data/lib/expect/interaction.rb +228 -210
- data/lib/expect/logging.rb +117 -162
- data/lib/expect/matcher.rb +42 -38
- data/lib/expect/pattern.rb +10 -3
- data/lib/expect/pattern_list.rb +39 -9
- data/lib/expect/redactor.rb +12 -5
- data/lib/expect/relay.rb +106 -81
- data/lib/expect/relay_writer.rb +7 -5
- data/lib/expect/result.rb +33 -9
- data/lib/expect/session.rb +554 -0
- data/lib/expect/session_resources.rb +19 -18
- data/lib/expect/version.rb +2 -2
- data/lib/expect.rb +68 -578
- data/sig/expect.rbs +279 -0
- metadata +14 -123
- data/.rubocop.yml +0 -66
- data/CONTRIBUTING.md +0 -29
- data/Gemfile +0 -28
- data/Rakefile +0 -38
- data/benchmark/matching.rb +0 -98
- data/benchmark/redactor.rb +0 -64
- data/benchmark/relay.rb +0 -48
- data/benchmark/scaling.rb +0 -70
- data/benchmark/send_slow.rb +0 -41
- data/benchmark/support.rb +0 -114
- data/docs/COMPATIBILITY.md +0 -98
- data/docs/INTERNAL_CONTRACTS.md +0 -154
- data/docs/PERFORMANCE.md +0 -165
- data/docs/RELEASING.md +0 -84
- data/docs/VERIFICATION.md +0 -456
- data/examples/dialogue.rb +0 -30
- data/examples/kibitz/README.md +0 -81
- data/examples/kibitz/kibitz.rb +0 -142
- data/examples/kibitz/test_kibitz.rb +0 -37
- data/examples/ssh_auto.rb +0 -94
- data/examples/ssh_interact.rb +0 -159
- data/examples/ssh_login.rb +0 -64
- data/expect-pty.gemspec +0 -33
- data/lib/expect/configuration.rb +0 -122
- data/lib/expect/terminal.rb +0 -102
- data/script/ci +0 -121
- data/script/release.rb +0 -319
- data/test/buffer_accounting_test.rb +0 -83
- data/test/cleanup_test.rb +0 -251
- data/test/compare_upstream.rb +0 -157
- data/test/configuration_test.rb +0 -133
- data/test/deadline_test.rb +0 -237
- data/test/diagnostics_test.rb +0 -353
- data/test/edge_case_test.rb +0 -262
- data/test/fixtures/ssh_scripts/01_identity.sh +0 -4
- data/test/fixtures/ssh_scripts/02_output.sh +0 -5
- data/test/fixtures/ssh_scripts/03_delayed.sh +0 -6
- data/test/fixtures/ssh_scripts/04_failure.sh +0 -2
- data/test/fixtures/ssh_scripts/05_recovery.sh +0 -3
- data/test/initialization_failure_test.rb +0 -92
- data/test/integration/README.md +0 -109
- data/test/integration/ssh_scripts.rb +0 -94
- data/test/interact_test.rb +0 -253
- data/test/interconnect_test.rb +0 -425
- data/test/io_test.rb +0 -321
- data/test/kibitz_test.rb +0 -45
- data/test/lifecycle_contract_test.rb +0 -71
- data/test/literal_scan_test.rb +0 -97
- data/test/matching_test.rb +0 -211
- data/test/multi_session_test.rb +0 -66
- data/test/ownership_sequence_test.rb +0 -208
- data/test/pattern_offset_test.rb +0 -43
- data/test/process_interruption_test.rb +0 -296
- data/test/process_test.rb +0 -291
- data/test/redactor_test.rb +0 -183
- data/test/relay_recovery_test.rb +0 -451
- data/test/relay_reentrancy_test.rb +0 -159
- data/test/release_test.rb +0 -375
- data/test/ruby_api_test.rb +0 -515
- data/test/scan_reuse_test.rb +0 -109
- data/test/script_logging_test.rb +0 -124
- data/test/support/interact_probe.rb +0 -125
- data/test/support/kibitz_probe.rb +0 -177
- data/test/support/script_probe.rb +0 -158
- data/test/terminal_cleanup_test.rb +0 -349
- data/test/test_helper.rb +0 -58
- data/test/timeout_test.rb +0 -289
- data/test/write_contract_test.rb +0 -105
data/docs/API.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Ruby API
|
|
2
|
+
|
|
3
|
+
支持 Ruby 3.4+ 和 Linux/macOS。加载入口为 `require "expect/pty"`,独立流过滤器可加载 `expect/redactor`。
|
|
4
|
+
`Expect` 是模块;`Expect::Session` 是公开的真实会话。工厂、回调、链式方法与 `Result#session` 使用同一个 Session。
|
|
5
|
+
|
|
6
|
+
## 创建与关闭
|
|
7
|
+
|
|
8
|
+
`Expect.spawn(*command, env: {}, chdir: nil, raw: false, graceful: false, **settings)` 创建并启动 PTY。
|
|
9
|
+
`settings` 是下文列出的六个显式会话关键字的统称,并非任意配置 Hash。需要预设终端时先 `Expect::Session.new(**settings)`,
|
|
10
|
+
通过 `session.slave` 使用原生终端方法,再调用 `session.spawn(*command, env: {}, chdir: nil, raw: false)`。
|
|
11
|
+
Session 构造器不接收命令或操作参数。同一会话只能启动一次。
|
|
12
|
+
|
|
13
|
+
单字符串遵循 Ruby 的自动 shell 语义;多个独立参数按 argv 执行。外部输入应作为独立参数传入。
|
|
14
|
+
启动失败抛出 `Expect::SpawnError`;工厂失败时回收尚未交付的会话。
|
|
15
|
+
|
|
16
|
+
`Expect.open(io, writer: io, own: false, graceful: false, **settings)` 适配真实可 select 的 IO,默认借用。
|
|
17
|
+
`own: true` 接管关闭责任,包括初始化中已知设置的非法值导致的失败;未知关键字在 Ruby 方法入口拒绝,此时不接管 IO。
|
|
18
|
+
`spawn`、`open` 无块时返回 Session,有块时返回块结果并确保按 `graceful:` 关闭;非局部返回也清理。
|
|
19
|
+
清理中的 StandardError 不覆盖本次作用域已有的主异常;没有主异常时照常传播。新发生的 Interrupt/SystemExit 不会被清理包装吞掉。
|
|
20
|
+
|
|
21
|
+
`close(graceful: false)` 最终硬关闭兜底,返回 nil;`soft_close(timeout: 15, term_timeout: 1)` 收取尾部、关闭句柄并最多发送
|
|
22
|
+
TERM;
|
|
23
|
+
`hard_close(timeout: 0.2)` 关闭句柄后分阶段等待、TERM、KILL。后两者和 `wait(timeout: nil)` 返回真实 `Process::Status` 或
|
|
24
|
+
nil。
|
|
25
|
+
借用 IO 不随会话关闭。输入 EOF、IO 关闭与子进程退出分别由 `eof?`、`closed?`、`process_status` 表示。
|
|
26
|
+
|
|
27
|
+
## 匹配与回调
|
|
28
|
+
|
|
29
|
+
`session.expect(*patterns, timeout: session.timeout, deadline: nil, consume: true, reset_timeout_on_read: false)` 返回不可变
|
|
30
|
+
`Expect::Result`。
|
|
31
|
+
`Expect.expect` 使用相同操作关键字,另加 `from:` 指定默认来源;其 timeout 默认 nil。模式为 String、Regexp、`:eof` 或
|
|
32
|
+
`:timeout`,
|
|
33
|
+
位置模式与声明块不能混用。`number` 为从 1 开始的文本模式序号或 nil,事件声明也占序号;使用 `matched?` 判断成功。
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
result = session.expect(timeout: 3) do |patterns|
|
|
37
|
+
patterns.on("name: ") do |source|
|
|
38
|
+
source.puts("Ruby")
|
|
39
|
+
Expect.continue
|
|
40
|
+
end
|
|
41
|
+
patterns.on(/hello (\w+)/)
|
|
42
|
+
end
|
|
43
|
+
result => { number:, captures: }
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
无参数声明块在 PatternList 上运行,直接调用 `on`、`eof`、`timeout`;有参数块保留调用者 self。
|
|
47
|
+
注册完成后模式与分组冻结,借用的会话与回调不冻结。相邻同来源组按对象身份及顺序合并,优先级为组、会话、模式;不按文本位置重排。
|
|
48
|
+
`on`、`eof` 回调接收 Session;`timeout` 接收全部活跃来源。只有 `Expect.continue(reset_timeout: true)` 的控制符使等待继续,
|
|
49
|
+
`reset_timeout: false` 保持原相对期限。全部 EOF 后直接返回 EOF;已处理的来源不再参与超时。
|
|
50
|
+
|
|
51
|
+
`consume: false` 仅在本轮保留完整匹配缓冲;继续但不改变缓冲的模式暂停到输入变化,避免反复触发。
|
|
52
|
+
`reset_timeout_on_read: true` 使每次读取重置本轮相对期限。`deadline` 是单调时钟绝对秒数,不被输入或回调延长。
|
|
53
|
+
IO 期限不打断用户代码或单次正则;不可信正则应使用 Ruby `Regexp` 自身的 timeout。
|
|
54
|
+
|
|
55
|
+
Result 字段为 `number`、`error`、`match`、`before`、`after`、`session`、`captures`。对象、文本及捕获数组均不可变,未参与捕获为
|
|
56
|
+
nil。
|
|
57
|
+
错误为 nil、`:timeout`、`:eof` 或原始 IO 异常;来源会话与异常不复制、不冻结。使用 `matched?`、`timeout?`、`eof?` 查询,
|
|
58
|
+
使用 `to_h`、位置/键模式解构或 `with` 构造新结果;没有字段 writer 或 `to_a`。
|
|
59
|
+
会话的 `last_result`、`before`、`after`、`match`、`match_number`、`captures`、`error` 读取最近结果。
|
|
60
|
+
|
|
61
|
+
匹配、捕获按原始字节保存;文本显示时先 `dup` 再 `force_encoding`。固定 UTF-8 正则等待尾部字符收齐,非法编码抛出
|
|
62
|
+
EncodingError。
|
|
63
|
+
成功默认消费匹配前缀,超时保留缓冲,EOF 将余下内容放入 before 并清空缓冲。EOF 不表示子进程已经退出。
|
|
64
|
+
|
|
65
|
+
## 会话属性与读写
|
|
66
|
+
|
|
67
|
+
不提供全局配置或配置对象。构造器、工厂显式接受以下设置,会话可通过同名 reader/writer 修改;非法更新保留原值。
|
|
68
|
+
|
|
69
|
+
| 设置 | 默认值 | 语义 |
|
|
70
|
+
|------------------------|--------|--------------------------------------|
|
|
71
|
+
| timeout、write_timeout | nil | 非负有限秒数;nil 无限,0 非阻塞尝试 |
|
|
72
|
+
| buffer_limit | nil | 正整数尾部字节上限,nil 无限 |
|
|
73
|
+
| logger | nil | 借用 Ruby Logger 的 add/debug? 协议 |
|
|
74
|
+
| transcript | nil | 借用 write 协议,记录真实接收字节 |
|
|
75
|
+
| outputs | [] | 复制可写目标数组,原样转发接收字节 |
|
|
76
|
+
|
|
77
|
+
操作参数 `raw`、`consume`、`reset_timeout_on_read`、`graceful` 不持久化为会话设置。
|
|
78
|
+
`buffer` 返回副本,`buffer=` 复制并应用上限,`clear_buffer` 移交并清空缓冲;`buffer_discarded_bytes` 累计窗口裁剪量,匹配消费不计入。
|
|
79
|
+
|
|
80
|
+
`write(*objects)` 按 to_s 写字节并返回字节数,`<<` 返回 Session,`puts` 遵循 Ruby IO 换行与数组规则并返回 nil。
|
|
81
|
+
`send_slow(*objects, delay:)` 按字符延迟发送并收取回复。背压超时抛出 `Expect::WriteTimeout`,`bytes_written` 仅标识本次调用已确认进度,
|
|
82
|
+
嵌套写入异常放在 cause。成功短写不受 write_timeout 的总耗时限制。
|
|
83
|
+
`to_io`、`writer`、`slave` 暴露句柄;`pid`、`command`、`tty_name`、`fileno`、`tty?`、`alive?`、`exit_code` 查询会话属性。
|
|
84
|
+
终端直接使用 `session.to_io.console_mode`、`echo=`、`winsize` 等 Ruby io/console 接口,没有 stty 或窗口尺寸包装方法。
|
|
85
|
+
|
|
86
|
+
## 诊断、脱敏与转接
|
|
87
|
+
|
|
88
|
+
`logger=` 接收 nil 或支持 `add` / `debug?` 的对象,直接兼容 Ruby Logger 及符合此协议的 ActiveSupport logger;不依赖
|
|
89
|
+
ActiveSupport。
|
|
90
|
+
Logger 自身控制级别和格式。INFO 记录生命周期/匹配,DEBUG 增加收发字节;`add` 接收冻结事件 Hash
|
|
91
|
+
`{ event:, pid:, fd:, message: }` 和 progname `"Expect"`,message 字符串也冻结。
|
|
92
|
+
|
|
93
|
+
`transcript=` 接收 nil 或 writer;路径打开、权限和关闭由调用方通过 File.open 管理,不接收路径或 callable。
|
|
94
|
+
`write_transcript(*objects)` 补写接收记录并返回 nil;不发送到子进程。writer 必须返回实际接受的正整数字节数,支持短写。
|
|
95
|
+
logger、transcript、outputs 一律借用,显式关闭冲刷过滤尾部,但不会关闭这些目标。
|
|
96
|
+
|
|
97
|
+
`redact(*secrets)` 追加非空字符串,仅过滤 transcript 和诊断,不更改匹配、Result 或 outputs。
|
|
98
|
+
`Expect::Redactor` 提供 `append`、`finish(partial: true)`、`patterns=` 和完整文本类方法 `redact`;流尾部默认隐藏疑似秘密前缀。
|
|
99
|
+
|
|
100
|
+
`outputs=` 校验并复制可写目标数组,读取返回副本;`$stdout` 与其他 writer 一样显式加入。
|
|
101
|
+
`Expect.interconnect(*sources, timeout: nil)` 按 outputs 转接,返回停止来源或在总期限到达时返回 nil。
|
|
102
|
+
`on_sequence(String/Regexp/:eof) { ... }` 注册无参数回调,未给块或返回 nil/false 停止,其余值继续。
|
|
103
|
+
`pending_output?` 表示尚未交付数据;再次转接同源会话继续发送,不重放成功前缀;更换 outputs 不改变已排队字节的目标。
|
|
104
|
+
同源递归转接抛出 ReentrancyError;转义回调可嵌套匹配,所有来源状态按对象身份隔离。
|
|
105
|
+
|
|
106
|
+
`interact(input: $stdin, escape: nil, output: nil, timeout: nil, raw: true)` 临时建立双向转接,退出后恢复
|
|
107
|
+
outputs、转义和本地终端模式。
|
|
108
|
+
`output: nil` 使用默认输出,`escape: nil` 不注册退出序列;显式 false 不是缺省值,非法输出或转义会在转发前报错。
|
|
109
|
+
`raw: false` 将终端设置留给调用方。transcript、logger 及自定义 writer 同步执行,应及时返回。
|
|
110
|
+
|
|
111
|
+
`Expect.monotonic` 读取单调时钟,`Expect.duration` 校验秒数,`Expect.readable_sessions(*sources, timeout: 0)` 返回就绪来源。
|
|
112
|
+
Matcher、Relay、资源账本和标为 `@api private` 的 Session 协作方法不是用户契约。RBS 覆盖公开声明;`rbs validate` 不验证 Ruby
|
|
113
|
+
方法体。
|
data/docs/MIGRATION.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# 迁移说明
|
|
2
|
+
|
|
3
|
+
## 0.6.x → 0.7.0
|
|
4
|
+
|
|
5
|
+
0.7.0 主动移除门面和旧配置接口,不提供兼容别名。最低版本仍为 Ruby 3.4,`require "expect/pty"` 和不可变 Result 保持不变。
|
|
6
|
+
|
|
7
|
+
| 0.6.x | 0.7.0 | 迁移含义 |
|
|
8
|
+
|------------------------------------------------------|--------------------------------------------------|----------------------------------------------------------------|
|
|
9
|
+
| `Expect` 类与外层会话门面 | `Expect` 模块、公开 `Expect::Session` | 工厂、匹配回调、Result.session 和链式返回同一个 Session |
|
|
10
|
+
| `Expect.new(...)` | `Expect::Session.new(**settings)` | 仅预建 PTY;命令及 env/chdir/raw 交给之后的 session.spawn |
|
|
11
|
+
| `Expect.spawn` / `Expect.open` 返回 Expect | 返回 Expect::Session | 类型判断与 RBS 引用改为 Session;有块仍返回块结果 |
|
|
12
|
+
| `Expect.configure`、`configuration`、`Configuration` | 显式会话关键字与属性 | 应用共享默认值自行保存 Hash,调用时展开;不再继承类级配置 |
|
|
13
|
+
| `raw_pty: true` / `raw_pty=` | `spawn(raw: true)` | 每次启动的子终端操作参数 |
|
|
14
|
+
| `preserve_buffer = true` | `expect(consume: false)` | 每轮等待设置;默认仍消费成功匹配前缀 |
|
|
15
|
+
| `reset_timeout_on_read = true` | `expect(reset_timeout_on_read: true)` | 只作用于本轮相对期限,deadline 仍不可延长 |
|
|
16
|
+
| `raw_terminal = false` | `interact(raw: false)` | 只作用于本次人工接管 |
|
|
17
|
+
| `graceful_close = true` | 工厂 `graceful: true` 或 `close(graceful: true)` | 块清理策略与直接关闭均显式指定 |
|
|
18
|
+
| `session.continue(...)` | `Expect.continue(...)` | 继续控制符只由模块提供 |
|
|
19
|
+
| `listeners`、`log_listeners`、`log_stdout` | `outputs` writer 数组 | 把 `$stdout` 显式加入;清空或替换数组控制转发 |
|
|
20
|
+
| `debug_level`、`diagnostic_output` | `logger` / `logger=` | Ruby Logger 的 add/debug? 协议;级别、格式和输出由 logger 管理 |
|
|
21
|
+
| 诊断回调事件中的 `level` | Logger add 的 severity 参数 | 事件 Hash 保留 event/pid/fd/message;不再生成 buffer 诊断 |
|
|
22
|
+
| `log_output`、`log_to` | `transcript` / `transcript=` | 只接收 writer;文件打开、权限、模式和关闭由调用方负责 |
|
|
23
|
+
| `write_log(*objects)` | `write_transcript(*objects)` | 补写记录,不发送字节,固定返回 nil |
|
|
24
|
+
| 路径日志、callable 日志 | `File.open` 或实现 write 的目标 | 库不拥有日志文件;write 必须返回已接受字节数 |
|
|
25
|
+
| `session.stty(...)` | `session.to_io` 的 io/console 方法 | 原生 raw/cooked/echo=/console_mode;不再启动辅助命令 |
|
|
26
|
+
| `session.winsize` / `winsize=` | `session.to_io.winsize` / `winsize=` | 原生窗口尺寸操作 |
|
|
27
|
+
|
|
28
|
+
显式关键字只接受 timeout、write_timeout、buffer_limit、logger、transcript、outputs 六个会话设置。
|
|
29
|
+
未知关键字在 Ruby 方法入口抛出 ArgumentError,因此 `Expect.open(..., own: true, unknown: value)` 不接管 IO;
|
|
30
|
+
已知设置的非法值在初始化中失败,仍清理 own:true 的端点。logger、transcript、outputs 始终借用,关闭会话不关闭目标。
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
require "expect/pty"
|
|
34
|
+
require "logger"
|
|
35
|
+
|
|
36
|
+
defaults = { timeout: 5, buffer_limit: 65_536 }.freeze
|
|
37
|
+
logger = Logger.new($stderr, level: Logger::INFO)
|
|
38
|
+
File.open("session.log", File::WRONLY | File::CREAT | File::APPEND, 0o600) do |transcript|
|
|
39
|
+
Expect.spawn("/bin/sh", "-i", **defaults, logger: logger, transcript: transcript,
|
|
40
|
+
outputs: [$stdout], raw: true) do |session|
|
|
41
|
+
session.puts("printf 'ready\\n'")
|
|
42
|
+
session.expect("ready", consume: false, reset_timeout_on_read: true)
|
|
43
|
+
session.clear_buffer
|
|
44
|
+
session.puts("exit")
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
兼容 `add` / `debug?` 的 ActiveSupport logger 可直接注入,不需要为本库引入 ActiveSupport。
|
|
50
|
+
需要自定义诊断格式时设置 logger.formatter;需要自定义接收记录时实现 writer.write (String) 并返回已接受字节数。
|
|
51
|
+
|
|
52
|
+
内部 Session 协作协议通过 YARD `@api private` 标注,不因为 Ruby 可调用就成为发布契约。不要再访问门面实例变量或依赖旧配置类。
|
|
53
|
+
会话可以被应用扩展,但多个对象即使定义相同的 `==` / `eql?` / `hash`,仍是各自独立的读取来源与生命周期。
|
|
54
|
+
|
|
55
|
+
## 0.5.x → 0.6.0(历史)
|
|
56
|
+
|
|
57
|
+
从 0.5.x 直接升级到 0.7.0 时,先理解以下结果值变更,再应用上面的接口表。
|
|
58
|
+
|
|
59
|
+
- 最低运行环境改为 Ruby 3.4;支持 Ruby 3.4 和 4.0 的 Linux/macOS 组合。
|
|
60
|
+
- Result 从可变 Struct 改为不可变 Data;`result.match = value` 改为 `result.with(match: value)`。
|
|
61
|
+
- `result.to_a` 改为位置模式或优先使用键模式,如 `result => { number:, captures: }`。
|
|
62
|
+
- 结果文本和捕获值不可原地修改;调整编码用 `result.match.dup.force_encoding("UTF-8")`。
|
|
63
|
+
- `expect` 统一返回 Result,移除 expect_result;序号比较用 `.number`,成功用 `.matched?`,EOF/超时用 `.eof?` / `.timeout?`。
|
|
64
|
+
- 会话 before、after、match、match_number、captures、error 与 last_result 保留。
|
|
65
|
+
- 模式进入匹配器后冻结,运行中不能追加或替换本轮规则。
|
|
66
|
+
- Gem 仅携带源码、RBS 与使用文档;测试、示例、基准和发布脚本从仓库取得。
|
|
67
|
+
- 0.6.0 曾保留外层 Expect 门面和布尔配置,这两部分已由 0.7.0 的 Session 与操作关键字替代。
|
|
68
|
+
- 单字符串命令保留 Ruby 自动 shell 语义;多参数命令按 argv 传入。
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Expect
|
|
4
|
+
# 清理作用域只记录本次异常,不受调用者 rescue 中的旧 $! 影响。
|
|
5
|
+
# @api private
|
|
6
|
+
module Cleanup
|
|
7
|
+
# 包括非局部返回在内的所有退出均清理;StandardError 只在没有原异常时传播。
|
|
8
|
+
# 清理期间新发生的 Interrupt、SystemExit 等致命异常仍原样传播。
|
|
9
|
+
def self.always(on_exit)
|
|
10
|
+
yield
|
|
11
|
+
rescue Exception # rubocop:disable Lint/RescueException -- 中断也必须清理,然后原样传播。
|
|
12
|
+
failed = true
|
|
13
|
+
raise
|
|
14
|
+
ensure
|
|
15
|
+
begin
|
|
16
|
+
on_exit.call
|
|
17
|
+
rescue StandardError
|
|
18
|
+
raise unless failed
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# 完成初始化或交接后释放清理责任;异常与非局部退出仍回滚未发布资源。
|
|
23
|
+
def self.on_failure(on_exit)
|
|
24
|
+
completed = false
|
|
25
|
+
always(-> { on_exit.call unless completed }) do
|
|
26
|
+
result = yield
|
|
27
|
+
completed = true
|
|
28
|
+
result
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private_constant :Cleanup
|
|
34
|
+
end
|