about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2009-07-19 17:11:01 -0700
committerEric Wong <normalperson@yhbt.net>2009-07-19 17:12:40 -0700
commit86fd8957c7799368619aa8ce054b440716494a11 (patch)
treeb2f3c516424cf471a259d953f9b722e4ff19290a
parentf3d7ea324c893ba9d8787afbaa9d2e55fcae0133 (diff)
downloadunicorn-86fd8957c7799368619aa8ce054b440716494a11.tar.gz
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.

(cherry picked from commit 344b85ff28e160daa6563ab7c80b733abdeb874a)

Conflicts:

	lib/unicorn.rb
	lib/unicorn/app/exec_cgi.rb
	lib/unicorn/tee_input.rb
-rw-r--r--lib/unicorn.rb23
-rw-r--r--lib/unicorn/app/exec_cgi.rb9
-rw-r--r--lib/unicorn/util.rb17
3 files changed, 29 insertions, 20 deletions
diff --git a/lib/unicorn.rb b/lib/unicorn.rb
index aac530b..49435d8 100644
--- a/lib/unicorn.rb
+++ b/lib/unicorn.rb
@@ -54,8 +54,7 @@ module Unicorn
       0 => $0.dup,
     }
 
-    Worker = Struct.new(:nr, :tempfile) unless defined?(Worker)
-    class Worker
+    class Worker < Struct.new(:nr, :tmp)
       # worker objects may be compared to just plain numbers
       def ==(other_nr)
         self.nr == other_nr
@@ -323,7 +322,7 @@ module Unicorn
             self.pid = @pid.chomp('.oldbin') if @pid
             proc_name 'master'
           else
-            worker = WORKERS.delete(pid) and worker.tempfile.close rescue nil
+            worker = WORKERS.delete(pid) and worker.tmp.close rescue nil
             logger.info "reaped #{status.inspect} " \
                         "worker=#{worker.nr rescue 'unknown'}"
           end
@@ -383,16 +382,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 |pid, worker|
         stat = begin
-          worker.tempfile.stat
+          worker.tmp.stat
         rescue => e
           logger.warn "worker=#{worker.nr} PID:#{pid} stat error: #{e.inspect}"
           kill_worker(:QUIT, pid)
@@ -416,9 +415,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)
         pid = fork { worker_loop(worker) }
         WORKERS[pid] = worker
@@ -466,10 +463,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
       @timeout /= 2.0 # halve it for select()
       build_app! unless @preload_app
@@ -489,7 +486,7 @@ module Unicorn
       master_pid = Process.ppid # slightly racy, but less memory usage
       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
 
@@ -555,7 +552,7 @@ module Unicorn
       begin
         Process.kill(signal, pid)
       rescue Errno::ESRCH
-        worker = WORKERS.delete(pid) and worker.tempfile.close rescue nil
+        worker = WORKERS.delete(pid) 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 8f81d78..b0fbedc 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(''), Tempfile.new('')
-      out.unlink
-      err.unlink
+      out, err = Unicorn::Util.tmpio, Unicorn::Util.tmpio
       inp = force_file_input(env)
-      inp.sync = out.sync = err.sync = true
       pid = fork { run_child(inp, out, err, env) }
       inp.close
       pid, status = Process.waitpid2(pid)
@@ -126,9 +123,7 @@ module Unicorn::App
       elsif inp.size == 0 # inp could be a StringIO or StringIO-like object
         ::File.open('/dev/null')
       else
-        tmp = Tempfile.new('')
-        tmp.unlink
-        tmp.binmode
+        tmp = Unicorn::Util.tmpio
 
         # Rack::Lint::InputWrapper doesn't allow sysread :(
         buf = ''
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