NoMethodError#message takes very long

A

Adiel Mittmann

Hello.

I've been noticing a problem for a while in a larger application and today I
produced a minimal example, given below. When a method is not found, Sinatra
calls NoMethodError#message to display a nice error message, but that is taking
VERY long.

<snip>
require 'sinatra'
require 'nokogiri'
get '/' do
@xml = Nokogiri::XML(File.new('baz.xml', 'rb').read())
foo()
'hello'
end
</snip>

The file 'baz.xml' can be just a simple file, like a root element containing
some thousands of elements. The number of elements is important because, oddly
enough, the time taken for the message to be displayed is proportional to the
size of the XML file. In my system, with a 3.7 MB file this program takes almost
13 seconds to print the message, but in a larger application the message takes a
few minutes to appear.

You can try to print the line yourself instead, but that also takes very long:

<snip>
begin
foo()
rescue ::Exception => boom
puts boom.message
end
<snip>

I don't know where the error might be. The code above looks too innocent to
cause such a weird behavior, and I don't see how either Nokogiri or Sinatra
could be involved, although both of them are required to reproduce the problem.

I'm using Ruby 1.9.2p180, Nokogiri 1.4.4 and Sinatra 1.2.0.

Any help would be greatly appreciated!
 
E

Eric Hodel

Hello.
=20
I've been noticing a problem for a while in a larger application and = today I
produced a minimal example, given below. When a method is not found, = Sinatra
calls NoMethodError#message to display a nice error message, but that = is taking
VERY long.
=20
<snip>
require 'sinatra'
require 'nokogiri'
get '/' do
@xml =3D Nokogiri::XML(File.new('baz.xml', 'rb').read())
foo()
'hello'
end
</snip>
=20
The file 'baz.xml' can be just a simple file, like a root element = containing
some thousands of elements. The number of elements is important = because, oddly
enough, the time taken for the message to be displayed is proportional = to the
size of the XML file. In my system, with a 3.7 MB file this program = takes almost
13 seconds to print the message, but in a larger application the = message takes a
few minutes to appear.

Let me see if I have this straight:

If baz.xml is empty it is fast
If baz.xml is large it is slow

If this is true it is because it takes time to parse the XML file before =
it gets to foo and discovers that there is no method foo.

Ruby does not know that foo does not exist until you try to call it.=
 
R

Robert Klemme

Let me see if I have this straight:

If baz.xml is empty it is fast
If baz.xml is large it is slow

If this is true it is because it takes time to parse the XML file before =
it gets to foo and discovers that there is no method foo.
Ruby does not know that foo does not exist until you try to call it.

Additional remarks: the file is not properly closed and reading the
whole file in one go is probably also not a good solution. I'd rather

require 'sinatra'
require 'nokogiri'
get '/' do
@xml =3D File.open('baz.xml', 'rb') {|io| Nokogiri::XML(io)}
foo()
'hello'
end

Note, the fact that you read in a large file as one String can
actually be related to your slowness because it might case GC
overhead.

Cheers

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
A

Adiel Mittmann

Hello.

Let me see if I have this straight:

If baz.xml is empty it is fast
If baz.xml is large it is slow

If this is true it is because it takes time to parse the XML file before it gets to foo and discovers that there is no method foo.

Ruby does not know that foo does not exist until you try to call it.

The thing is: if method foo is not called at all, and thus no exception is
raised, the resulting web page is shown quickly enough. Nokogiri is pretty fast
at parsing XML files.

It only takes long when the inexistent method foo() is called and the pogram
tries to print the exception message.

Yes, I know this is *very*, *very* weird. Can anybody try to reproduce this bug?
The code snippet is very simple and it shouldn't take more than a couple
minutes.

Thanks for the reply.
 
R

Robert Klemme

Hello.



The thing is: if method foo is not called at all, and thus no exception is
raised, the resulting web page is shown quickly enough. Nokogiri is pretty fast
at parsing XML files.

It only takes long when the inexistent method foo() is called and the pogram
tries to print the exception message.

Yes, I know this is *very*, *very* weird. Can anybody try to reproduce this bug?
The code snippet is very simple and it shouldn't take more than a couple
minutes.

Not if one does not have the environment installed. Did you try the
suggested change?

Cheers

robert
 
A

Adiel Mittmann

Hello.

Additional remarks: the file is not properly closed and reading the
whole file in one go is probably also not a good solution. I'd rather

require 'sinatra'
require 'nokogiri'
get '/' do
@xml = File.open('baz.xml', 'rb') {|io| Nokogiri::XML(io)}
foo()
'hello'
end

Note, the fact that you read in a large file as one String can
actually be related to your slowness because it might case GC
overhead.

That is indeed a better way to read the file, thanks for pointing it out.

The same problem happens with the code you suggested, however.

In any case, did you test any of the variants? Was it very slow as in my case?

Thank you for your reply.
 
A

Adiel Mittmann

Hi, I discovered what's causing the problem.

Ruby's function name_err_mesg_to_str (file error.c, line 929, SVN snapshot),
which produces the dreaded message, proceeds as follows:

1. It converts the object that does not possess the method in question to a
string, by calling #inspect;

2. If it doesn't like that string (i.e., if it's nil or is bigger than 65
characters), then it creates a simpler representation and use it instead.

In other words, if you happen to call a method on an object that doesn't define
to_s but that does contain a lot of data, then calling an undefined method and
trying to print the resulting exception's message will take a long time because
all the data in the object will have to be converted to a string -- just to be
discarded.

Maybe in a case like this the message should always return the simpler
representation of the object instead of calling to_s?
 
R

Robert Klemme

Hi, I discovered what's causing the problem.

Ruby's function name_err_mesg_to_str (file error.c, line 929, SVN snapshot),
which produces the dreaded message, proceeds as follows:

1. It converts the object that does not possess the method in question to a
string, by calling #inspect;

2. If it doesn't like that string (i.e., if it's nil or is bigger than 65
characters), then it creates a simpler representation and use it instead.

In other words, if you happen to call a method on an object that doesn't define
to_s but that does contain a lot of data, then calling an undefined method and
trying to print the resulting exception's message will take a long time because
all the data in the object will have to be converted to a string -- just to be
discarded.

Maybe in a case like this the message should always return the simpler
representation of the object instead of calling to_s?

I'd rather provide a custom #inspect method for objects which may
contain potentially large volumes of data. Nobody can read that
anyway when printed on console or somewhere else.

Kind regards

robert
 
A

Adiel Mittmann

Hello.

I'd rather provide a custom #inspect method for objects which may
contain potentially large volumes of data. Nobody can read that
anyway when printed on console or somewhere else.

I agree with you: if I am going to print out a big object to the console, it's
better to provide a custom #inspect in order to generate a nice summary.

However, I find it odd that I *must* write such a method for all big objects,
regardless of whether I'm ever going to print them out or not -- because
otherwise my program may eventually freeze for up to a few minutes doing the
very complicated task of telling me that some method didn't exist :)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,539
Members
45,024
Latest member
ARDU_PROgrammER

Latest Threads

Top