Ответ 1
Ага, тонкая.
Существует скрытое, блокирующее предложение ensure
в конце всплывающего блока ввода-вывода во втором случае. Ошибка Timeout:: Error поднимается своевременно, но вы не можете rescue
до тех пор, пока выполнение не вернется из этого неявного предложения ensure
.
Под капотом, IO.popen(cmd) { |io| ... }
делает что-то вроде этого:
def my_illustrative_io_popen(cmd, &block)
begin
pio = IO.popen(cmd)
block.call(pio) # This *is* interrupted...
ensure
pio.close # ...but then control goes here, which blocks on cmd termination
end
а вызов IO # close действительно больше или меньше a pclose(3)
, который блокирует вас в waitpid(2)
до тех пор, пока спящий ребенок не выйдет.
Вы можете проверить это так:
#!/usr/bin/env ruby
require 'timeout'
BEGIN { $BASETIME = Time.now.to_i }
def xputs(msg)
puts "%4.2f: %s" % [(Time.now.to_f - $BASETIME), msg]
end
begin
Timeout.timeout(3) do
begin
xputs "popen(sleep 10)"
pio = IO.popen("sleep 10")
sleep 100 # or loop over pio.gets or whatever
ensure
xputs "Entering ensure block"
#Process.kill 9, pio.pid # <--- This would solve your problem!
pio.close
xputs "Leaving ensure block"
end
end
rescue Timeout::Error => ex
xputs "rescuing: #{ex}"
end
Итак, что вы можете сделать?
Вам нужно будет сделать это явным образом, поскольку интерпретатор не предоставляет способ переопределить логику ввода-вывода IO # popen ensure
. Вы можете использовать приведенный выше код в качестве стартового шаблона и раскомментировать строку kill()
, например.