summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Zhu <[email protected]>2024-08-20 13:28:11 -0400
committergit <[email protected]>2024-08-20 18:07:42 +0000
commita68331e7036d7ab433778bf65eb854aabd5009c4 (patch)
treee64a0d781c4a332631a49f9fa6066e088b1c92a3
parent41b427a2648ed2e049952450c698be917e0bb125 (diff)
[ruby/tempfile] Add FinalizerManager to manage finalizers
As @jeremyevans pointed out for commit eb2d8b1: > Each Tempfile instance has a separate File instance and file descriptor: > > t = Tempfile.new > t.to_i # => 6 > t.dup.to_i => 7 FinalizerManager will keep track of the open File objects for the particular file and will only unlink the file when all of the File objects have been closed. https://github.com/ruby/tempfile/commit/753ab16642
-rw-r--r--lib/tempfile.rb58
-rw-r--r--test/test_tempfile.rb35
2 files changed, 58 insertions, 35 deletions
diff --git a/lib/tempfile.rb b/lib/tempfile.rb
index 52a8d6cc9d..98706f7156 100644
--- a/lib/tempfile.rb
+++ b/lib/tempfile.rb
@@ -221,7 +221,6 @@ class Tempfile < DelegateClass(File)
@unlinked = false
@mode = mode|File::RDWR|File::CREAT|File::EXCL
- @finalizer_obj = Object.new
tmpfile = nil
::Dir::Tmpname.create(basename, tmpdir, **options) do |tmpname, n, opts|
opts[:perm] = 0600
@@ -231,29 +230,27 @@ class Tempfile < DelegateClass(File)
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))
+ @finalizer_manager = FinalizerManager.new(__getobj__.path)
+ @finalizer_manager.register(self, __getobj__)
end
def initialize_dup(other) # :nodoc:
initialize_copy_iv(other)
super(other)
+ @finalizer_manager.register(self, __getobj__)
end
def initialize_clone(other) # :nodoc:
initialize_copy_iv(other)
super(other)
+ @finalizer_manager.register(self, __getobj__)
end
private def initialize_copy_iv(other) # :nodoc:
@unlinked = other.unlinked
@mode = other.mode
@opts = other.opts
- @finalizer_obj = other.finalizer_obj
+ @finalizer_manager = other.finalizer_manager
end
# Opens or reopens the file with mode "r+".
@@ -263,8 +260,7 @@ class Tempfile < DelegateClass(File)
mode = @mode & ~(File::CREAT|File::EXCL)
__setobj__(File.open(__getobj__.path, mode, **@opts))
- ObjectSpace.undefine_finalizer(@finalizer_obj)
- define_finalizers
+ @finalizer_manager.register(self, __getobj__)
__getobj__
end
@@ -334,9 +330,6 @@ class Tempfile < DelegateClass(File)
return
end
- ObjectSpace.undefine_finalizer(@finalizer_obj)
- ObjectSpace.define_finalizer(@finalizer_obj, Closer.new(__getobj__))
-
@unlinked = true
end
alias delete unlink
@@ -370,35 +363,32 @@ class Tempfile < DelegateClass(File)
protected
- attr_reader :unlinked, :mode, :opts, :finalizer_obj
-
- class Closer # :nodoc:
- def initialize(tmpfile)
- @tmpfile = tmpfile
- end
-
- def call(*args)
- @tmpfile.close
- end
- end
+ attr_reader :unlinked, :mode, :opts, :finalizer_manager
- class Remover # :nodoc:
+ class FinalizerManager # :nodoc:
def initialize(path)
- @pid = Process.pid
+ @open_files = {}
@path = path
+ @pid = Process.pid
end
- def call(*args)
- return if @pid != Process.pid
+ def register(obj, file)
+ ObjectSpace.undefine_finalizer(obj)
+ ObjectSpace.define_finalizer(obj, self)
+ @open_files[obj.object_id] = file
+ end
- $stderr.puts "removing #{@path}..." if $DEBUG
+ def call(object_id)
+ @open_files.delete(object_id).close
- begin
- File.unlink(@path)
- rescue Errno::ENOENT
+ if @open_files.empty? && Process.pid == @pid
+ $stderr.puts "removing #{@path}..." if $DEBUG
+ begin
+ File.unlink(@path)
+ rescue Errno::ENOENT
+ end
+ $stderr.puts "done" if $DEBUG
end
-
- $stderr.puts "done" if $DEBUG
end
end
diff --git a/test/test_tempfile.rb b/test/test_tempfile.rb
index ccebbf3c1a..1e40e4e480 100644
--- a/test/test_tempfile.rb
+++ b/test/test_tempfile.rb
@@ -3,6 +3,8 @@ require 'test/unit'
require 'tempfile'
class TestTempfile < Test::Unit::TestCase
+ LIB_TEMPFILE_RB_PATH = File.expand_path(__dir__ + "/../lib/tempfile.rb")
+
def initialize(*)
super
@tempfile = nil
@@ -172,6 +174,38 @@ class TestTempfile < Test::Unit::TestCase
end
end unless /mswin|mingw/ =~ RUBY_PLATFORM
+ def test_finalizer_removes_file
+ assert_in_out_err(["-r#{LIB_TEMPFILE_RB_PATH}"], <<~RUBY) do |(filename,*), (error,*)|
+ file = Tempfile.new("foo")
+ puts file.path
+ RUBY
+ assert_file.not_exist?(filename)
+ assert_nil error
+ end
+ end
+
+ def test_finalizer_removes_file_when_dup
+ assert_in_out_err(["-r#{LIB_TEMPFILE_RB_PATH}"], <<~RUBY) do |(filename,*), (error,*)|
+ file = Tempfile.new("foo")
+ file.dup
+ puts file.path
+ RUBY
+ assert_file.not_exist?(filename)
+ assert_nil error
+ end
+ end
+
+ def test_finalizer_removes_file_when_clone
+ assert_in_out_err(["-r#{LIB_TEMPFILE_RB_PATH}"], <<~RUBY) do |(filename,*), (error,*)|
+ file = Tempfile.new("foo")
+ file.clone
+ puts file.path
+ RUBY
+ assert_file.not_exist?(filename)
+ assert_nil error
+ end
+ end
+
def test_finalizer_does_not_unlink_if_already_unlinked
assert_in_out_err('-rtempfile', <<-'EOS') do |(filename,*), (error,*)|
file = Tempfile.new('foo')
@@ -474,5 +508,4 @@ puts Tempfile.new('foo').path
assert_equal(true, t.autoclose?)
}
end
-
end