不同类之间的Ruby TempFile行为



我们的处理服务器主要与TempFiles一起工作,因为它让我们这边的事情变得更容易:不需要在垃圾收集或处理名称冲突时删除它们。

最近,我们遇到了TempFiles在这个过程中过早获得GCed的问题。特别是我们的一项服务,它将把Foo文件从url转换为某个Bar文件并上传到我们的服务器。

为了清晰起见,我在下面添加了一个案例场景,以便更容易进行讨论,并手头有一个示例。

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

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

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

我的两个问题:

  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" }
]
}

main.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

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

Tempfile的Ruby文档声明";当Tempfile对象被垃圾回收时,或者当Ruby解释器退出时,其关联的临时文件会自动删除">

根据评论,当遇到这个问题时,其他人可能会觉得这个对话很有帮助。

最新更新