黄瓜测试失败并显示“流已关闭(IOError)”。

10
我们目前正在将Rails应用程序升级到Rails 4。在3.2中,我们的Cucumber(1.3.17)测试运行良好(虽然有点慢),底层使用了Capybara(2.4.4)、Poltergeist(1.5.1)和PhantomJS(1.9.8)。
但是在4.0.12和4.1.8中,我们会在随机运行点上收到“流关闭(IOError)”的错误提示。
stream closed (IOError)
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/formatter/pretty.rb:156:in `write'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/formatter/pretty.rb:156:in `puts'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/formatter/pretty.rb:156:in `step_name'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:181:in `block in send_to_all'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:179:in `each'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:179:in `send_to_all'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:173:in `broadcast'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:119:in `visit_step_name'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:112:in `block in visit_step_result'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:170:in `broadcast'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:111:in `visit_step_result'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/step_invocation.rb:43:in `visit_step_result'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/step_invocation.rb:39:in `accept'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:106:in `block in visit_step'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:170:in `broadcast'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:105:in `visit_step'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/step_collection.rb:19:in `block in accept'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/step_collection.rb:18:in `each'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/step_collection.rb:18:in `accept'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:100:in `block in visit_steps'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:170:in `broadcast'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:99:in `visit_steps'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:15:in `block in execute'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/runtime.rb:83:in `block (2 levels) in with_hooks'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/runtime.rb:99:in `before_and_after'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/runtime.rb:82:in `block in with_hooks'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/runtime/support_code.rb:120:in `call'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/runtime/support_code.rb:120:in `block (3 levels) in around'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/language_support/language_methods.rb:9:in `block in around'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/language_support/language_methods.rb:97:in `call'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/language_support/language_methods.rb:97:in `execute_around'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/language_support/language_methods.rb:8:in `around'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/runtime/support_code.rb:119:in `block (2 levels) in around'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/runtime/support_code.rb:123:in `call'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/runtime/support_code.rb:123:in `around'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/runtime.rb:94:in `around'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/runtime.rb:81:in `with_hooks'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:13:in `execute'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/scenario.rb:32:in `block in accept'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/scenario.rb:79:in `with_visitor'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/scenario.rb:31:in `accept'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:58:in `block in visit_feature_element'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:170:in `broadcast'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:57:in `visit_feature_element'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/feature.rb:38:in `block in accept'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/feature.rb:37:in `each'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/feature.rb:37:in `accept'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:27:in `block in visit_feature'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:170:in `broadcast'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:26:in `visit_feature'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/features.rb:28:in `block in accept'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/features.rb:17:in `each'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/features.rb:17:in `each'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/features.rb:27:in `accept'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:21:in `block in visit_features'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:170:in `broadcast'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/ast/tree_walker.rb:20:in `visit_features'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/runtime.rb:49:in `run!'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/lib/cucumber/cli/main.rb:47:in `execute!'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/gems/cucumber-1.3.17/bin/cucumber:13:in `'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/bin/cucumber:23:in `load'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/bin/cucumber:23:in `'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/bin/ruby_executable_hooks:15:in `eval'
/var/lib/jenkins/.rvm/gems/ruby-2.1.5@tallama-integration/bin/ruby_executable_hooks:15:in `'

这个错误与“流已关闭”错误不同,后者表示您在流关闭后尝试操作该流。
不,MRI中唯一抛出流关闭(IOError)的时间是在rb_thread_fd_close中。该代码仅在rb_io_close中调用-即在关闭文件描述符时;它遍历其他活动线程并查看是否有任何线程正在等待关闭的文件描述符。如果有任何人在等待,则在等待线程中引发流关闭错误。这清楚地解释了为什么堆栈跟踪指向cucumber:那是正在等待从格式化程序写出到STDOUT的线程。但我们没有关于谁关闭文件描述符的任何信息。

由于我知道我正在处理线程,所以我开始怀疑超时在不应该的时候杀死了某些东西,但我无法知道timeout导致混乱的原因; 我从我的代码中删除了一个timeout,但发现我们使用的各种宝石中有35个,问题仍然存在。

我们还怀疑,在某些情况下,由于内存不足而杀死了其中一个phantomjs进程,并为每个场景添加了记录当前进程内存的puts工具… IOError几乎肯定是由于时间问题消失了。 当我删除记录工具时,错误就再次出现。

我还尝试过更新到最新版本的cucumber和capybara,但似乎都没有产生任何影响。

有没有一些聪明的方法来追踪这个问题,我还没有尝试过? 你见过这个问题并知道我需要进行的一行配置更改吗? 你有一个方便的补丁可以将timeout从所有Ruby中删除吗?


你是在通过虚拟机进行测试吗?其中主机文件位于主操作系统上,并通过NFS/其他方式与虚拟机共享?我曾经遇到过类似的错误,那是由于这些手段引入了文件系统延迟。 - SnakeWasTheNameTheyGaveMe
@JakeTheSnake 不,它是一个虚拟机,但文件托管在虚拟磁盘上。 - TALlama
2个回答

2
尝试的想法...
  • Narrow the issue by trying Capybara + WebKit; Poltergeist is not thread safe (AFAIK).

  • Narrow the issue by seeing if it's a Rack timeout:

    # config/initializers/timeout.rb
    Rack::Timeout.timeout = 600 
    Rack::Timeout.wait_timeout = 600 # or 0 means never timeout
    
  • Temporarily turn off any tests that call to externals, such as system, sidekiq, eventmachine, delayed job, etc.

  • For your idea of a patch that removes timeout from all Ruby everywhere:

    require 'timeout'
    Thread.handle_interrupt(TimeoutError => :never) {
      timeout(10){
        # TimeoutError doesn't occur here
        Thread.handle_interrupt(TimeoutError => :on_blocking) {
          # possible to be killed by TimeoutError
          # while blocking operation
        }
        # TimeoutError doesn't occur here
      }
    }
    

    See Guarding from Timeout Error

  • Track the open file objects at any breakpoints you want:

    # List all open File objects.
    ObjectSpace.each_object(File) do |f|
      puts "%s: %d" % [f.path, f.fileno] unless f.closed?
    end
    
    # List the "dangling" File object which we didn't store in a variable.
    ObjectSpace.each_object(File) do |f|
      unless f.closed?  
        printf "%s: %d\n", f.path, f.fileno unless f === filehandle
      end
    end
    

    See Where does Ruby keep track of its open file descriptors


0

试试这个:

在你的代码中,在打开文件进行读取后,你必须关闭它。 查看类似问题


如果我知道哪些文件被保持打开,那就容易了,但错误没有提供任何线索。我甚至不确定是我让这些文件保持打开的;它可能是来自 gem 的临时文件。很可能是我的问题,因为这个错误在网上很难找到,这不是一个常见问题,但我无从得知。 - TALlama

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接