about summary refs log tree commit homepage
path: root/lib
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2009-02-09 20:42:03 -0800
committerEric Wong <normalperson@yhbt.net>2009-02-09 20:51:58 -0800
commit8fd433875764163eb042a5d7489c2895677ddde1 (patch)
tree31232437d1ca9ae2a16a500630e9f591e4cb1b08 /lib
parent07e2f413ab891306d3cdd8e49d0b13acc5e4f679 (diff)
downloadunicorn-8fd433875764163eb042a5d7489c2895677ddde1.tar.gz
Like nginx, we'll replace the existing "pid_file" with
"pid_file.oldbin" when executing a new binary.  We'll also
refuse to reexecute a new binary if the ".oldbin" pid file
already exists and points to a valid PID.
Diffstat (limited to 'lib')
-rw-r--r--lib/unicorn.rb42
1 files changed, 42 insertions, 0 deletions
diff --git a/lib/unicorn.rb b/lib/unicorn.rb
index 9575944..cacb395 100644
--- a/lib/unicorn.rb
+++ b/lib/unicorn.rb
@@ -43,6 +43,7 @@ module Unicorn
       :before_fork => lambda { |server, worker_nr|
           server.logger.info("worker=#{worker_nr} spawning...")
         },
+      :pid_file => nil,
     }
 
     Worker = Struct.new(:nr, :tempfile) unless defined?(Worker)
@@ -93,6 +94,15 @@ module Unicorn
         io
       end
 
+      if @pid_file
+        if pid = pid_file_valid?(@pid_file)
+          raise ArgumentError, "Already running on pid=#{pid} ",
+                               "(or pid_file=#{@pid_file} is stale)"
+        end
+        File.open(@pid_file, 'wb') { |fp| fp.syswrite("#{$$}\n") }
+        at_exit { unlink_pid_file_safe(@pid_file) }
+      end
+
       # avoid binding inherited sockets, probably not perfect for TCPSockets
       # but it works for UNIXSockets
       @listeners -= inherited.map { |io| sock_name(io) }
@@ -260,6 +270,19 @@ module Unicorn
     # reexecutes the @start_ctx with a new binary
     def reexec
       check_reexec or return false
+
+      if @pid_file # clear the path for a new pid file
+        old_pid_file = "#{@pid_file}.oldbin"
+        if old_pid = pid_file_valid?(old_pid_file)
+          logger.error "old pid=#{old_pid} running with " \
+                       "existing pid_file=#{old_pid_file}, refusing rexec"
+          return
+        end
+        File.open(old_pid_file, 'wb') { |fp| fp.syswrite("#{$$}\n") }
+        at_exit { unlink_pid_file_safe(old_pid_file) }
+        File.unlink(@pid_file) if File.exist?(@pid_file)
+      end
+
       pid = spawn_start_ctx
       if waitpid(pid, WNOHANG)
         logger.error "rexec pid=#{pid} died with #{$?.exitstatus}"
@@ -422,5 +445,24 @@ module Unicorn
       @workers.keys.each { |pid| kill_worker(signal, pid) }
     end
 
+    # unlinks a PID file at given +path+ if it contains the current PID
+    # useful as an at_exit handler.
+    def unlink_pid_file_safe(path)
+      (File.read(path).to_i == $$ and File.unlink(path)) rescue nil
+    end
+
+    # returns a PID if a given path contains a non-stale PID file,
+    # nil otherwise.
+    def pid_file_valid?(path)
+      if File.exist?(path) && (pid = File.read(path).to_i) > 1
+        begin
+          kill(0, pid)
+          return pid
+        rescue Errno::ESRCH
+        end
+      end
+      nil
+    end
+
   end
 end