net-connector 0.10.0 → 0.10.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 +6 -0
- data/docs/architecture.md +2 -0
- data/lib/net/connector/device/base.rb +5 -11
- data/lib/net/connector/device/running_config.rb +5 -3
- data/lib/net/connector/device_session.rb +7 -2
- data/lib/net/connector/engine/known_hosts.rb +31 -3
- data/lib/net/connector/engine/log.rb +28 -21
- data/lib/net/connector/engine/transport.rb +59 -27
- data/lib/net/connector/execution_pool.rb +2 -1
- data/lib/net/connector/storage/backup_lock.rb +29 -3
- data/lib/net/connector/storage/safe_file.rb +30 -4
- data/lib/net/connector/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9891d74f563f6d1383bf3a5a41e1eeb51e607b1395ec4794a42ed45027ce3c34
|
|
4
|
+
data.tar.gz: 950f56fcf3428c4a9a33c2176768f5cab3bdb4f19e649dd6f8e89547810dcea7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a785501bd50b61f542b9222ae24597025a20d3aa1d10fd2a042d20a7653f5fbe8ec2f6ccaf3ab6ea34db0cd11553fb918a836c34fd21dda9b4cb8b0dec83ed81
|
|
7
|
+
data.tar.gz: 5e1ef923eb2d603595d6225ed7cb6f08c36dbfc12c760d5b9aa69c94bbf812ef7a288586a48ff1b75947fc4886eb7dfbf830a5038bda2bfd6a43205aca18848e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# 更新记录
|
|
2
2
|
|
|
3
|
+
## 0.10.1 - 2026-10-11
|
|
4
|
+
|
|
5
|
+
- 设备 `open`、PTY、SSH 信任快照、日志与本地文件统一保护资源交接和关闭边界;异步取消在资源登记或回收后交付,业务等待仍可取消,收尾故障不覆盖首个操作错误。
|
|
6
|
+
- 修复线程回收同时发生二次取消时的循环异常原因,保留原始失败。
|
|
7
|
+
- 配置正文检查提前结束扫描,减少大配置的对象分配和字符串复制,保持原有命令回显与空配置判定。
|
|
8
|
+
|
|
3
9
|
## 0.10.0 - 2026-10-11
|
|
4
10
|
|
|
5
11
|
- 新增 `sync_interface_descriptions`:一次调用、每台设备一个连接,完成邻居发现、描述修改、读回与保存;无变化时不写入或保存,后端仍支持定制格式和分阶段审批。
|
data/docs/architecture.md
CHANGED
|
@@ -299,6 +299,8 @@ TFTP 策略必须实现 `validate_options!(target, source_file:, vrf:)`、`recei
|
|
|
299
299
|
|
|
300
300
|
## 加载入口与厂商策略
|
|
301
301
|
|
|
302
|
+
公开设备 `open` 与批量任务共用 `DeviceSession` 的关闭边界。PTY、SSH 信任快照、日志及本地文件在取得资源与登记所有权之间延后异步取消,关闭时保护回收过程;设备命令、文件读取和锁竞争仍可取消。清理保留首个故障,二次取消不能替换主错误或形成循环异常原因。
|
|
303
|
+
|
|
302
304
|
`require "net/connector"` 加载设备 API 和引擎,厂商由 `vendor_class` 按需加载。TextFSM 能力模块可预先组合,但外部 `textfsm` gem 只在实际解析时加载。仅需会话引擎时使用 `require "net/connector/engine/core"`;离线文件操作使用 `net/connector/storage`,不加载厂商。
|
|
303
305
|
|
|
304
306
|
`engine/dialogue` 只定义 Interaction、Dialogue 与 Response;读取循环位于 `engine/response_reader`。`engine/errors` 不加载脱敏运行时,`engine/redactor` 负责秘密作用域。Session 明确加载并持有读取器与脱敏器。
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "forwardable"
|
|
4
|
+
require_relative "../device_session"
|
|
4
5
|
require_relative "../engine/core"
|
|
5
6
|
require_relative "profile"
|
|
6
7
|
require_relative "running_config"
|
|
@@ -62,20 +63,13 @@ module Net
|
|
|
62
63
|
def self.open(**)
|
|
63
64
|
raise ArgumentError, "open requires a block" unless block_given?
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
begin
|
|
66
|
+
value, failure = DeviceSession.call(factory: -> { new(**) }) do |device|
|
|
67
67
|
device.connect
|
|
68
68
|
yield device
|
|
69
|
-
rescue Exception # rubocop:disable Lint/RescueException -- 本次连接或块失败时仍关闭设备,并保留原始故障或中断。
|
|
70
|
-
failed = true
|
|
71
|
-
raise
|
|
72
|
-
ensure
|
|
73
|
-
begin
|
|
74
|
-
device.close
|
|
75
|
-
rescue Exception # rubocop:disable Lint/RescueException -- 只保留本次作用域的原始异常,不受外层已处理异常影响。
|
|
76
|
-
raise unless failed
|
|
77
|
-
end
|
|
78
69
|
end
|
|
70
|
+
raise failure if failure
|
|
71
|
+
|
|
72
|
+
value
|
|
79
73
|
end
|
|
80
74
|
|
|
81
75
|
# 校验连接配置,创建对话语法、传输层、恢复器和会话对象。
|
|
@@ -103,9 +103,11 @@ module Net
|
|
|
103
103
|
# 排除命令回显和提示符,确认响应确实包含配置正文。
|
|
104
104
|
def config_body?(step)
|
|
105
105
|
body = TerminalRenderer.render(step.output.delete_suffix(step.prompt.to_s)).strip
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
# 只需确认首个正文行;不为大配置创建全部行对象或重新拼接整份响应。
|
|
107
|
+
body.each_line.with_index.any? do |line, index|
|
|
108
|
+
text = line.strip
|
|
109
|
+
!text.empty? && !(index.zero? && text == step.command.text)
|
|
110
|
+
end
|
|
109
111
|
end
|
|
110
112
|
|
|
111
113
|
# 将缺少配置的情况统一映射为可识别的设备错误码。
|
|
@@ -14,11 +14,16 @@ module Net
|
|
|
14
14
|
begin
|
|
15
15
|
# 构造器在阻塞等待中仍可取消;非阻塞的返回交接须先登记所有权再交付取消。
|
|
16
16
|
# 工厂尚未返回时的部分资源由工厂清理,成功返回后统一由本作用域关闭。
|
|
17
|
-
Thread.handle_interrupt(Exception => :on_blocking)
|
|
17
|
+
Thread.handle_interrupt(Exception => :on_blocking) do
|
|
18
|
+
connector = factory.call
|
|
19
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 工厂同步故障先于退出屏蔽范围时交付的取消。
|
|
20
|
+
failure = error
|
|
21
|
+
raise
|
|
22
|
+
end
|
|
18
23
|
value = yield connector
|
|
19
24
|
completed = true
|
|
20
25
|
rescue Exception => error # rubocop:disable Lint/RescueException -- 收尾后保留最先发生的中断,普通故障单独交付业务层。
|
|
21
|
-
failure
|
|
26
|
+
failure ||= error
|
|
22
27
|
completed = true
|
|
23
28
|
ensure
|
|
24
29
|
begin
|
|
@@ -20,7 +20,7 @@ module Net
|
|
|
20
20
|
@host = "[#{@host}]:#{configuration.port}" if configuration.port && configuration.port != 22
|
|
21
21
|
@replace = replace
|
|
22
22
|
@original = read_shared
|
|
23
|
-
|
|
23
|
+
create_snapshot
|
|
24
24
|
@path = @file.path
|
|
25
25
|
@file.write(@original)
|
|
26
26
|
@file.flush
|
|
@@ -57,6 +57,36 @@ module Net
|
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def close
|
|
60
|
+
failure = nil
|
|
61
|
+
# 清除引用至删除快照期间延后异步取消,不能留下失去所有者的临时文件。
|
|
62
|
+
Thread.handle_interrupt(Exception => :never) do
|
|
63
|
+
close_snapshot
|
|
64
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 保存同步清理故障,退出屏蔽范围后继续传播。
|
|
65
|
+
failure = error
|
|
66
|
+
raise
|
|
67
|
+
end
|
|
68
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 延迟取消不能覆盖清理过程中已经发生的故障。
|
|
69
|
+
raise failure || error, cause: nil
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
# 临时文件返回后先登记所有权,阻塞构造期间仍允许取消。
|
|
75
|
+
def create_snapshot
|
|
76
|
+
failure = nil
|
|
77
|
+
begin
|
|
78
|
+
Thread.handle_interrupt(Exception => :on_blocking) do
|
|
79
|
+
@file = Tempfile.new([".nc-known-hosts-", ""], File.dirname(@destination))
|
|
80
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 构造故障先于退出屏蔽范围时递送的二次取消。
|
|
81
|
+
failure = error
|
|
82
|
+
end
|
|
83
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 成功交接后取消仍传播,但不覆盖已登记的构造故障。
|
|
84
|
+
failure ||= error
|
|
85
|
+
end
|
|
86
|
+
raise failure, cause: nil if failure
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def close_snapshot
|
|
60
90
|
file = @file
|
|
61
91
|
@file = nil
|
|
62
92
|
File.unlink(@path + ".old") if @path && File.exist?(@path + ".old")
|
|
@@ -71,8 +101,6 @@ module Net
|
|
|
71
101
|
end
|
|
72
102
|
end
|
|
73
103
|
|
|
74
|
-
private
|
|
75
|
-
|
|
76
104
|
# 初始化失败时保留原始异常,同时尽力清理已经创建的临时文件。
|
|
77
105
|
def close_preserving_failure
|
|
78
106
|
close
|
|
@@ -31,15 +31,17 @@ module Net
|
|
|
31
31
|
@transport = transport
|
|
32
32
|
@session_id = SecureRandom.hex(8).freeze
|
|
33
33
|
@command_sequence = 0
|
|
34
|
-
|
|
34
|
+
open_file(@configuration.log_file) if @configuration.log_file
|
|
35
35
|
if @configuration.log_format == :raw
|
|
36
36
|
@writer = @output = RedactingWriter.new(@io, @redactor) if @io
|
|
37
37
|
elsif @io || @configuration.logger
|
|
38
38
|
@logger = @configuration.logger || ::Logger.new(@io, formatter: Formatter.new)
|
|
39
39
|
open_transcript if @io && @configuration.log_level == :debug
|
|
40
40
|
end
|
|
41
|
-
rescue => error
|
|
41
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 文件已登记后的中断也必须释放资源,仍原样交付中断。
|
|
42
42
|
close_preserving_failure
|
|
43
|
+
raise unless error.is_a?(StandardError)
|
|
44
|
+
|
|
43
45
|
raise build_error("unable to open session log", error), cause: nil
|
|
44
46
|
end
|
|
45
47
|
|
|
@@ -192,10 +194,16 @@ module Net
|
|
|
192
194
|
failed = true
|
|
193
195
|
raise
|
|
194
196
|
ensure
|
|
197
|
+
close_failure = nil
|
|
195
198
|
begin
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
+
Thread.handle_interrupt(Exception => :never) do
|
|
200
|
+
@io&.close
|
|
201
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 文件关闭先发生的故障优先于待处理中断。
|
|
202
|
+
close_failure = error
|
|
203
|
+
raise
|
|
204
|
+
end
|
|
205
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 文件关闭不能覆盖首个日志收尾故障或中断。
|
|
206
|
+
raise close_failure || error, cause: nil unless failed
|
|
199
207
|
ensure
|
|
200
208
|
@writer = @output = @transcript = @logger = @io = @transport = @session_id = nil
|
|
201
209
|
@attached = false
|
|
@@ -244,29 +252,28 @@ module Net
|
|
|
244
252
|
|
|
245
253
|
# 以私有权限打开设备日志文件。
|
|
246
254
|
def open_file(path)
|
|
255
|
+
failure = nil
|
|
247
256
|
FileUtils.mkdir_p(File.dirname(path), mode: 0o700)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
file.chmod(0o600)
|
|
254
|
-
file.binmode
|
|
255
|
-
file.write("\n") if file.size.positive? && @configuration.log_format == :text
|
|
256
|
-
file
|
|
257
|
-
rescue Exception # rubocop:disable Lint/RescueException -- 中断时也释放尚未完成设置的文件。
|
|
258
|
-
begin
|
|
259
|
-
file.close
|
|
260
|
-
rescue Exception # rubocop:disable Lint/RescueException -- 保留文件设置的原始故障或中断。
|
|
261
|
-
# 保留设置失败的原始原因。
|
|
262
|
-
end
|
|
257
|
+
# 返回文件后先登记唯一所有者;后续设置失败由 open 统一释放,不重复交接资源。
|
|
258
|
+
Thread.handle_interrupt(Exception => :never) do
|
|
259
|
+
@io = File.open(path, File::WRONLY | File::CREAT | File::APPEND | File::NOFOLLOW | File::NONBLOCK, 0o600)
|
|
260
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 非阻塞打开的主故障不能被待处理中断替换。
|
|
261
|
+
failure = error
|
|
263
262
|
raise
|
|
264
263
|
end
|
|
264
|
+
# FIFO 的打开不受登录截止时间约束;验证同一个 FD 后才能修改权限或写入。
|
|
265
|
+
raise ArgumentError, "session log must be a regular file" unless @io.stat.file?
|
|
266
|
+
|
|
267
|
+
@io.chmod(0o600)
|
|
268
|
+
@io.binmode
|
|
269
|
+
@io.write("\n") if @io.size.positive? && @configuration.log_format == :text
|
|
270
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 恢复取消交付时仍保留打开阶段的首个故障。
|
|
271
|
+
raise failure || error, cause: nil
|
|
265
272
|
end
|
|
266
273
|
|
|
267
274
|
# 在日志打开失败后尽力释放资源。
|
|
268
275
|
def close_preserving_failure
|
|
269
|
-
close
|
|
276
|
+
Thread.handle_interrupt(Exception => :never) { close }
|
|
270
277
|
rescue Exception # rubocop:disable Lint/RescueException -- 清理故障或中断不能覆盖日志打开失败。
|
|
271
278
|
nil
|
|
272
279
|
end
|
|
@@ -37,18 +37,23 @@ module Net
|
|
|
37
37
|
|
|
38
38
|
# 打开伪终端、设置终端大小并启动设备进程。
|
|
39
39
|
def open
|
|
40
|
+
failure = nil
|
|
40
41
|
configuration.validate_endpoint!
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
# 构造期间的阻塞仍可取消;返回交接先登记信道,再交付异步中断。
|
|
43
|
+
Thread.handle_interrupt(Exception => :on_blocking) do
|
|
44
|
+
@channel = if @channel_factory
|
|
45
|
+
@channel_factory.call
|
|
46
|
+
elsif defined?(Expect::Session)
|
|
45
47
|
Expect::Session.new(buffer_limit: nil, write_timeout: configuration.write_timeout)
|
|
46
48
|
else
|
|
47
49
|
Expect.new(raw_pty: true, reset_timeout_on_read: false, buffer_limit: nil,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
preserve_buffer: false, log_stdout: false, log_listeners: false,
|
|
51
|
+
debug_level: 0, write_timeout: configuration.write_timeout)
|
|
50
52
|
end
|
|
51
|
-
|
|
53
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 退出屏蔽范围交付取消之前,先保存构造器的同步故障。
|
|
54
|
+
failure = error
|
|
55
|
+
raise
|
|
56
|
+
end
|
|
52
57
|
# Profile 使用宽、高;PTY 使用行数、列数。
|
|
53
58
|
@channel.slave.winsize = @terminal_size.reverse if @terminal_size
|
|
54
59
|
if @channel.respond_to?(:transcript=)
|
|
@@ -65,9 +70,9 @@ module Net
|
|
|
65
70
|
@channel.spawn(*argv)
|
|
66
71
|
end
|
|
67
72
|
self
|
|
68
|
-
rescue Exception # rubocop:disable Lint/RescueException -- 中断时也必须释放尚未完成初始化的 PTY。
|
|
73
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 中断时也必须释放尚未完成初始化的 PTY。
|
|
69
74
|
close_preserving_failure
|
|
70
|
-
raise
|
|
75
|
+
raise failure || error, cause: nil
|
|
71
76
|
end
|
|
72
77
|
|
|
73
78
|
# 从信道读取下一事件,并把字符串字段转换为字节字符串。
|
|
@@ -107,10 +112,19 @@ module Net
|
|
|
107
112
|
|
|
108
113
|
# 清除信道和日志目标,并强制关闭底层进程。
|
|
109
114
|
def close
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
failure = nil
|
|
116
|
+
# 从字段移交到关闭完成期间不能取消,避免丢失尚未回收的进程。
|
|
117
|
+
Thread.handle_interrupt(Exception => :never) do
|
|
118
|
+
channel = @channel
|
|
119
|
+
@channel = nil
|
|
120
|
+
@log_target = nil
|
|
121
|
+
channel&.hard_close
|
|
122
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 延迟取消不能覆盖底层关闭的首个故障。
|
|
123
|
+
failure = error
|
|
124
|
+
raise
|
|
125
|
+
end
|
|
126
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 恢复取消交付时仍保留最先观察到的故障。
|
|
127
|
+
raise failure || error, cause: nil
|
|
114
128
|
end
|
|
115
129
|
|
|
116
130
|
# 把人工交互结果转换为设备 EOF、超时或输入状态。
|
|
@@ -156,14 +170,21 @@ module Net
|
|
|
156
170
|
end
|
|
157
171
|
|
|
158
172
|
def open
|
|
173
|
+
failure = nil
|
|
159
174
|
configuration.validate_endpoint!
|
|
160
175
|
unless configuration.host_key_policy == :strict
|
|
161
|
-
|
|
176
|
+
# 临时信任文件的返回交接与信道遵守同一所有权边界。
|
|
177
|
+
Thread.handle_interrupt(Exception => :on_blocking) do
|
|
178
|
+
@known_hosts = KnownHosts.new(configuration, replace: @replace_host_key)
|
|
179
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 构造器先失败时,延迟取消不得覆盖其原始异常。
|
|
180
|
+
failure = error
|
|
181
|
+
raise
|
|
182
|
+
end
|
|
162
183
|
end
|
|
163
184
|
super
|
|
164
|
-
rescue Exception # rubocop:disable Lint/RescueException -- 启动失败或中断时释放临时信任文件。
|
|
185
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 启动失败或中断时释放临时信任文件。
|
|
165
186
|
close_preserving_failure
|
|
166
|
-
raise
|
|
187
|
+
raise failure || error, cause: nil
|
|
167
188
|
end
|
|
168
189
|
|
|
169
190
|
def authenticated
|
|
@@ -172,18 +193,29 @@ module Net
|
|
|
172
193
|
end
|
|
173
194
|
|
|
174
195
|
def close
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
196
|
+
failure = nil
|
|
197
|
+
# 一次取消须等待信道和信任快照均完成回收;同步故障仍保留首个。
|
|
198
|
+
Thread.handle_interrupt(Exception => :never) do
|
|
199
|
+
known_hosts = @known_hosts
|
|
200
|
+
@known_hosts = nil
|
|
201
|
+
begin
|
|
202
|
+
super
|
|
203
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 全部资源均须尝试关闭,保留首个失败或中断。
|
|
204
|
+
failure = error
|
|
205
|
+
raise
|
|
206
|
+
ensure
|
|
207
|
+
begin
|
|
208
|
+
known_hosts&.close
|
|
209
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 信任文件收尾不能覆盖传输关闭的原始异常。
|
|
210
|
+
unless failure
|
|
211
|
+
failure = error
|
|
212
|
+
raise
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
186
216
|
end
|
|
217
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 恢复取消交付也不能盖掉已经发生的关闭故障。
|
|
218
|
+
raise failure || error, cause: nil
|
|
187
219
|
end
|
|
188
220
|
|
|
189
221
|
# 返回 SSH 协议标识。
|
|
@@ -101,7 +101,8 @@ module Net
|
|
|
101
101
|
end
|
|
102
102
|
end
|
|
103
103
|
rescue Exception => error # rubocop:disable Lint/RescueException -- 恢复交付时保留原故障,二次取消不改变批次失败原因。
|
|
104
|
-
|
|
104
|
+
# 二次取消可能已以原故障为 cause;不能反向关联形成异常原因环。
|
|
105
|
+
raise primary_failure || error, cause: nil
|
|
105
106
|
end
|
|
106
107
|
end
|
|
107
108
|
end
|
|
@@ -46,17 +46,43 @@ module Net
|
|
|
46
46
|
return yield self
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
file = nil
|
|
49
|
+
file = acquisition_error = nil
|
|
50
50
|
begin
|
|
51
|
-
|
|
51
|
+
# 文件取得与所有权登记不可拆开;竞争锁和业务块仍立即响应中断。
|
|
52
|
+
Thread.handle_interrupt(Exception => :never) do
|
|
53
|
+
begin
|
|
54
|
+
file = open_lock
|
|
55
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 取得失败先于边界退出时递送的中断。
|
|
56
|
+
acquisition_error = error
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
raise acquisition_error, cause: nil if acquisition_error
|
|
60
|
+
|
|
52
61
|
verify_lock!(file)
|
|
53
62
|
acquire(file)
|
|
54
63
|
verify_lock!(file)
|
|
55
64
|
@owner = [Process.pid, Fiber.current]
|
|
56
65
|
yield self
|
|
66
|
+
rescue Exception # rubocop:disable Lint/RescueException -- 解锁收尾保留首个操作故障或中断。
|
|
67
|
+
failed = true
|
|
68
|
+
raise acquisition_error, cause: nil if acquisition_error
|
|
69
|
+
|
|
70
|
+
raise
|
|
57
71
|
ensure
|
|
58
72
|
@owner = nil
|
|
59
|
-
|
|
73
|
+
cleanup_error = nil
|
|
74
|
+
begin
|
|
75
|
+
Thread.handle_interrupt(Exception => :never) do
|
|
76
|
+
begin
|
|
77
|
+
file&.close
|
|
78
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 在递送待处理中断前登记关闭故障。
|
|
79
|
+
cleanup_error = error
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 待处理中断不能覆盖已登记的关闭故障。
|
|
83
|
+
cleanup_error ||= error
|
|
84
|
+
end
|
|
85
|
+
raise cleanup_error, cause: nil if cleanup_error && !failed
|
|
60
86
|
end
|
|
61
87
|
end
|
|
62
88
|
|
|
@@ -10,17 +10,43 @@ module Net
|
|
|
10
10
|
Fingerprint = Data.define(:path, :sha256, :mode, :dev, :ino)
|
|
11
11
|
|
|
12
12
|
def self.open(path, missing: false, replace_symlink: false)
|
|
13
|
-
file =
|
|
14
|
-
return nil unless file
|
|
15
|
-
|
|
13
|
+
file = acquisition_error = nil
|
|
16
14
|
begin
|
|
15
|
+
# 取得描述符与登记所有权共用短暂边界;读取和业务块仍可中断。
|
|
16
|
+
Thread.handle_interrupt(Exception => :never) do
|
|
17
|
+
begin
|
|
18
|
+
file = open_handle(path, missing: missing, replace_symlink: replace_symlink)
|
|
19
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 取得失败先于边界退出时递送的中断。
|
|
20
|
+
acquisition_error = error
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
raise acquisition_error, cause: nil if acquisition_error
|
|
24
|
+
return nil unless file
|
|
25
|
+
|
|
17
26
|
stat = file.stat
|
|
18
27
|
raise ArgumentError, "saved configuration is not a regular file" unless stat.file?
|
|
19
28
|
|
|
20
29
|
file.binmode
|
|
21
30
|
yield file, stat
|
|
31
|
+
rescue Exception # rubocop:disable Lint/RescueException -- 收尾不能覆盖读取故障或用户中断。
|
|
32
|
+
failed = true
|
|
33
|
+
raise acquisition_error, cause: nil if acquisition_error
|
|
34
|
+
|
|
35
|
+
raise
|
|
22
36
|
ensure
|
|
23
|
-
|
|
37
|
+
cleanup_error = nil
|
|
38
|
+
begin
|
|
39
|
+
Thread.handle_interrupt(Exception => :never) do
|
|
40
|
+
begin
|
|
41
|
+
file&.close
|
|
42
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 在递送待处理中断前登记关闭故障。
|
|
43
|
+
cleanup_error = error
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
rescue Exception => error # rubocop:disable Lint/RescueException -- 待处理中断不能覆盖已登记的关闭故障。
|
|
47
|
+
cleanup_error ||= error
|
|
48
|
+
end
|
|
49
|
+
raise cleanup_error, cause: nil if cleanup_error && !failed
|
|
24
50
|
end
|
|
25
51
|
end
|
|
26
52
|
|