是什么会导致Ruby Nomethoderror回溯如此慢?

发布于 2025-01-23 07:31:31 字数 1300 浏览 4 评论 0原文

我正在开发一个非常大的红宝石(非轨道)应用程序。考虑到它的大小和复杂的数量,这是相当快的快速(请使用Ruby!),但是有时我会用方法名称并获取Nomethoderror。

通常,当这种情况发生时,应用程序将悬挂大约20到30秒以打印出回溯。

具体来说,如果我这样做:

puts "about to crash!"
Array.new().inspekt    # NoMethodError here

我会看到“即将崩溃!”马上,在我终于得到nomethoderror和backtrace之前,似乎没有发生20多岁左右。

起初,我认为这可能是“你的意思是”宝石,所以我在命令行上关闭了 - did_you_mean,这关闭了“你的意思是”建议,但是没有什么可以加快回溯。

有趣的是,这仅适用于Nomethoderror。

如果我引起其他例外,例如:

puts "about to crash!"
a = 3/0

那时我会立即看到回溯。

甚至使事情变得更奇怪,如果我在“即将崩溃”之后中断过程! (例如Unix上的Ctrl-C),然后我立即获得了Nomethoderror,它是回溯。因此,它具有信息 - 但是Ruby可能会试图清理某些东西,这只会在Nomethoderror上清理?

信息:Ruby 2.7.0

OS:Centos Linux版本7.5.1804

更新 - 到目前为止响应: 每个人似乎都在关注返回和分析红宝石代码。

除了在那里没有放缓。在减速过程中没有执行的红宝石代码行。在此之前的所有线路,“在回溯”中已经执行,并且在一秒钟左右的时间内。然后,系统悬挂在位置和nomethoderror之间。两者之间都没有Ruby代码,因此任何正在查看Ruby脚本中编写的代码的探险器都不会有帮助。放缓是Ruby的内部内容,并且不在我的代码中,除非我对正在发生的事情感到非常困惑。

非常清楚:

Line 10042:    puts "HERE"                  # Happens at ~1s
Line 10043:    Array.new().inspekt          # Happens at ~20-30s

这些行之间没有代码。在10042行执行之前,20-30不会在任何代码中发生,因此对没有帮助的分析。

do 还有其他暂停的纤维。但是这里没有代码能屈服。当击中异常时,是否有一些奇怪的内置收益代码试图运行其他(暂停)纤维?我想不出您想要这种行为的原因,以及它会造成灾难性的许多原因,但我想不出会引起这个问题的其他任何东西(这也可以用Ctrl-C杀死! )

I have a pretty large ruby (non-rails) application that I'm developing. It's reasonably fast considering how large and complex it is (go ruby!), but sometimes I fat finger a method name and get the NoMethodError.

And usually when this happens, the application hangs for like 20 to 30 seconds to just print out the backtrace.

Specifically, if I do something like this:

puts "about to crash!"
Array.new().inspekt    # NoMethodError here

I see the "about to crash!" right away, and then 20s or so nothing seems to happen before I finally get the NoMethodError and backtrace.

At first I thought it might be the "did you mean" gem, so I turned that off with --disable-did_you_mean on the command line, and that turned off the "did you mean" suggestions, but nothing sped up the backtrace.

What's interesting is that this is only for NoMethodError.

If I cause some other exception, such as:

puts "about to crash!"
a = 3/0

Then I see the backtrace immediately.

And to make things even weirder, if I interrupt the process right after the "about to crash!" (such as with a ctrl-c on unix) then I immediately get the NoMethodError and it's backtrace. So it has the information - but ruby is stuck on trying to clean something up perhaps, something that only gets cleaned up on NoMethodError?

Info: ruby 2.7.0

OS: CentOS Linux release 7.5.1804

UPDATE - to responses so far:
Everyone seems to be concerned about the backtrace and profiling the ruby code.

Except the slowdown is NOT happening there. There are NO LINES OF RUBY CODE that are executed during the slowdown. All of the lines prior to this, "in the backtrace" are already executed and in a matter of a second or so. Then the system hangs, between the puts and the NoMethodError. There is no ruby code in between to profile, so any profiler that is looking at code written in my ruby script isn't going to help. The slowdown is something internal to ruby and is not in my code, unless I'm terribly confused about what's happening.

To be very clear:

Line 10042:    puts "HERE"                  # Happens at ~1s
Line 10043:    Array.new().inspekt          # Happens at ~20-30s

There is no code between those lines to profile. The 20-30s is not happening in any code before line 10042 executes, so profiling that will not help.

I do have other Fibers that are paused. But there is no code here that yields to them. Is it possible that there's some strange built-in yield code that attempts to run other (paused) fibers when an exception is hit? I can't think of a reason you'd ever want this behavior, and many reasons why it would be catastrophic, but I can't think of anything else that would cause this problem (that is also killable with a ctrl-c!)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

允世 2025-01-30 07:31:31

我终于弄清楚了这个问题。

问题在于,Nomethoderror将使用Inspect,这将检查接收器的所有实例变量到Nomethoderror。

