Commit 90f9121a by Erkki Eilonen Committed by Mike Burns

Close + unlink Tempfiles

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.
parent f3841743
...@@ -537,6 +537,10 @@ module Paperclip ...@@ -537,6 +537,10 @@ module Paperclip
reduce(original) do |file, processor| reduce(original) do |file, processor|
file = Paperclip.processor(processor).make(file, style.processor_options, self) file = Paperclip.processor(processor).make(file, style.processor_options, self)
intermediate_files << file unless file == @queued_for_write[:original] 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 file
end end
...@@ -596,7 +600,11 @@ module Paperclip ...@@ -596,7 +600,11 @@ module Paperclip
def unlink_files(files) def unlink_files(files)
Array(files).each do |file| Array(files).each do |file|
file.close unless file.closed? 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
end end
......
...@@ -4,7 +4,7 @@ module Paperclip ...@@ -4,7 +4,7 @@ module Paperclip
class AbstractAdapter class AbstractAdapter
OS_RESTRICTED_CHARACTERS = %r{[/:]} 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 delegate :binmode, :binmode?, :close, :close!, :closed?, :eof?, :path, :readbyte, :rewind, :unlink, :to => :@tempfile
alias :length :size alias :length :size
......
...@@ -31,7 +31,13 @@ module Paperclip ...@@ -31,7 +31,13 @@ module Paperclip
if source.staged? if source.staged?
link_or_copy_file(source.staged_path(@style), destination.path) link_or_copy_file(source.staged_path(@style), destination.path)
else else
begin
source.copy_to_local_file(@style, destination.path) 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 end
destination destination
end end
......
...@@ -8,6 +8,10 @@ module Paperclip ...@@ -8,6 +8,10 @@ module Paperclip
if Paperclip::MediaTypeSpoofDetector.using(adapter, value.original_filename, value.content_type).spoofed? if Paperclip::MediaTypeSpoofDetector.using(adapter, value.original_filename, value.content_type).spoofed?
record.errors.add(attribute, :spoofed_media_type) record.errors.add(attribute, :spoofed_media_type)
end end
if adapter.tempfile
adapter.tempfile.close(true)
end
end end
end end
......
...@@ -2,14 +2,32 @@ require 'spec_helper' ...@@ -2,14 +2,32 @@ require 'spec_helper'
require 'open-uri' require 'open-uri'
describe 'Paperclip' do 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 context "Many models at once" do
before do before do
rebuild_model rebuild_model
@file = File.new(fixture_file("5k.png"), 'rb') @file = File.new(fixture_file("5k.png"), 'rb')
# Deals with `Too many open files` error # Deals with `Too many open files` error
Dummy.import 100.times.map { Dummy.new avatar: @file } dummies = Array.new(300) { Dummy.new avatar: @file }
Dummy.import 100.times.map { Dummy.new avatar: @file } Dummy.import dummies
Dummy.import 100.times.map { Dummy.new avatar: @file } # 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 end
after { @file.close } after { @file.close }
...@@ -134,6 +152,14 @@ describe 'Paperclip' do ...@@ -134,6 +152,14 @@ describe 'Paperclip' do
end end
it "allows us to selectively create each thumbnail" do 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_small_path)
assert_file_not_exists(@thumb_large_path) assert_file_not_exists(@thumb_large_path)
...@@ -158,7 +184,11 @@ describe 'Paperclip' do ...@@ -158,7 +184,11 @@ describe 'Paperclip' do
assert_not_equal File.size(@file.path), @dummy.avatar.size assert_not_equal File.size(@file.path), @dummy.avatar.size
end end
after { @file.close } after do
@file.close
# save attachment instance to run after hooks (including tempfile cleanup)
@dummy.avatar.save
end
end end
context "A model with attachments scoped under an id" do context "A model with attachments scoped under an id" do
...@@ -346,6 +376,8 @@ describe 'Paperclip' do ...@@ -346,6 +376,8 @@ describe 'Paperclip' do
it "is not ok with bad files" do it "is not ok with bad files" do
@dummy.avatar = @bad_file @dummy.avatar = @bad_file
assert ! @dummy.valid? assert ! @dummy.valid?
# save attachment instance to run after hooks (including tempfile cleanup)
@dummy.avatar.save
end end
it "knows the difference between good files, bad files, and not files when validating" do it "knows the difference between good files, bad files, and not files when validating" do
...@@ -353,8 +385,13 @@ describe 'Paperclip' do ...@@ -353,8 +385,13 @@ describe 'Paperclip' do
@d2 = Dummy.find(@dummy.id) @d2 = Dummy.find(@dummy.id)
@d2.avatar = @file @d2.avatar = @file
assert @d2.valid?, @d2.errors.full_messages.inspect 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 @d2.avatar = @bad_file
assert ! @d2.valid? assert ! @d2.valid?
# save attachment instance to run after hooks (including tempfile cleanup)
@d2.avatar.save
end end
it "is able to reload without saving and not have the file disappear" do it "is able to reload without saving and not have the file disappear" do
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment