Как попросить рубинов остановить все остальные потоки
Я пытаюсь отладить многопоточный 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, что обеспечивает большую поддержку.