【问题标题】:Ruby TempFile behaviour among different classes不同类之间的 Ruby TempFile 行为
【发布时间】:2020-11-07 00:45:18
【问题描述】:

我们的处理服务器主要与 TempFiles 一起使用,因为它使我们的事情变得更容易:无需担心删除它们,因为它们会被垃圾收集或处理名称冲突等。

最近,我们遇到了 TempFiles 在这个过程中过早地被 GCed 的问题。尤其是我们的一项服务,它将 Foo 文件从 url 转换为一些 Bar 文件并将其上传到我们的服务器。

为了清楚起见,我在下面添加了一个案例场景,以便于讨论并提供一个示例。

此工作流程执行以下操作:

  1. 获取一个url作为参数
  2. 将 Foo 文件下载为 TempFile
  3. 将其复制到新的 TempFile
  4. 将相关资产下载到 TempFiles
  5. 将相关资产链接到本地​​ dup TempFile
  6. 将 Foo 转换为 Bar 格式
  7. 上传到我们的服务器

有时转换失败,一切都表明我们的本地 Foo 文件指向在转换之前已创建和 GC 的相关资产。

我的两个问题:

  1. 我的 TempFiles 是否有可能过早地被 GCed?我阅读了有关 Ruby GCed 系统的信息,避免这些情况非常保守。

  2. 如何避免这种情况发生?我可以尝试从download_and_replace_uri(node) 保存所有相关资产,并将它们作为回报传递,以在ConvertService 的实例仍然存在时使其保持活力。但我不确定这是否会解决它。

myfile.foo

{
  "buffers": [
    { "uri": "http://example.com/any_file.jpg" },
    { "uri": "http://example.com/any_file.png" },
    { "uri": "http://example.com/any_file.jpmp3" }
  ]
}

ma​​in.rb

  ConvertService.new('http://example.com/myfile.foo')

转换服务

class ConvertService
  def initialize(url)
    @url = url
    @bar_file = Tempfile.new
  end

  def call
    import_foo
    convert_foo
    upload_bar
  end

  private

  def import_foo
    @foo_file = ImportService.new(@url).call.edited_file
  end

  def convert_foo
    `create-bar "#{@foo_file.path}" "#{@bar_file.path}"`
  end

  def upload_bar
    UploadBarService.new(@bar_file).call
  end
end

导入服务

class ImportService
  def initialize(url)
    @url = url
    @edited_file ||= Tempfile.new
  end

  def call
    download
    duplicate
    replace
  end

  private

  def download
    @original = DownloadFileService.new(@url).call.file
  end

  def duplicate
    FileUtils.cp(@original.path, @edited_file.path)
  end

  def replace
    file = File.read(@edited_file.path)
    json = JSON.parse(file, symbolize_names: true)
    json[:buffers]&.each do |node| 
      node[:uri] = DownloadFileService.new(node[:uri]).call.file.path
    end
    write_to_disk(@edited_file.path, json.to_json)
  end
end

下载文件服务

module Helper
  class DownloadFileService < ApplicationHelperService
    def initialize(url)
      @url = url
      @file = Tempfile.new
    end

    def call
      uri = URI.parse(@url)
      Net::HTTP.start(
        uri.host, 
        uri.port, 
        use_ssl: uri.scheme == 'https'
      ) do |http|
        response = http.request(Net::HTTP::Get.new(uri.path))
        @file.binmode
        @file.write(response.body)
        @file.flush
      end
    end
  end
end

UploadBarService

module Helper
  class UploadBarService < ApplicationHelperService
    def initialize(file)
      @file = file
    end

    def call
      HTTParty.post('http://example.com/upload', body: { file: @file })
      # NOTE: End points returns the url for the uploaded file
    end
  end
end

【问题讨论】:

  • 请花点时间阅读minimal reproducible example。您实际上已将整个应用程序上传到此处,并希望有人对其进行解析并为您解决问题。通过自己做一些工作来大幅减少这种情况,您更有可能获得帮助。您提供的代码甚至无法由任何人运行,因为它缺少像 ApplicationHelperService 这样的部分,即使存在,它仍然无法使用,因为它取决于我们没有的其他数据。跨度>
  • 也许this discussion 会有所帮助?
  • @anothermh 代码的目标是让人们对结构有所了解。这实际上不可能提供开箱即用的东西,因为(如果你会查看消息)将需要设置上传服务器和其他超出 ruby​​ 范围的机制以及 TempFile 的工作方式......
  • @lacostenycoder 很棒的链接!它指向的链接说得更多(简而言之)。如果没有正在运行的代码指向 TempFiles,则它会被 GCed。 ImportService#replace 中的代码通过 DownloadFileService 获取 TempFiles 并使用它的路径,而不在内存中保存对它的引用。所以当这个实例被 GCed 时,对 TempFile 的引用(即使仍在其他 TempFiles 上使用)被破坏。为了避免这种情况,在ImportService 中添加一个类数组并将 TempFile 实例附加到它的全局应该就足够了,因为我的ImportService 将持续到ConvertService 结束

标签: ruby garbage-collection temporary-files


【解决方案1】:

由于您的代码的复杂性和可能对我们造成混淆的缺失部分,您的问题的简单答案是确保您的 tempfile 实例对象在需要它们的整个生命周期中都保留在内存中,否则它们会得到垃圾立即收集,从文件系统中删除临时文件,并将导致您遇到的丢失临时文件状态。

Ruby Document for Tempfile states“当一个 Tempfile 对象被垃圾回收,或者当 Ruby 解释器退出时,它相关的临时文件被自动删除。”

根据 cmets,其他人在遇到此问题时可能会发现 this conversation 很有帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-05-28
    • 1970-01-01
    • 2011-06-04
    • 1970-01-01
    • 1970-01-01
    • 2023-03-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多