Почему в 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
это, по крайней мере, не отставать, и, возможно, работать быстрее. Вы можете сесть с вашим системным администратором и немного поразмыслить.