Как попросить ruby ​​pry остановить все остальные потоки

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

binding.pry

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


person pguardiario    schedule 28.05.2017    source источник


Ответы (3)


Похоже, вы предлагаете использовать вызов 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, но поток будет переведен в спящий режим до того, как вернет соединение в пул и получит ответ? Плохие вещи. Много плохого.

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

person anothermh    schedule 01.06.2017

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

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

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

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

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

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

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

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

person lax1089    schedule 01.06.2017

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

Треды не работают, что не так?

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

Получается, что Ruby, построенный с помощью 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

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

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

person Kathryn    schedule 01.06.2017