about summary refs log tree commit homepage
path: root/bin
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2009-02-09 14:26:34 -0800
committerEric Wong <normalperson@yhbt.net>2009-02-09 19:52:20 -0800
commit101fb9ad1372e97ddf998c7fd677e352719c90e8 (patch)
tree354b0cd16fa1ca81b2f24b843e7bedf2f1eeef97 /bin
parent0b9dac5de7ecf8111dd3d9fa621edc759c9c47e3 (diff)
downloadunicorn-101fb9ad1372e97ddf998c7fd677e352719c90e8.tar.gz
Along with worker process management.  This is nginx-style
inplace upgrading (I don't know of another web server that does
this).  Basically we can preserve our opened listen sockets
across entire executable upgrades.

Signals:

  USR2 - Sending USR2 to the master unicorn process will cause
  it to exec a new master and keep the original workers running.
  This is useful to validate that the new code changes took place
  are valid and don't immediately die.  Once the changes are
  validated (manually), you may send QUIT to the original
  master process to have it gracefully exit.

  HUP - Sending this to the master will make it immediately exec
  a new binary and cause the old workers to gracefully exit.
  Use this if you're certain the latest changes to Unicorn (and
  your app) are ready and don't need validating.

Unlike nginx, re-execing a new binary will pick up any and all
configuration changes.  However listener sockets cannot be
removed when exec-ing; only added (for now).

I apologize for making such a big change in one commit, but once
I got the ability to replace the entire codebase while preserving
connections, it was too tempting to continue working.

So I wrote a large chunk of this while hitting
the unicorn-hello-world app with the following loop:

   while curl -vSsfN http://0:8080; do date +%N; done

_Zero_ requests lost across multiple restarts.
Diffstat (limited to 'bin')
-rwxr-xr-xbin/unicorn17
-rwxr-xr-xbin/unicorn-hello-world26
2 files changed, 43 insertions, 0 deletions
diff --git a/bin/unicorn b/bin/unicorn
new file mode 100755
index 0000000..a4c3f19
--- /dev/null
+++ b/bin/unicorn
@@ -0,0 +1,17 @@
+#!/home/ew/bin/ruby
+STDIN.sync = STDOUT.sync = STDERR.sync = true
+usage = "Usage: #{File.basename($0)} <config_file>"
+require 'unicorn'
+exit 0 if ARGV.size == 2 && ARGV[-1] == 'check' # used for reexec_check
+ARGV.size == 1 or abort usage
+case ARGV[0]
+when 'check' then exit
+when '-h' then puts usage
+when '-v' then puts "unicorn v#{Unicorn::Const::UNICORN_VERSION}"
+else
+  File.readable?(ARGV[0]) && File.file?(ARGV[0]) or abort usage
+  config = eval(File.read(ARGV[0]))
+  config.kind_of?(Hash) or abort "config is not a hash: #{config.class}"
+  app = config.delete(:app) or abort "Missing :app key in config!"
+  Unicorn.run(app, config)
+end
diff --git a/bin/unicorn-hello-world b/bin/unicorn-hello-world
new file mode 100755
index 0000000..2aba773
--- /dev/null
+++ b/bin/unicorn-hello-world
@@ -0,0 +1,26 @@
+#!/usr/bin/env ruby
+# Simple "Hello World" application for Unicorn
+
+# Exec ourselves with unicorn.  A shebang (e.g. "#!/usr/bin/unicorn")
+# won't work since unicorn itself is a Ruby script with a shebang, but
+# this does:
+exec('unicorn', $0) if $0 == __FILE__
+
+# Rack-compatible "Hello World" application
+class HelloWorld
+  MSG = "Hello world!\n"
+
+  def call(env)
+    [ 200,
+       { "Content-Type" => "text/plain",
+         "Content-Length" => MSG.size},
+     [ MSG ]
+    ]
+  end
+end
+
+# make sure this hash is the last statement, as this is eval-ed by unicorn
+{
+  # :listeners => %w(0.0.0.0:8080 127.0.0.1:7701 /tmp/test.sock),
+  :app => HelloWorld.new,
+}