ConradIrwin / pry-rescue

Start a pry session whenever something goes wrong.
MIT License
850 stars 49 forks source link

When peeking via SIGQUIT, every 'require' causes an error #52

Open zach opened 10 years ago

zach commented 10 years ago

I have had poor luck with getting peek to work via Ctrl-\ since every require statement seems to cause an error, worst of all being those in Pry :before_session hooks and those from CodeRay autoloads. As a result, I never get to see the code I'm trying to peek at, nor can I explore the stack or the other stuff. I really like this functionality and would love to make this work, but if require doesn't work I don't understand how it ever did.

Here are some example sessions:

^\Preparing to peek via pry!
require 'pry-debugger' # Failed, saying: can't be called from trap context

Frame number: 0/44
Frame type: method
before_session hook failed: ThreadError: can't be called from trap context

(hangs)

^\Preparing to peek via pry!
require 'pry-debugger' # Failed, saying: can't be called from trap context
require 'pry-doc' # Failed, saying: can't be called from trap context
require 'pry-git' # Failed, saying: can't be called from trap context
require 'pry-rails' # Failed, saying: can't be called from trap context
require 'pry-remote' # Failed, saying: can't be called from trap context

Frame number: 0/2
Frame type: block
before_session hook failed: ThreadError: can't be called from trap context
/Users/zach/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/coderay-1.1.0/lib/coderay.rb:169:in `scan'
(see _pry_.hooks.errors to debug)
before_session hook failed: ThreadError: can't be called from trap context
/Users/zach/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/interception-0.3/lib/interception.rb:64:in `synchronize'
(see _pry_.hooks.errors to debug)
[1] (pry) main: 0> up
ThreadError: can't be called from trap context
from /Users/zach/.rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/coderay-1.1.0/lib/coderay.rb:169:in `scan'
[1] (pry) main: 0>

This is in Ruby 2.0 on OS X 10.9. Maybe there's something I don't have configured properly, but even a simple script that I break into with Ctrl-\ has this problem. I made a repo with this example (run main.rb and use Ctrl-\ or just run the test script and peek after it prints the first line of numbers):

https://github.com/zach/pry-rescue-peek-test

Why does this seem to work in other circumstances and yet I can't seem to make this work? Is this a Bundler-related issue?

Incanus3 commented 10 years ago

+1 I get exactly the same errors when trying to rescue a hanging rspec test using rescue rspec and Ctrl-\ in ruby 2.1, pry-rescue 1.2.0.

Incanus3 commented 10 years ago

I just did bundle update to pry-rescue 1.3.1 and interception 0.4. It's still failing, but the output changed a bit:

^\Preparing to peek via pry!
Error loading ./.pryrc: can't be called from trap context
/home/jakub/.rvm/gems/ruby-2.1.0@objednavky/gems/activesupport-4.0.1/lib/active_support/dependencies.rb:229:in `require'
require 'pry-doc' # Failed, saying: can't be called from trap context
require 'pry-docmore' # Failed, saying: can't be called from trap context
require 'pry-rails' # Failed, saying: can't be called from trap context

Frame number: 0/54
Frame type: method
before_session hook failed: RegexpError: empty char-class: /^define_singleton_method\(?\s*[:\"\'][]|^def\s*self\.[]/
/home/jakub/.rvm/gems/ruby-2.1.0@global/gems/pry-0.9.12.4/lib/pry/method.rb:182:in `singleton_method_definition?'
(see _pry_.hooks.errors to debug)
before_session hook failed: ThreadError: can't be called from trap context
/home/jakub/.rvm/gems/ruby-2.1.0@objednavky/gems/interception-0.4/lib/interception.rb:64:in `synchronize'
(see _pry_.hooks.errors to debug)

Not really sure what the last line means, but any command after this fails. Can provide more info if you tell me where to look.

zach commented 10 years ago

I found what seems to be the root cause, which is that Ruby has a very limited ability to perform I/O (in this case, apparently including reading files via require) inside signal handlers in Ruby 2.0. In 1.9.x performing such actions could cause deadlock, so in 2.0 they are simply not allowed. Thanks to @mperham I was able to pick up on this quickly, since he wrote up his experience encountering this in Sidekiq:

http://www.mikeperham.com/2013/02/23/signal-handling-with-ruby/

There are some workarounds for libraries like Foreman (see https://github.com/ddollar/foreman/issues/332 for their pipe-based solution), but they don't have to interrupt arbitrary code in its current execution state. I don't know enough about the details of pry and ruby execution state to say whether there might be a workaround for pry-rescue.

This looks like a tough one. Any ideas?

ConradIrwin commented 10 years ago

Is there anything we can do in a signal handler to escape? e.g. Signal.trap{ <capture bindings>; Thread.new{ <go wild> }.join }

zach commented 10 years ago

Update: Guard has tackled this issue as well using a separate thread. I don't think it has access to the current binding, but that's a lot to ask. https://github.com/guard/guard/issues/571

elia commented 10 years ago

As done by @mperham the trap should just add signals to a queue (an array or, better, a queue*) and then you can keep a loop inside a thread that will pop from the event queue.

* the queue is better because avoids using sleep by using the blocking #pop. Using #sleep of course is fine in sidekiq but not in a user facing app

elia commented 10 years ago

@mperham ouch! https://twitter.com/elia/status/477581841361534976 :frog:

stdedos commented 6 years ago

Similar backtrace:

[5] pry(Timeout)> yield(0)
^\Preparing to peek via pry!
ThreadError: can't be called from trap context
from /data/ruby/lib/ruby/2.4.0/monitor.rb:187:in `lock'
[6] pry(Timeout)> bt
=> nil
[7] pry(Timeout)> backtrace
--> #0  #<Class:Timeout>.timeout(sec#Float, klass#NilClass, message#NilClass) at /spool/ruby/lib/ruby/2.4.0/timeout.rb:77

And the full trace:


#<ThreadError: can't be called from trap context>
/data/ruby/lib/ruby/2.4.0/monitor.rb:187:in `lock'
/data/ruby/lib/ruby/2.4.0/monitor.rb:187:in `mon_enter'
/data/ruby/lib/ruby/site_ruby/2.4.0/rubygems/core_ext/kernel_require.rb:40:in `require'
/data/ruby/lib/ruby/gems/2.4.0/gems/pry-rescue-1.4.5/lib/pry-rescue/peek.rb:10:in `peek!'
(and then mixed code with gems: event-machine, aspector, timeout)