mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 13:39:04 +02:00
[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.
753ab16642
This commit is contained in:
parent
41b427a264
commit
a68331e703
2 changed files with 58 additions and 35 deletions
|
@ -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
|
||||
attr_reader :unlinked, :mode, :opts, :finalizer_manager
|
||||
|
||||
class Closer # :nodoc:
|
||||
def initialize(tmpfile)
|
||||
@tmpfile = tmpfile
|
||||
end
|
||||
|
||||
def call(*args)
|
||||
@tmpfile.close
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue