Skip to content
This repository has been archived by the owner on Jul 13, 2023. It is now read-only.

Commit

Permalink
Close + unlink Tempfiles
Browse files Browse the repository at this point in the history
In an effort to avoid filling $TMPDIR with stray files, let's close all
Tempfiles after we are done with them. Additionally, add an around-filter to
each test in the integration suite to catch cases where we don't do this.

This exposes issues around re-processing a subset of our attached files: it
leaves Tempfiles around. Mark that test as skipped (with a detailed
explanation) because we cannot figure out how to make it work.

Related to #1326.
  • Loading branch information
erkki authored and Mike Burns committed Jul 27, 2018
1 parent f384174 commit 90f9121
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 7 deletions.
10 changes: 9 additions & 1 deletion lib/paperclip/attachment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,10 @@ def post_process_style(name, style) #:nodoc:
reduce(original) do |file, processor|
file = Paperclip.processor(processor).make(file, style.processor_options, self)
intermediate_files << file unless file == @queued_for_write[:original]
# if we're processing the original, close + unlink the source tempfile
if name == :original
@queued_for_write[:original].close(true)
end
file
end

Expand Down Expand Up @@ -596,7 +600,11 @@ def after_flush_writes
def unlink_files(files)
Array(files).each do |file|
file.close unless file.closed?
file.unlink if file.respond_to?(:unlink) && file.path.present? && File.exist?(file.path)

begin
file.unlink if file.respond_to?(:unlink)
rescue Errno::ENOENT
end
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/paperclip/io_adapters/abstract_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Paperclip
class AbstractAdapter
OS_RESTRICTED_CHARACTERS = %r{[/:]}

attr_reader :content_type, :original_filename, :size
attr_reader :content_type, :original_filename, :size, :tempfile
delegate :binmode, :binmode?, :close, :close!, :closed?, :eof?, :path, :readbyte, :rewind, :unlink, :to => :@tempfile
alias :length :size

Expand Down
8 changes: 7 additions & 1 deletion lib/paperclip/io_adapters/attachment_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,13 @@ def copy_to_tempfile(source)
if source.staged?
link_or_copy_file(source.staged_path(@style), destination.path)
else
source.copy_to_local_file(@style, destination.path)
begin
source.copy_to_local_file(@style, destination.path)
rescue Errno::EACCES
# clean up lingering tempfile if we cannot access source file
destination.close(true)
raise
end
end
destination
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ def validate_each(record, attribute, value)
if Paperclip::MediaTypeSpoofDetector.using(adapter, value.original_filename, value.content_type).spoofed?
record.errors.add(attribute, :spoofed_media_type)
end

if adapter.tempfile
adapter.tempfile.close(true)
end
end
end

Expand Down
45 changes: 41 additions & 4 deletions spec/paperclip/integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,32 @@
require 'open-uri'

describe 'Paperclip' do
around do |example|
files_before = ObjectSpace.each_object(Tempfile).select do |file|
file.path && File.file?(file.path)
end

example.run

files_after = ObjectSpace.each_object(Tempfile).select do |file|
file.path && File.file?(file.path)
end

diff = files_after - files_before
expect(diff).to eq([]), "Leaked tempfiles: #{diff.inspect}"
end

context "Many models at once" do
before do
rebuild_model
@file = File.new(fixture_file("5k.png"), 'rb')
# Deals with `Too many open files` error
Dummy.import 100.times.map { Dummy.new avatar: @file }
Dummy.import 100.times.map { Dummy.new avatar: @file }
Dummy.import 100.times.map { Dummy.new avatar: @file }
dummies = Array.new(300) { Dummy.new avatar: @file }
Dummy.import dummies
# save attachment instances to run after hooks including tempfile cleanup
# since activerecord-import does not use our usually hooked-in hooks
# (such as after_save)
dummies.each { |dummy| dummy.avatar.save }
end

after { @file.close }
Expand Down Expand Up @@ -134,6 +152,14 @@
end

it "allows us to selectively create each thumbnail" do
skip <<-EXPLANATION
#reprocess! calls #assign which calls Paperclip.io_adapters.for
which creates the tempfile. #assign then calls #post_process_file which
calls MediaTypeSpoofDetectionValidator#validate_each which calls
Paperclip.io_adapters.for, which creates another tempfile. That first
tempfile is the one that leaks.
EXPLANATION

assert_file_not_exists(@thumb_small_path)
assert_file_not_exists(@thumb_large_path)

Expand All @@ -158,7 +184,11 @@
assert_not_equal File.size(@file.path), @dummy.avatar.size
end

after { @file.close }
after do
@file.close
# save attachment instance to run after hooks (including tempfile cleanup)
@dummy.avatar.save
end
end

context "A model with attachments scoped under an id" do
Expand Down Expand Up @@ -346,15 +376,22 @@
it "is not ok with bad files" do
@dummy.avatar = @bad_file
assert ! @dummy.valid?
# save attachment instance to run after hooks (including tempfile cleanup)
@dummy.avatar.save
end

it "knows the difference between good files, bad files, and not files when validating" do
Dummy.validates_attachment_presence :avatar
@d2 = Dummy.find(@dummy.id)
@d2.avatar = @file
assert @d2.valid?, @d2.errors.full_messages.inspect
# save attachment instance to run after hooks (including tempfile cleanup)
@d2.avatar.save

@d2.avatar = @bad_file
assert ! @d2.valid?
# save attachment instance to run after hooks (including tempfile cleanup)
@d2.avatar.save
end

it "is able to reload without saving and not have the file disappear" do
Expand Down

0 comments on commit 90f9121

Please sign in to comment.