exifr 中的什么导致此临时文件被关闭?

问题描述 投票:0回答:1

在这段使用

exifr
处理 UploadedFile

的 Ruby 代码中
f = uploaded_file.tempfile
p "1 #{f.closed?} #{f.instance_variable_get(:'@unlinked')}"
#1 EXIFR::JPEG.new(StringIO.new(f.read))
#2 EXIFR::JPEG.new(f)
p "2 #{f.closed?} #{f.instance_variable_get(:'@unlinked')}"
GC.start
sleep 0.01
p "3 #{f.closed?} #{f.instance_variable_get(:'@unlinked')}"
p "4 #{f.size}"

注意GC.start/sleep 是为了使问题可靠地复制。

取消注释时

#1
,一切都很好:

"1 false false"
"2 false false"
"3 false false"
"4 3822528"

但是,取消注释

#2
(而不是
#1
)的结果是:

"1 false false"
"2 false false"
"3 true false"
[c4b7ce6b-5492-43db-8c64-726cafaccce0] [Thread: 24800] Errno::ENOENT (No such file or directory @ rb_file_s_size - /var/folders/vx/v0rn818s0257_3l491_v48bm0000gn/T/RackMultipart20240221-71765-acbi7v.JPG):

现在 exifr 所做的就是这样:

    def initialize(file, load_thumbnails: true)
...
        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)
...

还有一些来自

io
的阅读,所以我不明白什么会导致文件被关闭。

这发生在 puma 上运行的 Rails 应用程序中。

#2 更好,因为它不需要将文件完全加载到内存中(在我的例子中,我们谈论的是最多 50 MB)。

ruby-on-rails ruby puma
1个回答
0
投票

发生这种情况的原因很可能是因为

Tempfile
文档中的解释:

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

现在,当

EXIFR
调用
file.dup
时,它会在文件对象上执行
examine
。一旦
EXIFR
读取完文件,这个重复的对象将被垃圾收集,并且作为此垃圾收集的副作用,临时文件将被删除。

我们将此问题称为

Tempfile
竞争条件。
Tempfile
很可能不是“dup-safe”,并且
EXIFR
可能被编程为期望
File
对象,在这种情况下,这个问题不会发生。

因此,如果您想保留临时文件以供以后处理,解决方案是不要使用

EXIFR::JPEG.new(f)

另一种解决方案是使用

File
对象自行打开临时文件,然后将该对象传递给
EXIFR::JPEG.new

这样,您就不必将文件读入内存,只要您在某处维护对

f
的引用,垃圾收集也不会删除临时文件。

© www.soinside.com 2019 - 2024. All rights reserved.