如何从Ruby启动mycmd守护进程:阻塞至BOOTED输出且Ruby退出后持续运行
解决方案
方法一:结合nohup与管道关闭
使用nohup启动mycmd确保其忽略SIGHUP信号,读取到BOOTED后关闭所有关联管道,让进程脱离Ruby控制:
require 'open3' # 用nohup启动进程,保留stdout/stderr管道供Ruby读取 stdin, stdout, stderr, wait_thr = Open3.popen3("nohup mycmd --whatever") target_pid = wait_thr.pid puts "Starting mycmd, pid=#{target_pid}" # 阻塞直到mycmd输出BOOTED stdout.each_line do |line| break if line.include?("BOOTED") end # 关闭所有与mycmd关联的管道,避免Ruby持有文件描述符导致进程无法正常留存 stdin.close stdout.close stderr.close # 脱离进程,Ruby退出时不会等待该进程 Process.detach(target_pid)
原理
nohup会让mycmd忽略终端发送的SIGHUP信号(Ruby退出时终端会向同进程组的进程发送该信号)。- 关闭管道后,
mycmd的后续输出会自动转向nohup.out(默认),不再依赖Ruby的文件描述符,因此Ruby退出后进程可继续运行。
方法二:使用Process.spawn手动管理进程组与管道
通过Process.spawn创建独立进程组,读取完输出后关闭管道,确保进程脱离Ruby控制:
# 启动mycmd,创建独立进程组,保留stdout管道 pid = Process.spawn( "mycmd --whatever", out: [:child, :pipe], # 保留子进程stdout供Ruby读取 err: [:child, :pipe], pgroup: true # 创建新进程组,避免Ruby退出时发送SIGHUP ) stdout = IO.new(pid.out, "r") stderr = IO.new(pid.err, "r") puts "Starting mycmd, pid=#{pid}" # 阻塞直到BOOTED输出 stdout.each_line do |line| break if line.include?("BOOTED") end # 关闭管道,释放文件描述符 stdout.close stderr.close # 脱离进程 Process.detach(pid) # 可选:将mycmd的后续输出重定向到/dev/null(需系统安装gdb,且有对应权限) # system("gdb -p #{pid} -ex 'call dup2(open(\"/dev/null\", 0), 1)' -ex 'call dup2(open(\"/dev/null\", 0), 2)' -ex quit >/dev/null 2>&1")
原理
pgroup: true让mycmd处于独立进程组,Ruby退出时终端不会向该进程组发送SIGHUP。- 关闭管道后,
mycmd失去与Ruby的关联,可继续运行。如需将后续输出转向/dev/null,可通过gdb修改进程的文件描述符。
方法三:临时文件中转输出
先让mycmd将输出写入临时文件,Ruby读取临时文件直到BOOTED,之后修改mycmd的输出目标:
require 'tempfile' # 创建临时文件存储mycmd的启动输出 temp_log = Tempfile.new("mycmd_boot") temp_log_path = temp_log.path temp_log.close # 启动mycmd,将stdout/stderr输出到临时文件并后台运行 pid = Process.spawn("mycmd --whatever > #{temp_log_path} 2>&1 &") puts "Starting mycmd, pid=#{pid}" # 轮询临时文件,直到出现BOOTED loop do break if File.read(temp_log_path).include?("BOOTED") sleep 0.1 end # 修改mycmd的输出到/dev/null(需系统安装gdb,且有对应权限) system("gdb -p #{pid} -ex 'call dup2(open(\"/dev/null\", 0), 1)' -ex 'call dup2(open(\"/dev/null\", 0), 2)' -ex quit >/dev/null 2>&1") # 删除临时文件 File.delete(temp_log_path)
原理
- 通过临时文件中转启动输出,Ruby读取文件直到
BOOTED。 - 之后将
mycmd的输出重定向到/dev/null,完成后删除临时文件。这种方法无需保留Ruby与mycmd的管道连接,天然避免了Ruby退出时的影响。
内容的提问来源于stack exchange,提问作者Seth
相关产品推荐
相关产品推荐