由于我正在编写一个相当大的程序,其中包含大量数据,因此我的许多顶级对象需要大量时间来计算“ Inspect”字符串。

有趣的是,如果您“ ctrl-c” nomethoderror,它将停止尝试构建检查字符串并仅使用类名称。我觉得其他实例试图构建检查字符串,并将在同一位置出现,打印出简单的nomethoderror,对幕后发生的事情(和失败)都不知道。

解决方案非常简单,我可以定义“检查”以使我的所有对象更合理。另一个选项是在nomethoderror中重新定义“详细信息”,以便在计算消息之前暂时重新定义错误的接收器“ to_s”的“检查”方法。对于具有相同类型的问题的其他例外,这可能会被概括,但我目前仅在Nomethoderror上看到它。

代码在下面 - 考虑到我对Singleton_classes的理解(缺乏)范围,我可能会做一些不太正确的事情,但是在各种测试中似乎都可以正常工作。请注意,仅当接收器明确定义to_s时,才能进行解决方法 - 这是该方法的第四行中的检查。这样会错过例如,例如,您继承了一堂课而不要重新定义to_s,因此您可能需要将其删除以为您的代码删除。 YMMV。

class NoMethodError
  def detailed_message(*a,**k)
    # Does the object *have* a to_s?  It could be unbound?
    return super(*a,**k) unless receiver.respond_to?(:to_s)
    # Does the object actually define to_s (itself)?
    return super(*a,**k) unless receiver.method(:to_s).owner == receiver.class

    # Save the old inspect
    oldInspect = receiver.respond_to?(:inspect) ? receiver.method(:inspect) : nil

    # Redefine inspect to call to_s
    receiver.define_singleton_method(:inspect) do; to_s; end

    # Get the detailed_message with to_s
    gotMessage = super(*a,**k)

    # Restore the inspect method
    if oldInspect
      receiver.define_singleton_method(:inspect, oldInspect)
    else
      # I think we could have just done this one...
      receiver.singleton_class.remove_method(:inspect)
    end

    # Return message
    gotMessage
  end
end

I finally figured out the issue.

The problem was that NoMethodError will use inspect, and this inspects out all the instance variables of the receiver to the NoMethodError.

Since I'm writing a fairly large program with massive objects containing lots of data, many of my top level objects take huge amounts of time to calculate the 'inspect' string.

Interestingly, if you 'ctrl-c' the NoMethodError, it stops trying to build the inspect string and just uses the class name. I have a feeling the other instances were running out of memory trying to build the inspect string and would end up in the same place, printing a simple NoMethodError, with no clue as to what was happening (and failing) behind the scenes.

The solution is pretty simple, I could just define 'inspect' to be more reasonable on all of my objects. Another option is to redefine the 'detailed_message' in NoMethodError so that it temporarily redefines the 'inspect' method on the receiver of the error to be 'to_s' before calculating the message. This could probably be generalized for other exceptions with the same types of issues, but I'm currently only seeing it on NoMethodError.

Code is below - I'm probably doing something not quite right with the scope considering my (lack of) understanding of singleton_classes, but it seems to work properly in the various tests could come up with. Note that it only does this workaround if to_s is defined explicitly by the receiver - this is a check in the 4th line of the method. This will miss cases where, for example, you inherit a class and don't redefine to_s, so you may want to remove that for your code. YMMV.

class NoMethodError
  def detailed_message(*a,**k)
    # Does the object *have* a to_s?  It could be unbound?
    return super(*a,**k) unless receiver.respond_to?(:to_s)
    # Does the object actually define to_s (itself)?
    return super(*a,**k) unless receiver.method(:to_s).owner == receiver.class

    # Save the old inspect
    oldInspect = receiver.respond_to?(:inspect) ? receiver.method(:inspect) : nil

    # Redefine inspect to call to_s
    receiver.define_singleton_method(:inspect) do; to_s; end

    # Get the detailed_message with to_s
    gotMessage = super(*a,**k)

    # Restore the inspect method
    if oldInspect
      receiver.define_singleton_method(:inspect, oldInspect)
    else
      # I think we could have just done this one...
      receiver.singleton_class.remove_method(:inspect)
    end

    # Return message
    gotMessage
  end
end
半城柳色半声笛 2025-01-30 07:31:31

我会尝试在那里调试完整的回溯,以查看我

begin
  puts "about to crash!"
  Array.new().inspekt
rescue => e
  puts e.backtrace
  raise # raise anyway
end

在我的情况下实际发生的事情,我会使用Ruby 2.6.3和IRB进行20行回溯通过修改backtrace的每个文件并在每个步骤打印时间来测量每个运行时,请调试YAY!

I would try to debug the full backtrace in there to see what is actually happening

begin
  puts "about to crash!"
  Array.new().inspekt
rescue => e
  puts e.backtrace
  raise # raise anyway
end

In my case I get 20 lines of backtrace with ruby 2.6.3 and irb, if that doesn't really tell you anything interesting I would then do the tedious work of measuring each runtime by modifying each file of the backtrace and printing the times at each step, debugging yay!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文