Как попросить рубинов остановить все остальные потоки

Я пытаюсь отладить многопоточный ruby ​​ script, проблема в том, что я делаю

binding.pry

Другие потоки продолжают отправлять вывод на консоль. Как заставить их останавливаться при bind.pry, а затем запускать снова, когда я выхожу? Я думаю, что есть способ сделать это в .pryrc

Ответы

Ответ 1

Похоже, вы предлагаете использовать вызов binding.pry для опроса всех дочерних потоков и приостановки их до окончания сеанса. Это невозможно по техническим и практическим причинам. Классы Binding и Thread не работают таким образом, а многопоточность в Ruby не работает.

Темы в Ruby могут быть приостановлены только при вызове Kernel#sleep или Thread.stop. (и они функционально эквивалентны). Реально, эти методы могут быть вызваны только в текущем потоке. Один поток не может приостановить другой поток. (Thread.stop - метод класса, а не метод экземпляра)

Посмотрим, что на самом деле делает binding.pry: объекты класса Binding инкапсулируют контекст выполнения в определенном месте в коде и сохранить этот контекст для будущего использования. Поэтому, когда вы помещаете binding.pry в свой код, вы сообщаете Ruby об инкапсулировании контекста выполнения для текущего потока.

То, что это означает, когда вы вызываете binding.pry в основном потоке, объект Binding имеет контекст для текущего потока и может сказать себе заснуть, но основной класс Ruby Thread не позволяет ему сообщать другим нитьм спать.

Даже если бы он поддерживал его, это было бы странно и подвержено ошибкам, а также причиной большого количества царапин на голове. Представьте, что у вас есть такой код:

# we are in the main thread
Thread.new do
  # we are in the child thread
  foo = Foo.new(bar.fetch(:baz, {}))
  foo.save
end

# we are in the main thread
binding.pry

Из-за того, как Ruby обрабатывает контекстное переключение, если binding.pry сказал все дочерние потоки останавливаться, тогда дочерний поток может остановить ЛЮБОЕ в стеке вызовов, в том числе в любом месте кода для Foo.new или .save. Если эти потоки приостанавливаются и возобновляются в середине выполнения кода, который вы не писали, это может вызвать проблемы. Например, что произойдет, если соединение ActiveRecord из пула было извлечено и использовано для запроса SELECT, но поток был послан в режим сна до того, как он вернул соединение с пулом и до того, как он получил ответ? Плохие вещи. Много плохих вещей.

Похоже, что реальным решением для вас является изменение многословности дочерних потоков. Если вы устраняете код чата, а ваши другие потоки шумны, пока вы пытаетесь работать в одном потоке, тогда установите другие потоки для использования, например, на более низком уровне ведения журнала.

Ответ 2

Если binding.pry дает непоследовательные результаты, попробуйте следующее:

  • Если он существует, удалите pry-stack_explorer из своего gemfile, а затем переупаковка вашего приложения. Кажется, что этот камень имеет проблемы с pry-byebug.

  • Кроме того, не уверен, что вы пробовали это уже, но несколько серверов   перезапуск не может повредить; это разрешило такие странные проблемы, как это для   меня чаще, чем я хотел бы признать.

Однако вы можете пытаться сделать что-то, что просто невозможно сделать с помощью bind.pry:

Один фундаментальный вопрос о binding.pry, который должен быть понят, заключается в том, что его область охвата будет включать только текущий поток, а не каждый поток в многопоточном приложении, как вы описали.

Гипотетически, если binding.pry влияет на каждый поток (опять же, это не так), это приведет к непредсказуемому поведению, особенно если некоторые потоки выполняют операции доступа к данным/поиска/обновления.

Чтобы выполнить то, что вы хотите, вам может потребоваться другой подход:

Хотя это может быть утомительно в зависимости от того, сколько потоков в вашем приложении, вам может потребоваться контролировать/останавливать каждый поток отдельно. Очевидно, это можно сделать с помощью Thread.stop.

Ответ 3

Согласно часто задаваемые вопросы:

Нити не работают, что не так?

Некоторые системы (в частности, Mac OS X) используют Editline вместо GNU Readline. В настоящее время библиотека Ruby Readline имеет проблему при компиляции с помощью библиотеки Editline, которая блокирует все потоки, а не только вызов Readline.readline() (он блокирует, удерживая глобальную блокировку VM, не позволяя другим потокам ее приобретать). Это можно устранить, установив GNU Readline в OS X.

Итак, похоже, что Rubys, созданный с помощью Editline, блокирует все потоки, как вы ожидаете, и что разработчики Pry считают это ошибкой. Я не мог найти способ использовать Pry для автоматического блокирования всех потоков от одного вызова до binding.pry.

Этот ответ предлагает использовать Mutex для синхронизации ваших потоков перед вызовом binding.pry. Очевидно, что это требует изменений в коде, и вы можете не захотеть синхронизировать потоки только ради отладки. С другой стороны, синхронизация потоков уменьшает неопределенность и может облегчить отладку без использования Pry вообще.

В качестве альтернативы вы можете установить точки останова в каждом потоке:

require 'pry'

Thread.new do
  10.times do |i|
    binding.pry
    puts "subthread: #{i}"
  end
end


10.times do |i|
  binding.pry
  puts "main thread: #{i}"
end

Это остановит выполнение как для основного, так и для subthreads в определенный момент. К сожалению, это не помогает, если вы хотите проверить другой поток или точно знать, где он находился, когда текущий поток был остановлен. Это также немного боль, чтобы прокомментировать все точки останова, если они в настоящее время не отлаживаются. (Но вы можете полностью отключить Pry, установив переменную окружения DISABLE_PRY в значение, отличное от нуля.)

Если вам действительно нужно отлаживать многопоточную программу, вы, вероятно, захотите использовать что-то вроде GDB, что обеспечивает большую поддержку.