Ruby Timeout::timeout在Open3执行ssh命令时失效问题求助
问题根源:Ruby的Timeout是线程级的,管不了外部进程!
兄弟我之前也栽过这个坑!先给你拆解清楚:
为什么无限循环能被Timeout终止,ssh不行?
Ruby的Timeout::timeout本质是在另一个线程里设置定时器,到点就给目标线程扔Timeout::Error异常。但这个逻辑只对Ruby自己的代码生效:
- 无限循环是Ruby线程在执行的Ruby代码,抛出异常就能直接打断循环;
- 但当你用Open3调用ssh时,Ruby线程会被阻塞在系统调用上(比如等待子进程退出的
waitpid),这时候线程根本没法响应那个Timeout异常——因为它卡在操作系统层面了,不是在跑Ruby代码!
ssh进程一旦因为需要交互(比如首次连接的主机密钥确认、等待密码输入)挂起,它会一直占着终端,Ruby的主线程就死死等在那,Timeout的异常根本传不进去,自然不会打印任何东西。
正确的解决方案:直接监控+杀死外部进程
别用Timeout::timeout处理外部命令了,换个思路:启动ssh进程后,单独监控超时时间,到点直接给ssh进程发终止信号。
给你个示例代码:
require 'open3' # 提前处理ssh的交互问题,避免挂起(很重要!) ssh_cmd = "ssh -o StrictHostKeyChecking=no -o BatchMode=yes user@host 'echo hello'" # 启动子进程 stdin, stdout, stderr, wait_thr = Open3.popen3(ssh_cmd) # 设置超时时间(比如10秒) timeout = 10 start_time = Time.now # 非阻塞等待进程结束,同时检查超时 loop do break if wait_thr.join(0.1).nil? == false # 0.1秒轮询一次 if Time.now - start_time > timeout # 杀死ssh进程(包括可能的子进程) Process.kill('TERM', wait_thr.pid) # 等进程彻底退出 wait_thr.join puts "Timeout exceeded" exit 1 end end # 进程正常结束的处理 puts "Debug 1" # 这里可以处理stdout/stderr的输出
额外提醒
- 先解决ssh的交互问题:一定要给ssh加
-o BatchMode=yes(禁止交互)和-o StrictHostKeyChecking=no(自动接受新主机密钥),不然ssh大概率会因为需要用户输入而挂起; - 如果ssh命令需要执行长时间任务,也可以用类似的方式监控,超时就杀进程;
- 不要依赖
Timeout::timeout处理任何涉及系统调用的操作,它只适合纯Ruby代码的超时控制。
内容的提问来源于stack exchange,提问作者user8897013
相关产品推荐
相关产品推荐

