Rails中使用exifr时Tempfile被关闭的原因排查
examine(file.dup, load_thumbnails: load_thumbnails)
...
end
end
class Reader < SimpleDelegator
def readbyte; readchar; end unless File.method_defined?(:readbyte)
def readint; (readbyte << 8) + readbyte; end
def readframe; read(readint - 2); end
def readsof; [readint, readbyte, readint, readint, readbyte]; end
def next
c = readbyte while c != 0xFF
c = readbyte while c == 0xFF
c
end
end
def examine(io, load_thumbnails: true)
io = Reader.new(io)
...
由于#2方式无需将大文件(最大50MB)全部加载到内存,是更优方案,因此需要明确:exifr中哪部分逻辑导致了Tempfile被关闭? --- # 原因分析与解决方案 ### 核心原因:IO对象dup后的GC回收触发Tempfile清理 问题的根源在于exifr初始化时调用的`file.dup`操作,结合Ruby的GC机制和Rack Tempfile的特性共同导致: 1. 调用`Tempfile#dup`会生成一个指向同一临时文件的新IO对象(普通File实例),该对象与原Tempfile共享底层文件资源。 2. exifr处理完这个dup后的IO对象后,没有保留对它的引用,使其成为垃圾对象。 3. GC触发时,这个dup后的IO对象被回收,Ruby自动关闭其关联的文件描述符。 4. Rack提供的UploadedFile Tempfile存在自动清理机制:当所有指向该临时文件的文件描述符被关闭时,会自动关闭原Tempfile对象并删除临时文件,最终导致后续调用`f.size`触发文件不存在的错误。 ### 可行解决方案 针对这个问题,有几种无需放弃内存优势的修复方式: #### 方案1:手动保留dup对象的引用 在调用exifr前手动复制原Tempfile并保留引用,避免所有文件描述符被GC回收: ```rb f = uploaded_file.tempfile # 手动创建dup并保留引用,防止GC时所有文件描述符被关闭 temp_dup = f.dup EXIFR::JPEG.new(f) # 处理完成后可手动关闭副本 temp_dup.close
方案2:直接通过文件路径打开新的File对象
绕过原Tempfile的dup操作,直接基于文件路径创建新的File实例传递给exifr:
f = uploaded_file.tempfile EXIFR::JPEG.new(File.open(f.path))
方案3:处理后重新打开原Tempfile
如果需要继续使用原Tempfile对象,可在exifr处理完成后重新打开文件:
f = uploaded_file.tempfile EXIFR::JPEG.new(f) # 重新打开文件恢复可用状态 f.reopen(f.path)
内容的提问来源于stack exchange,提问作者Evgeniy Berezovsky
相关产品推荐
相关产品推荐

