Commit 588d5630 authored by Sean McGivern's avatar Sean McGivern

Remove GITLAB_TEMPFILE_IMMEDIATE_UNLINK env var

We have rolled this out in production and seen that it works: no
increase in error rate, and temporary files do not hang around any more.
parent 26be8f2c
...@@ -3,99 +3,97 @@ ...@@ -3,99 +3,97 @@
if Gitlab::Runtime.puma? if Gitlab::Runtime.puma?
raise "Remove this monkey patch: #{__FILE__}" unless Puma::Const::VERSION == '5.1.1' raise "Remove this monkey patch: #{__FILE__}" unless Puma::Const::VERSION == '5.1.1'
if ENV['GITLAB_TEMPFILE_IMMEDIATE_UNLINK'] == '1' # This is copied from https://github.com/puma/puma/blob/v5.1.1/lib/puma/client.rb,
# This is copied from https://github.com/puma/puma/blob/v5.1.1/lib/puma/client.rb, # with two additions: both times we create a temporary file, we immediately
# with two additions: both times we create a temporary file, we immediately # call `#unlink`. This means that if the process gets terminated without being
# call `#unlink`. This means that if the process gets terminated without being # able to clean up itself, the temporary file will not linger on the file
# able to clean up itself, the temporary file will not linger on the file # system. We will try to get this patch accepted upstream if it works for us
# system. We will try to get this patch accepted upstream if it works for us # (we just need to check if the temporary file responds to `#unlink` as that
# (we just need to check if the temporary file responds to `#unlink` as that # won't work on Windows, for instance).
# won't work on Windows, for instance). module Puma
module Puma class Client
class Client private
private
def setup_body
def setup_body @body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
if @env[HTTP_EXPECT] == CONTINUE
if @env[HTTP_EXPECT] == CONTINUE # TODO allow a hook here to check the headers before
# TODO allow a hook here to check the headers before # going forward
# going forward @io << HTTP_11_100
@io << HTTP_11_100 @io.flush
@io.flush end
end
@read_header = false @read_header = false
body = @parser.body body = @parser.body
te = @env[TRANSFER_ENCODING2] te = @env[TRANSFER_ENCODING2]
if te if te
if te.include?(",") if te.include?(",")
te.split(",").each do |part| te.split(",").each do |part|
if CHUNKED.casecmp(part.strip) == 0 # rubocop:disable Metrics/BlockNesting if CHUNKED.casecmp(part.strip) == 0 # rubocop:disable Metrics/BlockNesting
return setup_chunked_body(body) return setup_chunked_body(body)
end
end end
elsif CHUNKED.casecmp(te) == 0
return setup_chunked_body(body)
end end
elsif CHUNKED.casecmp(te) == 0
return setup_chunked_body(body)
end end
end
@chunked_body = false @chunked_body = false
cl = @env[CONTENT_LENGTH]
unless cl
@buffer = body.empty? ? nil : body
@body = EmptyBody
set_ready
return true
end
remain = cl.to_i - body.bytesize
if remain <= 0 cl = @env[CONTENT_LENGTH]
@body = StringIO.new(body)
@buffer = nil
set_ready
return true
end
if remain > MAX_BODY
@body = Tempfile.new(Const::PUMA_TMP_BASE)
@body.binmode
@body.unlink # This is the changed part
@tempfile = @body
else
# The body[0,0] trick is to get an empty string in the same
# encoding as body.
@body = StringIO.new body[0,0] # rubocop:disable Layout/SpaceAfterComma
end
@body.write body unless cl
@buffer = body.empty? ? nil : body
@body = EmptyBody
set_ready
return true
end
@body_remain = remain remain = cl.to_i - body.bytesize
return false # rubocop:disable Style/RedundantReturn if remain <= 0
@body = StringIO.new(body)
@buffer = nil
set_ready
return true
end end
def setup_chunked_body(body) if remain > MAX_BODY
@chunked_body = true
@partial_part_left = 0
@prev_chunk = ""
@body = Tempfile.new(Const::PUMA_TMP_BASE) @body = Tempfile.new(Const::PUMA_TMP_BASE)
@body.binmode @body.binmode
@body.unlink # This is the changed part @body.unlink # This is the changed part
@tempfile = @body @tempfile = @body
@chunked_content_length = 0 else
# The body[0,0] trick is to get an empty string in the same
# encoding as body.
@body = StringIO.new body[0,0] # rubocop:disable Layout/SpaceAfterComma
end
if decode_chunk(body) @body.write body
@env[CONTENT_LENGTH] = @chunked_content_length
return true # rubocop:disable Style/RedundantReturn @body_remain = remain
end
return false # rubocop:disable Style/RedundantReturn
end
def setup_chunked_body(body)
@chunked_body = true
@partial_part_left = 0
@prev_chunk = ""
@body = Tempfile.new(Const::PUMA_TMP_BASE)
@body.binmode
@body.unlink # This is the changed part
@tempfile = @body
@chunked_content_length = 0
if decode_chunk(body)
@env[CONTENT_LENGTH] = @chunked_content_length
return true # rubocop:disable Style/RedundantReturn
end end
end end
end end
......
...@@ -14,9 +14,7 @@ module Gitlab ...@@ -14,9 +14,7 @@ module Gitlab
end end
def call(env) def call(env)
if ENV['GITLAB_TEMPFILE_IMMEDIATE_UNLINK'] == '1' env[Rack::RACK_MULTIPART_TEMPFILE_FACTORY] = FACTORY
env[Rack::RACK_MULTIPART_TEMPFILE_FACTORY] = FACTORY
end
@app.call(env) @app.call(env)
end end
......
...@@ -42,44 +42,20 @@ RSpec.describe Gitlab::Middleware::RackMultipartTempfileFactory do ...@@ -42,44 +42,20 @@ RSpec.describe Gitlab::Middleware::RackMultipartTempfileFactory do
context 'for a multipart request' do context 'for a multipart request' do
let(:env) { Rack::MockRequest.env_for('/', multipart_fixture) } let(:env) { Rack::MockRequest.env_for('/', multipart_fixture) }
context 'when the environment variable is enabled' do it 'immediately unlinks the temporary file' do
before do tempfile = Tempfile.new('foo')
stub_env('GITLAB_TEMPFILE_IMMEDIATE_UNLINK', '1')
end
it 'immediately unlinks the temporary file' do
tempfile = Tempfile.new('foo')
expect(tempfile.path).not_to be(nil)
expect(Rack::Multipart::Parser::TEMPFILE_FACTORY).to receive(:call).and_return(tempfile)
expect(tempfile).to receive(:unlink).and_call_original
subject.call(env) expect(tempfile.path).not_to be(nil)
expect(Rack::Multipart::Parser::TEMPFILE_FACTORY).to receive(:call).and_return(tempfile)
expect(tempfile).to receive(:unlink).and_call_original
expect(tempfile.path).to be(nil) subject.call(env)
end
it 'processes the request as normal' do expect(tempfile.path).to be(nil)
expect(subject.call(env)).to eq([200, { 'Content-Type' => 'image/jpeg' }, [file_contents]])
end
end end
context 'when the environment variable is disabled' do it 'processes the request as normal' do
it 'does not immediately unlink the temporary file' do expect(subject.call(env)).to eq([200, { 'Content-Type' => 'image/jpeg' }, [file_contents]])
tempfile = Tempfile.new('foo')
expect(tempfile.path).not_to be(nil)
expect(Rack::Multipart::Parser::TEMPFILE_FACTORY).to receive(:call).and_return(tempfile)
expect(tempfile).not_to receive(:unlink).and_call_original
subject.call(env)
expect(tempfile.path).not_to be(nil)
end
it 'processes the request as normal' do
expect(subject.call(env)).to eq([200, { 'Content-Type' => 'image/jpeg' }, [file_contents]])
end
end end
end end
......
...@@ -1634,7 +1634,6 @@ RSpec.describe API::Projects do ...@@ -1634,7 +1634,6 @@ RSpec.describe API::Projects do
end end
it "does not leave the temporary file in place after uploading, even when the tempfile reaper does not run" do it "does not leave the temporary file in place after uploading, even when the tempfile reaper does not run" do
stub_env('GITLAB_TEMPFILE_IMMEDIATE_UNLINK', '1')
tempfile = Tempfile.new('foo') tempfile = Tempfile.new('foo')
path = tempfile.path path = tempfile.path
......
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