Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TempfileReaper won't clean tmp file when request goes to 500 error. #1679

Closed
marvin-min opened this issue Jun 30, 2020 · 6 comments · Fixed by #1685
Closed

TempfileReaper won't clean tmp file when request goes to 500 error. #1679

marvin-min opened this issue Jun 30, 2020 · 6 comments · Fixed by #1685

Comments

@marvin-min
Copy link

We are using TempfileReaper to clear the /tmp/RackMultipart* files. For the normal requests, it works good.
But for some requests which goes to 500 error, the files can not be removed.

and we made some changes to make it work.

def call(env)
    env['rack.tempfiles'] ||= []
    status, headers, body = @app.call(env)
    body_proxy = BodyProxy.new(body) do
      env['rack.tempfiles'].each { |f| f.close! } unless env['rack.tempfiles'].nil?
    end
    [status, headers, body_proxy]
# Added the following 2 lines 
  ensure
    env['rack.tempfiles'].each { |f| f.close! } unless env['rack.tempfiles'].nil?
  end

With this changes, the files can be removed correctly.

#1118

@jeremyevans
Copy link
Contributor

I agree that the current behavior is problematic. #1118 was wrongly closed, because the webserver can only close the body if there is a body, it cannot close the body if an exception is raised instead. The discussion in #1118 was assuming the error was raised during iteration of the body, not during call.

In regards to the proposed patch, that closes the temp files before the body is closed, which violates the spec. Probably best to use begin/rescue around @app.call, and close tempfiles before reraising in the rescue block.

@ioquatix
Copy link
Member

ioquatix commented Jul 1, 2020

I'm not sure if this is a better approach, but one can allocate a file on disk, and then unlink it. Provided the file handle is kept open, it can be used for reading and writing. When the file descriptor is closed, it will be cleaned up by the OS. There are still edge cases where the TempfileReaper is insufficient (e.g. sigkill).

@marvin-min
Copy link
Author

@jeremyevans & @ioquatix Many thanks for your comments, which very helpful for us.

@marvin-min
Copy link
Author

marvin-min commented Jul 2, 2020

@jeremyevans @ioquatix Can I disable the file upload related functions? Currently, we can remove the temp file from disk. but I think this still is a risk, and in our project, we do not have file upload related functions.

Thanks,

@jeremyevans
Copy link
Contributor

@marvin-min You can set the 'rack.multipart.tempfile_factory' env key to a proc that raises. That way if someone attempts to upload a file, they will get an error and no temporary file will be created.

jeremyevans added a commit to jeremyevans/rack that referenced this issue Jul 13, 2020
Previously, tempfiles were only deleted if the exception occurred
when iterating over the body, not when raised during app.call.

Fixes rack#1679.
jeremyevans added a commit to jeremyevans/rack that referenced this issue Jul 13, 2020
Previously, tempfiles were only deleted if the exception occurred
when iterating over the body, not when raised during app.call.

Fixes rack#1679.
jeremyevans added a commit to jeremyevans/rack that referenced this issue Jul 13, 2020
Previously, tempfiles were only deleted if the exception occurred
when iterating over the body, not when raised during app.call.

Fixes rack#1679.
jeremyevans added a commit that referenced this issue Jul 13, 2020
Previously, tempfiles were only deleted if the exception occurred
when iterating over the body, not when raised during app.call.

Fixes #1679.
@smcgivern
Copy link
Contributor

I'm not sure if this is a better approach, but one can allocate a file on disk, and then unlink it. Provided the file handle is kept open, it can be used for reading and writing. When the file descriptor is closed, it will be cleaned up by the OS. There are still edge cases where the TempfileReaper is insufficient (e.g. sigkill).

I know this is an old (and closed) issue, but for what it's worth, @ioquatix's suggestion is explicitly mentioned in Ruby's Tempfile documentation: https://ruby-doc.org/stdlib-2.7.2/libdoc/tempfile/rdoc/Tempfile.html#class-Tempfile-label-Unlink+after+creation

On POSIX systems, it’s possible to unlink a file right after creating it, and before closing it. This removes the filesystem entry without closing the file handle, so it ensures that only the processes that already had the file handle open can access the file’s contents. It’s strongly recommended that you do this if you do not want any other processes to be able to read from or write to the Tempfile, and you do not need to know the Tempfile’s filename either.

For example, a practical use case for unlink-after-creation would be this: you need a large byte buffer that’s too large to comfortably fit in RAM, e.g. when you’re writing a web server and you want to buffer the client’s file upload data.

Thankfully, as Rack makes this pluggable, we were able to do this ourselves: https://gitlab.com/gitlab-org/gitlab/-/commit/9da1771de45e213eeb116cf9fd41434a75efbd2d

I'm happy to submit a PR to change the default behaviour of Rack::Multipart::Parser::TEMPFILE_FACTORY if people prefer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants