是什么导致Ruby NoMethodError回溯这么慢?

rbl8hiat  于 2023-05-22  发布在  Ruby
关注(0)|答案(2)|浏览(124)

我正在开发一个相当大的ruby(非rails)应用程序。考虑到它的庞大和复杂性,它的速度相当快(使用Ruby!),但有时我会用手指敲一个方法名,得到NoMethodError。
通常当这种情况发生时,应用程序会挂起20到30秒,以便打印出回溯。
具体地说,如果我做这样的事情:

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

我看到了“即将坠毁!“马上,然后20秒左右似乎什么都没有发生,然后我终于得到了NoMethodError和回溯。
起初我以为这可能是“did you mean”gem,所以我在命令行上使用--disable-did_you_mean关闭了它,这样就关闭了“did you mean”建议,但没有什么能加快回溯速度。
有趣的是,这只适用于NoMethodError。
如果我导致其他异常,例如:

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

然后我立即看到了回溯。
更奇怪的是,如果我在“即将崩溃”之后中断这个过程!”(例如在unix上使用ctrl-c),然后我立即得到NoMethodError,并且它是回溯。所以它有信息--但是ruby可能被困在试图清理一些东西上,一些只能在NoMethodError上清理的东西?
信息:ruby 2.7.0
操作系统:CentOS Linux 7.5.1804
更新-到目前为止的答复:每个人似乎都在关注Ruby代码的回溯和分析。
但减速并没有发生在那里。在减速期间没有执行Ruby代码行。在此之前的所有行,“在回溯中”已经被执行,并且在大约一秒左右的时间内。然后系统在puts和NoMethodError之间挂起。这中间没有ruby代码需要分析,所以任何分析器查看用ruby脚本编写的代码都不会有帮助。减速是ruby内部的事情,不在我的代码中,除非我对发生的事情感到非常困惑。
说清楚

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

这些行之间没有要分析的代码。在第10042行执行之前,20- 30 s不会在任何代码中发生,因此分析不会有帮助。
我 * 确实 * 有其他光纤被暂停。但是这里没有任何代码会屈服于它们。有没有可能存在一些奇怪的内置yield代码,当遇到异常时,这些代码会尝试运行其他(暂停的)纤程?我想不出你想要这种行为的原因,也想不出为什么它会是灾难性的,但是我想不出还有什么会导致这个问题的(这也是可以用ctrl-c杀死的!))

gtlvzcf8

gtlvzcf81#

我会尝试在那里调试完整的回溯,看看实际发生了什么

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

在我的例子中,我用ruby 2.6.3和irb得到了20行回溯,如果这没有告诉你任何有趣的事情,我会做一些繁琐的工作,通过修改回溯的每个文件来测量每个运行时,并打印每一步的时间,调试耶!

jv4diomz

jv4diomz2#

我终于弄明白了问题所在。
问题是NoMethodError将使用inspect,这将检查出NoMethodError的接收方的所有示例变量。
由于我正在编写一个相当大的程序,其中包含大量数据的大量对象,因此我的许多顶级对象都需要花费大量时间来计算“检查”字符串。
有趣的是,如果你'ctrl-c' NoMethodError,它会停止尝试构建inspect字符串,而只是使用类名。我有一种感觉,其他示例在试图构建inspect字符串时内存不足,最终会出现在同一个地方,打印一个简单的NoMethodError,而不知道幕后发生了什么(和失败)。
解决方案非常简单,我可以定义“inspect”,以便在所有对象上更合理。另一种选择是在NoMethodError中重新定义'detailed_message',以便在计算消息之前临时将错误接收方的'inspect'方法重新定义为'to_s'。这可能适用于具有相同类型问题的其他异常,但我目前只在NoMethodError上看到它。
代码如下-考虑到我对singleton_classes的理解(缺乏),我可能在范围内做了一些不太正确的事情,但它似乎在各种测试中正常工作。请注意,只有在接收方显式定义了to_s时,它才能解决这个问题--这是该方法第4行中的一个检查。这将错过一些情况,例如,您继承了一个类,但没有重新定义为_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

相关问题