Ruby тупиковое ожидание процесса :: Waiter с Open3.popen3

#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. Напишите программу, которая воспроизводит проблему, поставьте точку останова перед вызовом значения, убедитесь, что весь вывод был израсходован и все файловые дескрипторы закрыты.