Ruby exception.message takes too long

I see a very interesting and catastrophic behavior with ruby, see code below

class ExceptionTest def test @result = [0]*500000 begin no_such_method rescue Exception => ex puts "before #{ex.class}" st = Time.now ex.message puts "after #{Time.now-st} #{ex.message}" end end end ExceptionTest.new.test 

Ideally, ex.message should not take any time, and therefore the time should be in ms, but here is the output

 before NameError after 0.462443 undefined local variable or method `no_such_method' for #<ExceptionTest:0x007fc74a84e4f0> 

If I assign [0]*500000 local variable instead of an instance variable, for example. result = [0]*500000 runs as expected

 before NameError after 2.8e-05 undefined local variable or method `no_such_method' for #<ExceptionTest:0x007ff59204e518> 

It seems that somehow ex.message looping on instance variables, why do this, please enlighten me!

I tried it on ruby ​​ruby-1.9.2-p290, ruby-1.9.1-p376, ruby ​​2.0.0 and any version of Ruby on codepad.org.

Edit: file contains error http://bugs.ruby-lang.org/issues/8366

+4
source share
1 answer

After navigating to the source , I found that NameError#message tries to call inspect on its object first, and if this line calls to_s too long. He expected inspect take a lot of time as it recursively validates every instance variable. (See the documentation for verification.)

From error.c:

 d = rb_protect(rb_inspect, obj, &state); if (state) rb_set_errinfo(Qnil); if (NIL_P(d) || RSTRING_LEN(d) > 65) { d = rb_any_to_s(obj); } desc = RSTRING_PTR(d); 

You can roll up this test to make sure it has nothing to do with exceptions:

 class InspectTest def initialize @result = [0]*500000 end def test puts "before" st = Time.now self.inspect puts "after #{Time.now-st}" end end InspectTest.new.test #before #after 0.162566 InspectTest.new.foo # NoMethodError: undefined method `foo' for #<InspectTest:0x007fd7e317bf20> e=InspectTest.new.tap {|e| e.instance_variable_set(:@result, 0) } e.foo # NoMethodError: undefined method `foo' for #<InspectTest:0x007fd7e3184580 @result=0> e.test #before #after 1.5e-05 

If you know that your class will contain a lot of data and can cause many exceptions, you can theoretically override #inspect .

 class InspectTest def inspect to_s end end InspectTest.new.test #before #after 1.0e-05 
+2
source

Source: https://habr.com/ru/post/1479078/


All Articles