Почему в Ruby происходит сбой popen3 из-за того, что "слишком много файлов открыто"?

Я использую Popen3 для запуска некоторых скриптов Perl, а затем выгрузки их вывода в текстовый файл. Оказавшись в текстовом файле, я ищу результат сценария Perl. Я получаю ошибку после запуска в течение примерно 40 минут, что составляет около 220 файлов.

ruby/1.8/open3.rb:49:in `pipe': Too many open files (Errno::EMFILE)
    from /ruby/1.8/open3.rb:49:in `popen3'
    from ./RunAtfs.rb:9
    from ./RunAtfs.rb:8:in `glob'
    from ./RunAtfs.rb:8

Сценарий ниже.

require 'logger'
require 'open3'
atfFolder = ARGV[0]
testResult = ARGV[1]
res = "result.txt"
open('result.txt', 'w') { }
Dir.glob(atfFolder+'/*.pl') do |atfTest|
 Open3.popen3("atf.pl -c run-config.pl -t #{atfTest}") do |i, o, e, t|
   while line = e.gets
     $testFile = testResult + line[/^[0-9]+$/].to_s + "testOutput.txt"
      log = Logger.new($testFile)
      log.info(line)
      end
    log.close
end
 lastLine = `tail +1 #{$testFile}`
 file = File.open(res, 'a')
 if(lastLine.include? "(PASSED)")
    file.puts("Test #{atfTest} --> Passed")
    file.close
    File.delete($testFile)
 else
    file.puts("Test #{atfTest} --> Failed!")
    file.close
 end
end

Этот скрипт обрабатывает 4900 файлов Perl, поэтому я не знаю, слишком ли много файлов для popen3 или я не правильно его использую.

Спасибо за помощь!

Я реорганизовал свой сценарий после некоторых очень полезных указателей! Код работает отлично!

require 'open3'

atf_folder, test_result = ARGV[0, 2]
File.open('result.txt', 'w') do |file| end

Dir.glob("#{ atf_folder }/*.pl") do |atf_test|

test_file = atf_test[/\/\w+.\./][1..-2].to_s + ".txt"
comp_test_path = test_result + test_file
File.open(comp_test_path, 'w') do |file| end

Open3.popen3("atf.pl -c run-config.pl -t #{ atf_test }") do |i, o, e, t|

    while line = e.gets

      File.open(comp_test_path, 'a') do |file|
        file.puts(line)
      end
    end
end

last_line = `tail +1 #{comp_test_path}`
File.open('result.txt', 'a') do |file|

    output_str = if (last_line.include? "(PASSED)")

    File.delete(comp_test_path)

    "Passed"

    else

    "Failed!"

end

file.puts "Test #{ atf_test } --> #{ output_str }"

end
end

1 ответ

Решение

Учти это:

require 'logger'
require 'open3'

atf_folder, test_result = ARGV[0, 2]

Dir.glob("#{ atf_folder }/*.pl") do |atf_test|

  Open3.popen3("atf.pl -c run-config.pl -t #{ atf_test }") do |i, o, e, t|

    while line = e.gets
      $testFile = test_result + line[/^[0-9]+$/].to_s + "testOutput.txt"
      log = Logger.new($testFile)
      log.info(line)
      log.close
    end

  end

  lastLine = `tail +1 #{ $testFile }`
  File.open('result.txt', 'a') do |file|

    output_str = if (lastLine.include? "(PASSED)")

                  File.delete($testFile)

                  "Passed"

                else

                  "Failed!"

                end

    file.puts "Test #{ atf_test } --> #{ output_str }"

  end

end

Конечно, он не проверен, поскольку нет примеров данных, но он написан более неубедительно для Ruby.

Что следует отметить:

  • atf_folder, test_result = ARGV[0, 2] разрезает массив ARGV и использует параллельное назначение для получения обоих параметров одновременно. Вы должны проверить, что вы получили значения для них. И, когда вы переходите к более сложным сценариям, воспользуйтесь классом OptionParser, входящим в состав Ruby STDLIB.
  • Руби позволяет нам передать блок File.open, который автоматически закрывает файл при выходе из блока. Это основная сила Ruby и помогает уменьшить количество ошибок, которые вы видите. Logger не делает этого, поэтому нужно проявлять особую осторожность, чтобы не оставлять висящих файловых дескрипторов, как вы делаете. Вместо этого используйте:

      log = Logger.new($testFile)
      log.info(line)
      log.close
    

    немедленно закрыть ручку. Вы делаете это вне цикла, а не внутри него, поэтому у вас было множество открытых ручек.

    Также подумайте, нужен ли вам Logger, или если обычный File.open было бы достаточно. У логгера есть дополнительные накладные расходы.

  • Ваше использование $testFile сомнительно $variables являются глобальными, и их использование обычно является показателем того, что вы делаете что-то не так, по крайней мере, до тех пор, пока не поймете, почему и когда вы должны их использовать. Я бы рефакторинг кода с использованием этого.
  • В Ruby переменные и методы находятся в snake_case, а не в CamelCase, который используется для классов и модулей. Это не так уж много, пока_you_run_into CodeDoingTheWrongThing и_have_to_read_it. (Заметьте, как ваш мозг увяз в разгадке верблюжьего чемодана?)

В общем, я сомневаюсь, что это самый быстрый способ сделать то, что вы хотите. Я подозреваю, что вы могли бы написать сценарий оболочки, используя grep или же tail это, по крайней мере, не отставать, и, возможно, работать быстрее. Вы можете сесть с вашим системным администратором и немного поразмыслить.

Другие вопросы по тегам