summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2009-07-19 16:38:16 -0700
committerEric Wong <normalperson@yhbt.net>2009-07-19 16:50:36 -0700
commit97e469fc3afb751618b8b9a7b364cb447aaf90dd (patch)
tree1c3fdba70ccd2f88e6471441a4c66335189eafae
parent7db0e317dee3514fd80cc0a97a9b78a7a893ec22 (diff)
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.
-rw-r--r--lib/unicorn.rb23
-rw-r--r--lib/unicorn/app/exec_cgi.rb10
-rw-r--r--lib/unicorn/tee_input.rb9
-rw-r--r--lib/unicorn/util.rb17
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