Bug #19441


Closing an Tempfile#dup behaviour

Added by stac47 (Laurent Stacul) over 1 year ago. Updated 8 months ago.

Target version:
ruby -v:
ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-darwin20]


Hello amazing ruby folks!

I classified that ticket as a "Bug" although it is perhaps something I did not understand.

Here is a ruby session:

irb(main):001:0> file =
=> #<File:/var/folders/m2/bljzrgq160vbf0vk466k_7gw0000gn/T/20230216-39664-davfmj>
irb(main):002:0> file.fileno
=> 9
irb(main):003:0> dup = file.dup
=> #<File:/var/folders/m2/bljzrgq160vbf0vk466k_7gw0000gn/T/20230216-39664-davfmj>
irb(main):004:0> dup.fileno
=> 10
irb(main):005:0> dup.close
=> nil
irb(main):006:0> dup.closed?
=> false
irb(main):007:0> file.closed?
=> true

The two last lines are unexpected to me. I would have expected the converse:

irb(main):006:0> dup.closed?
=> true
irb(main):007:0> file.closed?
=> false

I tried this scenario in latest ruby but also in ruby 2.7.

Thanks in advance for helping me to understand this behaviour.

Updated by nobu (Nobuyoshi Nakada) over 1 year ago

What do you expect for the dupped Tempfile object?
An IO to the same file?
Or another temporary file?

Updated by stac47 (Laurent Stacul) over 1 year ago

Provided #dup generally returns an object of the same type, I would expect to have a Tempfile object.

The unexpected thing was that if I replay the same scenario with File, I have what I expect. I was just wondering whether the behaviour discrepancy between File and Tempfile was normal.

irb(main):001:0> file ="a_file.txt", "w+")
=> #<File:a_file.txt>
irb(main):002:0> file.size
=> 0
irb(main):003:0> dup = file.dup
=> #<File:a_file.txt>
irb(main):004:0> dup.closed?
=> false
irb(main):005:0> file.closed?
=> false
irb(main):006:0> dup.close
=> nil
irb(main):007:0> dup.closed?
=> true
irb(main):008:0> file.closed?
=> false

Updated by nobu (Nobuyoshi Nakada) over 1 year ago

A Tempfile uses a finalizer to clean up the target file.
That is the file will be removed when a dup of Tempfile is discarded (and also will be tried to remove again).
I don't think it is a good idea to use Tempfile#dup.
What do you want to achieve by using this method?

Actions #4

Updated by byroot (Jean Boussier) over 1 year ago

  • Subject changed from Closing an IO#dup behaviour to Closing an Tempfile#dup behaviour

Updated by stac47 (Laurent Stacul) over 1 year ago

Thanks nobu for your answer it makes perfect sense.

My case which led to the current question comes from a Rails application which has a controller receiving a file. My input params[:file] hold an ActionDispatch::Http::UploadedFile which itself has an attribute tempfile.

In the controller, params[:file] is passed to some functions (I don't have the control of) that can read and close the underlying tempfile. As a matter a fact, the code calls ActionDispatch::Http::UploadedFile#open (shortcut to tempfile#open) after the calls that close the file.

I was wondering whether I could avoid calling #open by providing the methods which can close the file with a duped file descriptor so that the original ActionDispatch::Http::UploadedFile is left untouched until I really use it in my own code.

The scenario in itself is not really interesting, it is just I was surprised by the behaviour when I used Tempfile#dup but your explanation is acceptable. Thanks for this.

Updated by jeremyevans0 (Jeremy Evans) about 1 year ago

I think this is a bug in Tempfile#{dup,clone} that we should fix. I submitted a pull request for it:

Actions #7

Updated by jeremyevans (Jeremy Evans) 8 months ago

  • Status changed from Open to Closed

Applied in changeset git|ddcfc9feabf22ed6cc1071e65948a1d512a906fe.

[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]


Also available in: Atom PDF