2013-05-21 21 views
1

我試圖重新使用下面的代碼來創建一個焦油球:紅寶石創建塊焦油球,以避免內存不足的錯誤

tarfile = File.open("#{Pathname.new(path).realpath.to_s}.tar","w") 
     Gem::Package::TarWriter.new(tarfile) do |tar| 
     Dir[File.join(path, "**/*")].each do |file| 
      mode = File.stat(file).mode 
      relative_file = file.sub /^#{Regexp::escape path}\/?/, '' 
      if File.directory?(file) 
      tar.mkdir relative_file, mode 
      else 
      tar.add_file relative_file, mode do |tf| 
       File.open(file, "rb") { |f| tf.write f.read } 
      end 
      end 
     end 
     end 
     tarfile.rewind 
     tarfile 

它正常工作,儘可能只有很小的文件夾包括但任何大的失敗,出現以下錯誤:

Error: Your application used more memory than the safety cap 

我怎麼能做到這一點的塊,以避免內存問題?

+0

是否有理由不能執行對系統tar命令的調用? – mcfinnigan

+0

是的,應用程序必須是可移植的。 – zulq

+0

你使用的是什麼ruby環境?如果是JRuby,則可以通過-J-Xmx啓動選項增加可用於JVM的虛擬內存量;詳細在這裏:http://stackoverflow.com/questions/6910429/how-do-i-specify-heap-size-configuration-in-a-config-file – mcfinnigan

回答

2

它看起來像問題可能是在這一行:

File.open(file, "rb") { |f| tf.write f.read } 

通過執行f.read來「輸入」輸入文件。 slurping意味着整個文件正在被讀入內存,這是不可擴展的,並且是使用read而沒有長度的結果。

相反,我會做一些讀取和寫入塊的文件,所以你有一個一致的內存使用情況。這讀入1MB塊。您可以調整自己的需要:

BLOCKSIZE_TO_READ = 1024 * 1000 

File.open(file, "rb") do |fi| 
    while buffer = fi.read(BLOCKSIZE_TO_READ) 
    tf.write buffer 
    end 
end 

這裏是the documentation說,關於read

If length is a positive integer, it try to read length bytes without any conversion (binary mode). It returns nil or a string whose length is 1 to length bytes. nil means it met EOF at beginning. The 1 to length-1 bytes string means it met EOF after reading the result. The length bytes string means it doesn’t meet EOF. The resulted string is always ASCII-8BIT encoding.

另外一個問題是,它看起來像你沒有正確地打開輸出文件:

tarfile = File.open("#{Pathname.new(path).realpath.to_s}.tar","w") 

由於"w"因爲您正在以「文本」模式寫入它。相反,你需要在二進制模式,"wb"寫,因爲壓縮包包含二進制(壓縮)數據:

tarfile = File.open("#{Pathname.new(path).realpath.to_s}.tar","wb") 

改寫原代碼更像我希望看到它,結果:

BLOCKSIZE_TO_READ = 1024 * 1000 

def create_tarball(path) 

    tar_filename = Pathname.new(path).realpath.to_path + '.tar' 

    File.open(tar_filename, 'wb') do |tarfile| 

    Gem::Package::TarWriter.new(tarfile) do |tar| 

     Dir[File.join(path, '**/*')].each do |file| 

     mode = File.stat(file).mode 
     relative_file = file.sub(/^#{ Regexp.escape(path) }\/?/, '') 

     if File.directory?(file) 
      tar.mkdir(relative_file, mode) 
     else 

      tar.add_file(relative_file, mode) do |tf| 
      File.open(file, 'rb') do |f| 
       while buffer = f.read(BLOCKSIZE_TO_READ) 
       tf.write buffer 
       end 
      end 
      end 

     end 
     end 
    end 
    end 

    tar_filename 

end 

BLOCKSIZE_TO_READ應該在你的文件的頂部,因爲它是一個常數,是一個「tweakable」 - 這是更有可能比代碼的身體改變。

該方法返回到tarball的路徑,而不是像原始代碼那樣的IO句柄。使用IO.open的塊形式自動關閉輸出,這將導致任何後續的open自動rewind。我更喜歡繞過路徑字符串而不是IO處理文件。

我還在封閉括號中包裝了一些方法參數。儘管Ruby中的方法參數不需要括號,並且有些人避開了它們,但我認爲他們通過定義參數開始和結束的位置來使代碼更易於維護。當你將參數和塊傳遞給方法時,它們也可以避免混淆Ruby - 一個衆所周知的錯誤原因。

+0

完美的男人,謝謝。 – zulq

+0

就像一般說明一樣,要非常懷疑使用'read'而不是逐行IO或「chunked」'read'的代碼。我們一次又一次發現人們填充內存並讓機器癱瘓的問題,因爲他們在生產中遇到了大文件。逐行IO非常快,並且簡化了很多文本文件處理問題,所以先抓住它。如果它是二進制文件,請首先進行塊讀取。 –

1

minitar看起來像寫入一個流,所以我不認爲內存將是一個問題。下面是pack方法的註釋和定義(如5月21日,2013年):從自述

# A convenience method to pack files specified by +src+ into +dest+. If 
# +src+ is an Array, then each file detailed therein will be packed into 
# the resulting Archive::Tar::Minitar::Output stream; if +recurse_dirs+ 
# is true, then directories will be recursed. 
# 
# If +src+ is an Array, it will be treated as the argument to Find.find; 
# all files matching will be packed. 
def pack(src, dest, recurse_dirs = true, &block) 
    Output.open(dest) do |outp| 
    if src.kind_of?(Array) 
     src.each do |entry| 
     pack_file(entry, outp, &block) 
     if dir?(entry) and recurse_dirs 
      Dir["#{entry}/**/**"].each do |ee| 
      pack_file(ee, outp, &block) 
      end                                                     
     end 
     end 
    else 
     Find.find(src) do |entry| 
     pack_file(entry, outp, &block) 
     end 
    end 
    end 
end 

實施例寫一個焦油:從自述

# Packs everything that matches Find.find('tests') 
File.open('test.tar', 'wb') { |tar| Minitar.pack('tests', tar) } 

實施例寫一個壓縮焦油

tgz = Zlib::GzipWriter.new(File.open('test.tgz', 'wb')) 
    # Warning: tgz will be closed! 
Minitar.pack('tests', tgz) 
+0

我見過minitar,但我很害怕使用它,因爲它似乎不被保留。 – zulq

+0

是的,它看起來好像還沒有被觸及。但是光明的一面是沒有公開的問題,整個lib只是一個文件。如果說到底,源代碼似乎並不像分叉和維護自己的分支那樣困難。 – veidt

+0

對紅寶石來說相當新鮮,因此不認爲現在是這樣做的正確時機。 – zulq