#ruby #process #deadlock #detach #popen3
#ruby #процесс #тупиковая ситуация #отсоединить #popen3
Вопрос:
Я использовал Open3.capture3
в своем ruby-коде и начал замечать взаимоблокировки. Я провел некоторое исследование и наткнулся на этот отличный блог о том, как open3 может вызывать взаимоблокировки. Принимая это во внимание, я придумал свой собственный метод capture3 (все еще используемый Open3.popen3
), чтобы заменить тот, который я использовал в настоящее время (см. Ниже). Однако я обнаружил, что взаимоблокировки все еще происходят, и ответственной стороной является блокирующий вызов wait_thread.value
для получения Process::Status
. Заглядывая в Open3
исходный код, я вижу, что этот wait_thread — это просто Process::Waiter
то, что вы получаете от Process.detach
вызова. Мне было интересно, что может быть причиной этой тупиковой ситуации? Есть ли какой-нибудь способ, которым я могу проверить, заблокирован ли этот поток, убить его и вернуть пользовательский Process::Status
(я бы хотел вернуть a Process::Status
, а не просто целое число, чтобы оставаться встроенным Open3.capture3
)
def capture3(cmd)
out_buff = ''
err_buff = ''
chunk_size = 4096
# Progressive timeout array
sleeps = [[0.05]*20,[0.1]*5,[0.5]*3,1,2].flatten
_in, out, err, wait_thread = Open3.popen3(cmd)
_in.close
open_pipes = [out, err]
while !open_pipes.empty?
timeout = sleeps.shift || timeout
ready_pipes, *_ = IO.select(open_pipes, nil, nil, timeout)
ready_pipes.each do |pipe|
tmp = ''
begin
tmp << pipe.readpartial(chunk_size)
rescue EOFError
pipe.close
open_pipes.delete(pipe)
end
out_buff << tmp if pipe == out
err_buff << tmp if pipe == err
end
end
[out_buff, err_buff, wait_thread.value]
end
Обратите внимание, что версия ruby, которую я использую, — 2.2.5 и работает на Mac.
Редактировать: точная ошибка No live threads left. Deadlock? (fatal)
указывает на номер строки вызова wait_thread.value. Я вызываю метод capture3 в другой функции, где я разветвляю процесс следующим образом
def fork_process(cmd, options={})
pid = fork {
Process.setproctitle(options[:process_name]) unless options[:process_name].nil?
ENV.merge!(options[:environment])
stdout, stderr, status = capture3(cmd)
yield(stdout, stderr, status) if block_given?
}
Process.detach(pid)
end
Полная трассировка стека:
/home/myapp/utils.rb:769:in `value': No live threads left. Deadlock? (fatal)
from /home/myapp/utils.rb:769:in `capture3'
from /home/myapp/utils.rb:106:in `block in fork_process'
from /home/myapp/utils.rb:104:in `fork'
from /home/myapp/utils.rb:104:in `fork_process'
from /home/myapp/server.rb:396:in `block in <class:MyServer>'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:1611:in `call'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:1611:in `block in compile!'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:975:in `[]'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:975:in `block (3 levels) in route!'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:994:in `route_eval'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:975:in `block (2 levels) in route!'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:1015:in `block in process_route'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:1013:in `catch'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:1013:in `process_route'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:973:in `block in route!'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:972:in `each'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:972:in `route!'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:1085:in `block in dispatch!'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:1067:in `block in invoke'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:1067:in `catch'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:1067:in `invoke'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:1082:in `dispatch!'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:907:in `block in call!'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:1067:in `block in invoke'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:1067:in `catch'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:1067:in `invoke'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:907:in `call!'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:895:in `call'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/rack-protection-1.5.3/lib/rack/protection/xss_header.rb:18:in `call'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/rack-protection-1.5.3/lib/rack/protection/path_traversal.rb:16:in `call'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/rack-protection-1.5.3/lib/rack/protection/json_csrf.rb:18:in `call'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/rack-protection-1.5.3/lib/rack/protection/base.rb:49:in `call'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/rack-protection-1.5.3/lib/rack/protection/base.rb:49:in `call'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/rack-protection-1.5.3/lib/rack/protection/frame_options.rb:31:in `call'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/rack-1.6.5/lib/rack/nulllogger.rb:9:in `call'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/rack-1.6.5/lib/rack/head.rb:13:in `call'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/show_exceptions.rb:25:in `call'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:182:in `call'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:2013:in `call'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:1487:in `block in call'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:1787:in `synchronize'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/sinatra-1.4.8/lib/sinatra/base.rb:1487:in `call'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/puma-3.8.2/lib/puma/configuration.rb:224:in `call'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/puma-3.8.2/lib/puma/server.rb:600:in `handle_request'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/puma-3.8.2/lib/puma/server.rb:435:in `process_client'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/puma-3.8.2/lib/puma/server.rb:299:in `block in run'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/puma-3.8.2/lib/puma/thread_pool.rb:120:in `call'
from /Users/app/.rvm/gems/ruby-2.2.5/gems/puma-3.8.2/lib/puma/thread_pool.rb:120:in `block in spawn_thread'
Комментарии:
1. Как именно вы сталкиваетесь с этими тупиками (трассировка стека / полное исключение)? В вашем коде также отсутствует обработка ошибок.
2. Добавлена полная трассировка стека с указанием того, как она называется
3. Я думаю, вам следует присоединиться к потоку, прежде чем спрашивать о его ценности.
4.
Thread#value
использует join для ожидания завершения потока, а затем возвращает значение, так что это не должно быть проблемой.5. Напишите программу, которая воспроизводит проблему, поставьте точку останова перед вызовом значения, убедитесь, что весь вывод был израсходован и все файловые дескрипторы закрыты.