From 97e469fc3afb751618b8b9a7b364cb447aaf90dd Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 19 Jul 2009 16:38:16 -0700 Subject: Remove core Tempfile dependency (1.9.2-preview1 compat) With the 1.9.2preview1 release (and presumably 1.9.1 p243), the Ruby core team has decided that bending over backwards to support crippled operating/file systems was necessary and that files must be closed before unlinking. Regardless, this is more efficient than using Tempfile because: 1) no delegation is necessary, this is a real File object 2) no mkdir is necessary for locking, we can trust O_EXCL to work properly without unnecessary FS activity 3) no finalizer is needed to unlink the file, we unlink it as soon as possible after creation. --- lib/unicorn.rb | 23 ++++++++++------------- lib/unicorn/app/exec_cgi.rb | 10 ++-------- lib/unicorn/tee_input.rb | 9 ++------- lib/unicorn/util.rb | 17 +++++++++++++++++ 4 files changed, 31 insertions(+), 28 deletions(-) diff --git a/lib/unicorn.rb b/lib/unicorn.rb index eb11f4d..556aba8 100644 --- a/lib/unicorn.rb +++ b/lib/unicorn.rb @@ -1,5 +1,4 @@ require 'fcntl' -require 'tempfile' require 'unicorn/socket_helper' autoload :Rack, 'rack' @@ -66,7 +65,7 @@ module Unicorn 0 => $0.dup, } - class Worker < Struct.new(:nr, :tempfile) + class Worker < Struct.new(:nr, :tmp) # worker objects may be compared to just plain numbers def ==(other_nr) self.nr == other_nr @@ -332,7 +331,7 @@ module Unicorn self.pid = pid.chomp('.oldbin') if pid proc_name 'master' else - worker = WORKERS.delete(wpid) and worker.tempfile.close rescue nil + worker = WORKERS.delete(wpid) and worker.tmp.close rescue nil logger.info "reaped #{status.inspect} " \ "worker=#{worker.nr rescue 'unknown'}" end @@ -392,16 +391,16 @@ module Unicorn end # forcibly terminate all workers that haven't checked in in timeout - # seconds. The timeout is implemented using an unlinked tempfile + # seconds. The timeout is implemented using an unlinked File # shared between the parent process and each worker. The worker - # runs File#chmod to modify the ctime of the tempfile. If the ctime + # runs File#chmod to modify the ctime of the File. If the ctime # is stale for >timeout seconds, then we'll kill the corresponding # worker. def murder_lazy_workers diff = stat = nil WORKERS.dup.each_pair do |wpid, worker| stat = begin - worker.tempfile.stat + worker.tmp.stat rescue => e logger.warn "worker=#{worker.nr} PID:#{wpid} stat error: #{e.inspect}" kill_worker(:QUIT, wpid) @@ -425,9 +424,7 @@ module Unicorn SIG_QUEUE << :QUIT # forcibly emulate SIGQUIT return end - tempfile = Tempfile.new(nil) # as short as possible to save dir space - tempfile.unlink # don't allow other processes to find or see it - worker = Worker.new(worker_nr, tempfile) + worker = Worker.new(worker_nr, Unicorn::Util.tmpio) before_fork.call(self, worker) WORKERS[fork { worker_loop(worker) }] = worker end @@ -482,10 +479,10 @@ module Unicorn proc_name "worker[#{worker.nr}]" START_CTX.clear init_self_pipe! - WORKERS.values.each { |other| other.tempfile.close! rescue nil } + WORKERS.values.each { |other| other.tmp.close rescue nil } WORKERS.clear LISTENERS.each { |sock| sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) } - worker.tempfile.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) + worker.tmp.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) after_fork.call(self, worker) # can drop perms self.timeout /= 2.0 # halve it for select() build_app! unless preload_app @@ -505,7 +502,7 @@ module Unicorn ppid = master_pid init_worker_process(worker) nr = 0 # this becomes negative if we need to reopen logs - alive = worker.tempfile # tempfile is our lifeline to the master process + alive = worker.tmp # tmp is our lifeline to the master process ready = LISTENERS t = ti = 0 @@ -570,7 +567,7 @@ module Unicorn begin Process.kill(signal, wpid) rescue Errno::ESRCH - worker = WORKERS.delete(wpid) and worker.tempfile.close rescue nil + worker = WORKERS.delete(wpid) and worker.tmp.close rescue nil end end diff --git a/lib/unicorn/app/exec_cgi.rb b/lib/unicorn/app/exec_cgi.rb index 147b279..67817c8 100644 --- a/lib/unicorn/app/exec_cgi.rb +++ b/lib/unicorn/app/exec_cgi.rb @@ -42,11 +42,8 @@ module Unicorn::App # Calls the app def call(env) - out, err = Tempfile.new(nil), Tempfile.new(nil) - out.unlink - err.unlink + out, err = Unicorn::Util.tmpio, Unicorn::Util.tmpio inp = force_file_input(env) - out.sync = err.sync = true pid = fork { run_child(inp, out, err, env) } inp.close pid, status = Process.waitpid2(pid) @@ -125,10 +122,7 @@ module Unicorn::App if inp.size == 0 # inp could be a StringIO or StringIO-like object ::File.open('/dev/null', 'rb') else - tmp = Tempfile.new(nil) - tmp.unlink - tmp.binmode - tmp.sync = true + tmp = Unicorn::Util.tmpio buf = Z.dup while inp.read(CHUNK_SIZE, buf) diff --git a/lib/unicorn/tee_input.rb b/lib/unicorn/tee_input.rb index 1bcbf1d..bbc496b 100644 --- a/lib/unicorn/tee_input.rb +++ b/lib/unicorn/tee_input.rb @@ -1,10 +1,8 @@ # Copyright (c) 2009 Eric Wong # You can redistribute it and/or modify it under the same terms as Ruby. -require 'tempfile' - # acts like tee(1) on an input input to provide a input-like stream -# while providing rewindable semantics through a Tempfile/StringIO +# while providing rewindable semantics through a File/StringIO # backing store. On the first pass, the input is only read on demand # so your Rack application can use input notification (upload progress # and like). This should fully conform to the Rack::InputWrapper @@ -16,10 +14,7 @@ module Unicorn class TeeInput def initialize(input, size, body) - @tmp = Tempfile.new(nil) - @tmp.unlink - @tmp.binmode - @tmp.sync = true + @tmp = Unicorn::Util.tmpio if body @tmp.write(body) diff --git a/lib/unicorn/util.rb b/lib/unicorn/util.rb index 2d3f827..d2214b7 100644 --- a/lib/unicorn/util.rb +++ b/lib/unicorn/util.rb @@ -1,4 +1,5 @@ require 'fcntl' +require 'tmpdir' module Unicorn class Util @@ -39,6 +40,22 @@ module Unicorn nr end + # creates and returns a new File object. The File is unlinked + # immediately, switched to binary mode, and userspace output + # buffering is disabled + def tmpio + fp = begin + File.open("#{Dir::tmpdir}/#{rand}", + File::RDWR|File::CREAT|File::EXCL, 0600) + rescue Errno::EEXIST + retry + end + File.unlink(fp.path) + fp.binmode + fp.sync = true + fp + end + end end -- cgit v1.2.3-24-ge0c7