Search code examples
rubyirb

Ruby: run irb from trap context?


The following example is given in the book Programming Ruby 1.9 & 2.0:

require 'irb'

trap "INT" do
  IRB.start
end

count = 0
loop do
  count += 1
  puts count
  puts "Value = #{@value}" if defined? @value
  sleep 1
end

Unfortunately, if I run this code with my Ruby 2.5.1 setup I get the following ThreadError (run_irb.rb is the name of the file with the code above):

    24: from run_irb.rb:8:in `<main>'
    23: from run_irb.rb:8:in `loop'
    22: from run_irb.rb:12:in `block in <main>'
    21: from run_irb.rb:12:in `sleep'
    20: from run_irb.rb:4:in `block in <main>'
    19: from /usr/lib/ruby/2.5.0/irb.rb:376:in `start'
    18: from /usr/lib/ruby/2.5.0/irb/init.rb:17:in `setup'
    17: from /usr/lib/ruby/2.5.0/irb/init.rb:112:in `init_config'
    16: from /usr/lib/ruby/2.5.0/irb/init.rb:112:in `new'
    15: from /usr/lib/ruby/2.5.0/irb/locale.rb:32:in `initialize'
    14: from /usr/lib/ruby/2.5.0/irb/locale.rb:108:in `load'
    13: from /usr/lib/ruby/2.5.0/irb/locale.rb:124:in `find'
    12: from /usr/lib/ruby/2.5.0/irb/locale.rb:145:in `search_file'
    11: from /usr/lib/ruby/2.5.0/irb/locale.rb:157:in `each_localized_path'
    10: from /usr/lib/ruby/2.5.0/irb/locale.rb:167:in `each_sublocale'
     9: from /usr/lib/ruby/2.5.0/irb/locale.rb:158:in `block in each_localized_path'
     8: from /usr/lib/ruby/2.5.0/irb/locale.rb:150:in `block in search_file'
     7: from /usr/lib/ruby/2.5.0/rubygems.rb:213:in `try_activate'
     6: from /usr/lib/ruby/2.5.0/rubygems/specification.rb:1063:in `find_by_path'
     5: from /usr/lib/ruby/2.5.0/rubygems/specification.rb:1063:in `find'
     4: from /usr/lib/ruby/2.5.0/rubygems/specification.rb:1063:in `each'
     3: from /usr/lib/ruby/2.5.0/rubygems/specification.rb:1064:in `block in find_by_path'
     2: from /usr/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:40:in `require'
     1: from /usr/lib/ruby/2.5.0/monitor.rb:185:in `mon_enter'
/usr/lib/ruby/2.5.0/monitor.rb:185:in `lock': can't be called from trap context (ThreadError)

Is there a way to get the irb run from the trap context with Ruby 2.5.1?


Solution

  • Ruby 2.0 does not allow Mutex#lock within a signal handler

    According to this bug 7917 in ruby lang this is the "expected" behaviour. So you've basically found a language bug and a book bug because the author didn't test this example in ruby 2.0 (confirmed that it works in 1.9.)

    An alternative method is to use an exception/rescue and execute an IRB session from an interrupt like this:

    require 'irb'
    
    count = 0
    loop do
      count += 1
      puts count
      puts "Value = #{@value}" if defined? @value
      sleep 1
    rescue Interrupt => e
      IRB.start
      break
    end 
    

    The above will correctly trap an interrupt and allow an IRB session to start. I did notice that IRB.start won't give you a local execution context, so you may want to look at binding.irb that let's you start a more useful debugging session like this:

    $ ruby thread-test.rb
    1
    2
    ^C
    [1] irb(main)>
    
    From: /work/_scratch/thread-test/thread-test.rb @ line 11 :
    
         6:   puts count
         7:   puts "Value = #{@value}" if defined? @value
         8:   sleep 1
         9: rescue Interrupt => e
        10:   binding.irb
     => 11:   break
        12: end
    
    [2] irb(main)> e
    => Interrupt
    [3] irb(main)> count
    => 2
    

    I found a few blog posts on handling Signals, Traps, and Rescues and Graceful Shutdown that may help you further with your problem.