[ruby/tempfile] Fix Tempfile#{dup,clone}
Instead of storing the delegate in @tmpfile, use __getobj__, since delegate library already handles dup/clone for that. Copy the unlinked, mode, and opts instance variables to the returned object when using dup/clone. Split the close/unlink finalizer into two finalizers. The close finalizer always closes when any Tempfile instance is GCed, since each Tempfile instance uses a separate file descriptor. The unlink finalizer unlinks only when the original and all duped/cloned Tempfiles are GCed, since all share the same path. For Tempfile#open, undefine the close finalizer after closing the current file, the redefine the close finalizer with the new file. Fixes [Bug #19441] https://github.com/ruby/tempfile/commit/dafabf9c7b
This commit is contained in:
parent
d80009d169
commit
ddcfc9feab
@ -152,26 +152,49 @@ class Tempfile < DelegateClass(File)
|
|||||||
|
|
||||||
@unlinked = false
|
@unlinked = false
|
||||||
@mode = mode|File::RDWR|File::CREAT|File::EXCL
|
@mode = mode|File::RDWR|File::CREAT|File::EXCL
|
||||||
|
@finalizer_obj = Object.new
|
||||||
|
tmpfile = nil
|
||||||
::Dir::Tmpname.create(basename, tmpdir, **options) do |tmpname, n, opts|
|
::Dir::Tmpname.create(basename, tmpdir, **options) do |tmpname, n, opts|
|
||||||
opts[:perm] = 0600
|
opts[:perm] = 0600
|
||||||
@tmpfile = File.open(tmpname, @mode, **opts)
|
tmpfile = File.open(tmpname, @mode, **opts)
|
||||||
@opts = opts.freeze
|
@opts = opts.freeze
|
||||||
end
|
end
|
||||||
ObjectSpace.define_finalizer(self, Remover.new(@tmpfile))
|
ObjectSpace.define_finalizer(@finalizer_obj, Remover.new(tmpfile.path))
|
||||||
|
ObjectSpace.define_finalizer(self, Closer.new(tmpfile))
|
||||||
|
|
||||||
super(@tmpfile)
|
super(tmpfile)
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize_dup(other)
|
||||||
|
initialize_copy_iv(other)
|
||||||
|
super(other)
|
||||||
|
ObjectSpace.define_finalizer(self, Closer.new(__getobj__))
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize_clone(other)
|
||||||
|
initialize_copy_iv(other)
|
||||||
|
super(other)
|
||||||
|
ObjectSpace.define_finalizer(self, Closer.new(__getobj__))
|
||||||
|
end
|
||||||
|
|
||||||
|
private def initialize_copy_iv(other)
|
||||||
|
@unlinked = other.unlinked
|
||||||
|
@mode = other.mode
|
||||||
|
@opts = other.opts
|
||||||
|
@finalizer_obj = other.finalizer_obj
|
||||||
end
|
end
|
||||||
|
|
||||||
# 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)
|
||||||
@tmpfile = File.open(@tmpfile.path, mode, **@opts)
|
__setobj__(File.open(__getobj__.path, mode, **@opts))
|
||||||
__setobj__(@tmpfile)
|
ObjectSpace.define_finalizer(self, Closer.new(__getobj__))
|
||||||
end
|
end
|
||||||
|
|
||||||
def _close # :nodoc:
|
def _close # :nodoc:
|
||||||
@tmpfile.close
|
__getobj__.close
|
||||||
end
|
end
|
||||||
protected :_close
|
protected :_close
|
||||||
|
|
||||||
@ -228,13 +251,13 @@ class Tempfile < DelegateClass(File)
|
|||||||
def unlink
|
def unlink
|
||||||
return if @unlinked
|
return if @unlinked
|
||||||
begin
|
begin
|
||||||
File.unlink(@tmpfile.path)
|
File.unlink(__getobj__.path)
|
||||||
rescue Errno::ENOENT
|
rescue Errno::ENOENT
|
||||||
rescue Errno::EACCES
|
rescue Errno::EACCES
|
||||||
# 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(self)
|
ObjectSpace.undefine_finalizer(@finalizer_obj)
|
||||||
@unlinked = true
|
@unlinked = true
|
||||||
end
|
end
|
||||||
alias delete unlink
|
alias delete unlink
|
||||||
@ -242,43 +265,56 @@ class Tempfile < DelegateClass(File)
|
|||||||
# Returns the full path name of the temporary file.
|
# Returns the full path name of the temporary file.
|
||||||
# This will be nil if #unlink has been called.
|
# This will be nil if #unlink has been called.
|
||||||
def path
|
def path
|
||||||
@unlinked ? nil : @tmpfile.path
|
@unlinked ? nil : __getobj__.path
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the size of the temporary file. As a side effect, the IO
|
# Returns the size of the temporary file. As a side effect, the IO
|
||||||
# buffer is flushed before determining the size.
|
# buffer is flushed before determining the size.
|
||||||
def size
|
def size
|
||||||
if !@tmpfile.closed?
|
if !__getobj__.closed?
|
||||||
@tmpfile.size # File#size calls rb_io_flush_raw()
|
__getobj__.size # File#size calls rb_io_flush_raw()
|
||||||
else
|
else
|
||||||
File.size(@tmpfile.path)
|
File.size(__getobj__.path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
alias length size
|
alias length size
|
||||||
|
|
||||||
# :stopdoc:
|
# :stopdoc:
|
||||||
def inspect
|
def inspect
|
||||||
if @tmpfile.closed?
|
if __getobj__.closed?
|
||||||
"#<#{self.class}:#{path} (closed)>"
|
"#<#{self.class}:#{path} (closed)>"
|
||||||
else
|
else
|
||||||
"#<#{self.class}:#{path}>"
|
"#<#{self.class}:#{path}>"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Remover # :nodoc:
|
protected
|
||||||
|
|
||||||
|
attr_reader :unlinked, :mode, :opts, :finalizer_obj
|
||||||
|
|
||||||
|
class Closer # :nodoc:
|
||||||
def initialize(tmpfile)
|
def initialize(tmpfile)
|
||||||
@pid = Process.pid
|
|
||||||
@tmpfile = tmpfile
|
@tmpfile = tmpfile
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def call(*args)
|
||||||
|
@tmpfile.close
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Remover # :nodoc:
|
||||||
|
def initialize(path)
|
||||||
|
@pid = Process.pid
|
||||||
|
@path = path
|
||||||
|
end
|
||||||
|
|
||||||
def call(*args)
|
def call(*args)
|
||||||
return if @pid != Process.pid
|
return if @pid != Process.pid
|
||||||
|
|
||||||
$stderr.puts "removing #{@tmpfile.path}..." if $DEBUG
|
$stderr.puts "removing #{@path}..." if $DEBUG
|
||||||
|
|
||||||
@tmpfile.close
|
|
||||||
begin
|
begin
|
||||||
File.unlink(@tmpfile.path)
|
File.unlink(@path)
|
||||||
rescue Errno::ENOENT
|
rescue Errno::ENOENT
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -63,6 +63,22 @@ class TestTempfile < Test::Unit::TestCase
|
|||||||
assert_match(/\.txt$/, File.basename(t.path))
|
assert_match(/\.txt$/, File.basename(t.path))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_dup
|
||||||
|
t = tempfile
|
||||||
|
t2 = t.dup
|
||||||
|
t2.close
|
||||||
|
assert_equal true, t2.closed?
|
||||||
|
assert_equal false, t.closed?
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_clone
|
||||||
|
t = tempfile
|
||||||
|
t2 = t.clone
|
||||||
|
t2.close
|
||||||
|
assert_equal true, t2.closed?
|
||||||
|
assert_equal false, t.closed?
|
||||||
|
end
|
||||||
|
|
||||||
def test_unlink
|
def test_unlink
|
||||||
t = tempfile("foo")
|
t = tempfile("foo")
|
||||||
path = t.path
|
path = t.path
|
||||||
|
Loading…
x
Reference in New Issue
Block a user