Class: Expect::SessionResources

Inherits:
Object
  • Object
show all
Defined in:
lib/expect/session_resources.rb

Overview

独立保存句柄、PID 和日志所有权,让终结器无需直接捕获会话即可清理遗弃资源。 IO 是否关闭与子进程是否退出分别记录;不能仅凭句柄状态清空 PID 或伪造退出状态。

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(reader, writer:, slave: nil, own: false) ⇒ SessionResources

记录创建资源的进程;fork 后的副本不能向父进程拥有的子进程发信号。



12
13
14
15
16
17
18
# File 'lib/expect/session_resources.rb', line 12

def initialize(reader, writer:, slave: nil, own: false)
  @reader = reader
  @writer = writer
  @slave = slave
  @own = own
  @owner = Process.pid
end

Instance Attribute Details

#own ⇒ Object (readonly)

Returns the value of attribute own.



9
10
11
# File 'lib/expect/session_resources.rb', line 9

def own
  @own
end

#owned_log ⇒ Object

owned_log 只保存库打开的文件;借用的 IO/日志回调留在会话中,不能成为终结器的引用根。



8
9
10
# File 'lib/expect/session_resources.rb', line 8

def owned_log
  @owned_log
end

#owner ⇒ Object (readonly)

Returns the value of attribute owner.



9
10
11
# File 'lib/expect/session_resources.rb', line 9

def owner
  @owner
end

#pid ⇒ Object

owned_log 只保存库打开的文件;借用的 IO/日志回调留在会话中,不能成为终结器的引用根。



8
9
10
# File 'lib/expect/session_resources.rb', line 8

def pid
  @pid
end

#reader ⇒ Object (readonly)

Returns the value of attribute reader.



9
10
11
# File 'lib/expect/session_resources.rb', line 9

def reader
  @reader
end

#slave ⇒ Object (readonly)

Returns the value of attribute slave.



9
10
11
# File 'lib/expect/session_resources.rb', line 9

def slave
  @slave
end

#status ⇒ Object

owned_log 只保存库打开的文件;借用的 IO/日志回调留在会话中,不能成为终结器的引用根。



8
9
10
# File 'lib/expect/session_resources.rb', line 8

def status
  @status
end

#writer ⇒ Object (readonly)

Returns the value of attribute writer.



9
10
11
# File 'lib/expect/session_resources.rb', line 9

def writer
  @writer
end

Class Method Details

.close_handles(*handles) ⇒ Object

账本尚未建立时也能释放局部 IO;不依赖完整会话,也不调用无效参数上的用户方法。



27
28
29
30
31
32
33
34
35
36
# File 'lib/expect/session_resources.rb', line 27

def self.close_handles(*handles)
  # 初始化校验失败时可能含无效参数,只关闭真实 IO;PTY 读写端也需要去重。
  failure = nil
  handles.grep(IO).uniq(&:object_id).each do |io|
    io.close unless io.closed?
  rescue IOError, SystemCallError => error
    failure ||= error
  end
  raise failure if failure
end

.finalizer(resources) ⇒ Object

构造只持有资源对象的终结回调,避免闭包中的 self 绑定到会话而妨碍回收。



73
74
75
# File 'lib/expect/session_resources.rb', line 73

def self.finalizer(resources)
  proc { resources.finalize }
end

Instance Method Details

#close_handles ⇒ Object

只关闭由本库拥有的 IO;借用的 reader、writer 由调用方管理。 常规关闭错误延后到所有句柄尝试完再抛出,失败句柄仍留在账本内供下一次关闭重试。



22
23
24
# File 'lib/expect/session_resources.rb', line 22

def close_handles
  self.class.close_handles(reader, writer, slave) if own
end

#finalize ⇒ Object

GC 兜底关闭所属句柄和日志,并强制终止尚存活的子进程;不执行软关闭等待。



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/expect/session_resources.rb', line 55

def finalize
  return unless owner == Process.pid

  begin
    close_handles
  ensure
    begin
      owned_log.close if owned_log && !owned_log.closed?
    ensure
      # 每个阶段独立收尾;句柄或日志关闭失败不能跳过进程回收。
      finalize_child
    end
  end
rescue IOError, SystemCallError
  nil
end

#reap ⇒ Object

非阻塞回收直属子进程,缓存 Process::Status,成功后清空 PID 以支持重复查询。



39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/expect/session_resources.rb', line 39

def reap
  return status unless pid && owner == Process.pid

  if (reaped = Process.waitpid2(pid, Process::WNOHANG))
    @status = reaped.last
    @pid = nil
  end
  status
rescue Errno::ECHILD
  # 子进程可能已被调用方或其他线程回收,不再保留可能被系统复用的 PID。
  # 无法获取别人已取走的 Process::Status,保留未知状态而不是推断成功或失败。
  @pid = nil
  status
end