[ruby/tempfile] Ensure finalizer order in Tempfile

The Closer and Remover finalizers are defined on different objects in
Tempfile. The Closer is defined on the Tempfile object while the Remover
is defined on the finalizer_obj. This means that there is no guarantee
of the finalizer order.

On Windows, we must close the file before removing it because we cannot
remove an open file. But since the order is not guaranteed, the GC may
run the Remover finalizer first, which will fail with an Errno::EACCES
(Permission denied @ apply2files).

This commit changes it so that both the Closer and Remover finalizers
are defined on the finalizer_obj, which guarantees the order that it is
ran.

https://github.com/ruby/tempfile/commit/eb2d8b1175
This commit is contained in:
Peter Zhu 2024-08-15 12:17:12 -04:00 committed by git
parent d6f18b226e
commit 41b427a264

View File

@ -228,22 +228,25 @@ class Tempfile < DelegateClass(File)
tmpfile = File.open(tmpname, @mode, **opts) tmpfile = File.open(tmpname, @mode, **opts)
@opts = opts.freeze @opts = opts.freeze
end end
ObjectSpace.define_finalizer(@finalizer_obj, Remover.new(tmpfile.path))
ObjectSpace.define_finalizer(self, Closer.new(tmpfile))
super(tmpfile) super(tmpfile)
define_finalizers
end
private def define_finalizers
ObjectSpace.define_finalizer(@finalizer_obj, Closer.new(__getobj__))
ObjectSpace.define_finalizer(@finalizer_obj, Remover.new(__getobj__.path))
end end
def initialize_dup(other) # :nodoc: def initialize_dup(other) # :nodoc:
initialize_copy_iv(other) initialize_copy_iv(other)
super(other) super(other)
ObjectSpace.define_finalizer(self, Closer.new(__getobj__))
end end
def initialize_clone(other) # :nodoc: def initialize_clone(other) # :nodoc:
initialize_copy_iv(other) initialize_copy_iv(other)
super(other) super(other)
ObjectSpace.define_finalizer(self, Closer.new(__getobj__))
end end
private def initialize_copy_iv(other) # :nodoc: private def initialize_copy_iv(other) # :nodoc:
@ -256,10 +259,13 @@ class Tempfile < DelegateClass(File)
# Opens or reopens the file with mode "r+". # Opens or reopens the file with mode "r+".
def open def open
_close _close
ObjectSpace.undefine_finalizer(self)
mode = @mode & ~(File::CREAT|File::EXCL) mode = @mode & ~(File::CREAT|File::EXCL)
__setobj__(File.open(__getobj__.path, mode, **@opts)) __setobj__(File.open(__getobj__.path, mode, **@opts))
ObjectSpace.define_finalizer(self, Closer.new(__getobj__))
ObjectSpace.undefine_finalizer(@finalizer_obj)
define_finalizers
__getobj__ __getobj__
end end
@ -327,7 +333,10 @@ class Tempfile < DelegateClass(File)
# may not be able to unlink on Windows; just ignore # may not be able to unlink on Windows; just ignore
return return
end end
ObjectSpace.undefine_finalizer(@finalizer_obj) ObjectSpace.undefine_finalizer(@finalizer_obj)
ObjectSpace.define_finalizer(@finalizer_obj, Closer.new(__getobj__))
@unlinked = true @unlinked = true
end end
alias delete unlink alias delete unlink