ruby-on-rails - Ruby 以 block 的形式创建 tar 球以避免内存不足错误

标签 ruby-on-rails ruby

我正在尝试重新使用以下代码来创建 tar 球:

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

如何分块执行以避免内存问题?

最佳答案

看起来问题可能出在这一行:

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

您正在通过执行f.read来“窃取”您的输入文件。 slurping 意味着整个文件被读入内存,这根本不可扩展,并且是使用不带长度的 read 的结果。

相反,我会做一些事情来读取和写入 block 中的文件,以便您拥有一致的内存使用情况。读取 1MB block 。您可以根据自己的需要进行调整:

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关于阅读:

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" 编写,因为 tarball 包含二进制(压缩)数据:

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 应该位于文件的顶部,因为它是一个常量并且是“可调整的”——比代码主体更可能被更改的内容。

该方法返回 tarball 的路径,而不是像原始代码那样返回 IO 句柄。使用 IO.open 的 block 形式会自动关闭输出,这将导致任何后续 open 自动倒带。我更喜欢传递路径字符串而不是文件的 IO 句柄。

我还将一些方法参数括在括号中。虽然 Ruby 中的方法参数不需要括号,而且有些人会避开它们,但我认为它们通过界定参数的开始和结束位置使代码更易于维护。当您向方法传递参数和 block 时,它们还可以避免混淆 Ruby——众所周知,这是导致错误的原因。

关于ruby-on-rails - Ruby 以 block 的形式创建 tar 球以避免内存不足错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16669557/

相关文章:

ruby-on-rails - Rails 有多种条件

ruby-on-rails - 在数据库中存储正则表达式

ruby - Ruby 中的 p 方法很难搜索

mysql - Rails,将列添加到选择语句

javascript - 如果禁用了 Javascript,则停止加载页面

javascript - 更改 EJS 模板目录

ruby-on-rails - 使用 webpack dev server 和 rails server 进行 webpack 热重载

ruby-on-rails - 在 rspec 中不期望任何记录器时有记录器的错误消息

ruby - 在 Mac OSX 10.8.2 上安装 Jekyll 时出错

mysql - 项目中缺少 database.yml 文件