unicorn 0.1.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +15 -9
- data/CONTRIBUTORS +28 -19
- data/DESIGN +2 -1
- data/Manifest +1 -0
- data/README +63 -9
- data/SIGNALS +2 -0
- data/bin/unicorn_rails +238 -0
- data/lib/unicorn.rb +36 -34
- data/lib/unicorn/configurator.rb +4 -0
- data/lib/unicorn/const.rb +1 -1
- data/lib/unicorn/socket.rb +1 -0
- data/test/exec/test_exec.rb +17 -1
- data/test/test_helper.rb +12 -1
- data/test/unit/test_upload.rb +28 -0
- data/unicorn.gemspec +5 -6
- metadata +5 -2
data/CHANGELOG
CHANGED
@@ -1,19 +1,25 @@
|
|
1
|
+
v0.2.1 - Fix broken Manifest that cause unicorn_rails to not be bundled
|
2
|
+
|
3
|
+
v0.2.0 - unicorn_rails launcher script.
|
4
|
+
|
1
5
|
v0.1.0 - Unicorn - UNIX-only fork of Mongrel free of threading
|
2
6
|
|
3
|
-
|
7
|
+
-- old Mongrel changelog --
|
8
|
+
|
9
|
+
v2.0. (WIP) Rack support.
|
4
10
|
|
5
|
-
v1.1.4. Fix camping handler. Correct treatment of @throttle parameter.
|
11
|
+
v1.1.4. Fix camping handler. Correct treatment of @throttle parameter.
|
6
12
|
|
7
|
-
v1.1.3. Fix security flaw of DirHandler; reported on mailing list.
|
13
|
+
v1.1.3. Fix security flaw of DirHandler; reported on mailing list.
|
8
14
|
|
9
|
-
v1.1.2. Fix worker termination bug; fix JRuby 1.0.3 load order issue; fix require issue on systems without Rubygems.
|
15
|
+
v1.1.2. Fix worker termination bug; fix JRuby 1.0.3 load order issue; fix require issue on systems without Rubygems.
|
10
16
|
|
11
|
-
v1.1.1. Fix mongrel_rails restart bug; fix bug with Rack status codes.
|
17
|
+
v1.1.1. Fix mongrel_rails restart bug; fix bug with Rack status codes.
|
12
18
|
|
13
|
-
v1.1. Pure Ruby URIClassifier. More modular architecture. JRuby support. Move C URIClassifier into mongrel_experimental project.
|
19
|
+
v1.1. Pure Ruby URIClassifier. More modular architecture. JRuby support. Move C URIClassifier into mongrel_experimental project.
|
14
20
|
|
15
|
-
v1.0.4. Backport fixes for versioning inconsistency, mongrel_rails bug, and DirHandler bug.
|
21
|
+
v1.0.4. Backport fixes for versioning inconsistency, mongrel_rails bug, and DirHandler bug.
|
16
22
|
|
17
|
-
v1.0.3. Fix user-switching bug; make people upgrade to the latest from the RC.
|
23
|
+
v1.0.3. Fix user-switching bug; make people upgrade to the latest from the RC.
|
18
24
|
|
19
|
-
v1.0.2. Signed gem; many minor bugfixes and patches.
|
25
|
+
v1.0.2. Signed gem; many minor bugfixes and patches.
|
data/CONTRIBUTORS
CHANGED
@@ -1,20 +1,29 @@
|
|
1
|
-
Unicorn
|
1
|
+
Unicorn developers:
|
2
|
+
* Eric Wong
|
3
|
+
* ... (help wanted)
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
5
|
+
We would like to thank following folks for helping make Unicorn possible:
|
6
|
+
|
7
|
+
* Ezra Zygmuntowicz - for helping Eric decide on a sane configuration
|
8
|
+
format and reasonable defaults.
|
9
|
+
* Christian Neukirchen - for Rack, which let us put more focus on the server
|
10
|
+
and drastically cut down on the amount of code we have to maintain.
|
11
|
+
* Zed A. Shaw - for Mongrel, without which Unicorn would not be possible
|
12
|
+
|
13
|
+
The original Mongrel contributors:
|
14
|
+
|
15
|
+
* Luis Lavena
|
16
|
+
* Wilson Bilkovich
|
17
|
+
* Why the Lucky Stiff
|
18
|
+
* Dan Kubb
|
19
|
+
* MenTaLguY
|
20
|
+
* Filipe Lautert
|
21
|
+
* Rick Olson
|
22
|
+
* Wayne E. Seguin
|
23
|
+
* Kirk Haines
|
24
|
+
* Bradley Taylor
|
25
|
+
* Matt Pelletier
|
26
|
+
* Ry Dahl
|
27
|
+
* Nick Sieger
|
28
|
+
* Evan Weaver
|
29
|
+
* Marc-André Cournoyer
|
data/DESIGN
CHANGED
@@ -32,7 +32,8 @@
|
|
32
32
|
Rack application itself is called only within the worker process (but
|
33
33
|
can be loaded within the master). A copy-on-write friendly garbage
|
34
34
|
collector like Ruby Enterprise Edition can be used to minimize memory
|
35
|
-
usage along with the "preload_app true" directive
|
35
|
+
usage along with the "preload_app true" directive (see
|
36
|
+
Unicorn::Configurator).
|
36
37
|
|
37
38
|
* The number of worker processes should be scaled to the number of
|
38
39
|
CPUs, memory or even spindles you have. If you have an existing
|
data/Manifest
CHANGED
data/README
CHANGED
@@ -35,7 +35,7 @@ proxy we know of that meets this requirement.
|
|
35
35
|
== License
|
36
36
|
|
37
37
|
Unicorn is copyright 2009 Eric Wong and contributors.
|
38
|
-
It is based on Mongrel:
|
38
|
+
It is based on Mongrel and carries the same license:
|
39
39
|
|
40
40
|
Mongrel is copyright 2007 Zed A. Shaw and contributors. It is licensed
|
41
41
|
under the Ruby license and the GPL2. See the include LICENSE file for
|
@@ -46,28 +46,82 @@ details.
|
|
46
46
|
The library consists of a C extension so you'll need a C compiler or at
|
47
47
|
least a friend who can build it for you.
|
48
48
|
|
49
|
-
|
49
|
+
You may download the tarball from the Mongrel project page on Rubyforge
|
50
|
+
and run setup.rb after unpacking it:
|
50
51
|
|
51
|
-
|
52
|
+
http://rubyforge.org/frs/?group_id=1306
|
52
53
|
|
53
|
-
|
54
|
+
You may also install it via Rubygems on Rubyforge:
|
55
|
+
|
56
|
+
gem install unicorn
|
57
|
+
|
58
|
+
You can get the latest source via git from the following locations
|
59
|
+
(these versions may not be stable):
|
54
60
|
|
61
|
+
git://git.bogomips.org/unicorn.git
|
55
62
|
http://git.bogomips.org/unicorn.git
|
63
|
+
git://repo.or.cz/unicorn.git (mirror)
|
64
|
+
http://repo.or.cz/r/unicorn.git (mirror)
|
65
|
+
|
66
|
+
If you have web browser software for the World Wide Web
|
67
|
+
(on the Information Superhighway), you may browse the code from
|
68
|
+
your web browser and download the latest snapshot tarballs here:
|
69
|
+
|
70
|
+
* http://git.bogomips.org/cgit/unicorn.git
|
71
|
+
* http://repo.or.cz/w/unicorn.git (gitweb mirror)
|
56
72
|
|
57
73
|
== Usage
|
58
74
|
|
75
|
+
=== non-Rails Rack applications
|
76
|
+
|
59
77
|
Unicorn will look for the config.ru file used by rackup in APP_ROOT.
|
60
|
-
Optionally, it can use a config file
|
61
|
-
command-line switch.
|
78
|
+
Optionally, it can use a config file for unicorn-specific options
|
79
|
+
specified by the --config-file/-c command-line switch. See
|
80
|
+
Unicorn::Configurator for the syntax of the unicorn-specific
|
81
|
+
config options.
|
82
|
+
|
83
|
+
In APP_ROOT, just run:
|
84
|
+
|
85
|
+
unicorn
|
62
86
|
|
63
|
-
Unicorn should be capable of running
|
87
|
+
Unicorn should be capable of running most Rack applications. Since this
|
64
88
|
is a preforking webserver, you do not have to worry about thread-safety
|
65
89
|
of your application or libraries. However, your Rack application may use
|
66
90
|
threads internally (and should even be able to continue running threads
|
67
91
|
after the request is complete).
|
68
92
|
|
69
|
-
|
93
|
+
=== Rack-enabled versions of Rails (v2.3.2+)
|
94
|
+
|
95
|
+
In RAILS_ROOT, run:
|
96
|
+
|
97
|
+
unicorn_rails
|
98
|
+
|
99
|
+
Most command-line options for other Rack applications (above) are also
|
100
|
+
supported. The unicorn_rails launcher attempts to combine the best
|
101
|
+
features of the Rails-bundled "script/server" with the "rackup"-like
|
102
|
+
functionality of the `unicorn' launcher.
|
103
|
+
|
104
|
+
== Disclaimer
|
70
105
|
|
71
|
-
|
106
|
+
There are no known production instances of unicorn deployed
|
107
|
+
anywhere in the world. The original author of unicorn only has
|
108
|
+
one, internal, low-traffic Sinatra application deployed with it.
|
109
|
+
Maybe you'll be the first guinea pig to test it in production.
|
110
|
+
Of course there is NO WARRANTY whatsoever if anything goes wrong,
|
111
|
+
but let us know and we'll try our best to fix it.
|
112
|
+
|
113
|
+
== Known Issues
|
114
|
+
|
115
|
+
* WONTFIX: code reloading with Sinatra 0.3.2 (and likely older
|
116
|
+
versions) apps is broken. The workaround is to force production
|
117
|
+
mode to disable code reloading in your Sinatra application:
|
118
|
+
set :env, :production
|
119
|
+
Since this is no longer an issue with Sinatra 0.9.x apps and only
|
120
|
+
affected non-production instances, this will not be fixed on our end.
|
121
|
+
Also remember we're capable of replacing the running binary without
|
122
|
+
dropping any connections regardless of framework :)
|
123
|
+
|
124
|
+
== Contact
|
72
125
|
|
126
|
+
Newsgroup and mailing list maybe coming...
|
73
127
|
Email Eric Wong at normalperson@yhbt.net for now.
|
data/SIGNALS
CHANGED
@@ -7,6 +7,8 @@ processes are documented here as well.
|
|
7
7
|
=== Master Process
|
8
8
|
|
9
9
|
* HUP - reload config file and gracefully restart all workers
|
10
|
+
If preload_app is false (the default), the application code
|
11
|
+
will be reloaded when workers are restarted as well.
|
10
12
|
|
11
13
|
* INT/TERM - quick shutdown, kills all workers immediately
|
12
14
|
|
data/bin/unicorn_rails
ADDED
@@ -0,0 +1,238 @@
|
|
1
|
+
#!/home/ew/bin/ruby
|
2
|
+
$stdin.sync = $stdout.sync = $stderr.sync = true
|
3
|
+
require 'unicorn' # require this first to populate Unicorn::DEFAULT_START_CTX
|
4
|
+
require 'optparse'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
rails_pid = File.join(Unicorn::HttpServer::DEFAULT_START_CTX[:cwd],
|
8
|
+
"/tmp/pids/unicorn.pid")
|
9
|
+
cmd = File.basename($0)
|
10
|
+
daemonize = false
|
11
|
+
listeners = []
|
12
|
+
options = { :listeners => listeners }
|
13
|
+
host, port = Unicorn::Const::DEFAULT_HOST, 3000
|
14
|
+
ENV['RAILS_ENV'] ||= "development"
|
15
|
+
map_path = ENV['RAILS_RELATIVE_URL_ROOT']
|
16
|
+
|
17
|
+
opts = OptionParser.new("", 24, ' ') do |opts|
|
18
|
+
opts.banner = "Usage: #{cmd} " \
|
19
|
+
"[ruby options] [#{cmd} options] [rackup config file]"
|
20
|
+
opts.separator "Ruby options:"
|
21
|
+
|
22
|
+
lineno = 1
|
23
|
+
opts.on("-e", "--eval LINE", "evaluate a LINE of code") do |line|
|
24
|
+
eval line, TOPLEVEL_BINDING, "-e", lineno
|
25
|
+
lineno += 1
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") do
|
29
|
+
$DEBUG = true
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on("-w", "--warn", "turn warnings on for your script") do
|
33
|
+
$-w = true
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on("-I", "--include PATH",
|
37
|
+
"specify $LOAD_PATH (may be used more than once)") do |path|
|
38
|
+
$LOAD_PATH.unshift(*path.split(/:/))
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on("-r", "--require LIBRARY",
|
42
|
+
"require the library, before executing your script") do |library|
|
43
|
+
require library
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.separator "#{cmd} options:"
|
47
|
+
|
48
|
+
# some of these switches exist for rackup command-line compatibility,
|
49
|
+
|
50
|
+
opts.on("-o", "--host HOST",
|
51
|
+
"listen on HOST (default: #{Unicorn::Const::DEFAULT_HOST})") do |h|
|
52
|
+
host = h
|
53
|
+
end
|
54
|
+
|
55
|
+
opts.on("-p", "--port PORT", "use PORT (default: #{port})") do |p|
|
56
|
+
port = p.to_i
|
57
|
+
end
|
58
|
+
|
59
|
+
opts.on("-E", "--env ENVIRONMENT",
|
60
|
+
"use ENVIRONMENT for defaults (default: development)") do |e|
|
61
|
+
ENV['RAILS_ENV'] = e
|
62
|
+
end
|
63
|
+
|
64
|
+
opts.on("-D", "--daemonize", "run daemonized in the background") do |d|
|
65
|
+
daemonize = d ? true : false
|
66
|
+
end
|
67
|
+
|
68
|
+
# Unicorn-specific stuff
|
69
|
+
opts.on("-l", "--listen {HOST:PORT|PATH}",
|
70
|
+
"listen on HOST:PORT or PATH",
|
71
|
+
"this may be specified multiple times",
|
72
|
+
"(default: #{Unicorn::Const::DEFAULT_LISTEN})") do |address|
|
73
|
+
listeners << address
|
74
|
+
end
|
75
|
+
|
76
|
+
opts.on("-c", "--config-file FILE", "Unicorn-specific config file") do |f|
|
77
|
+
options[:config_file] = File.expand_path(f)
|
78
|
+
end
|
79
|
+
|
80
|
+
opts.on("-P", "--path PATH", "Runs Rails app mounted at a specific path.",
|
81
|
+
"(default: /") do |v|
|
82
|
+
map_path = v
|
83
|
+
end
|
84
|
+
|
85
|
+
# I'm avoiding Unicorn-specific config options on the command-line.
|
86
|
+
# IMNSHO, config options on the command-line are redundant given
|
87
|
+
# config files and make things unnecessarily complicated with multiple
|
88
|
+
# places to look for a config option.
|
89
|
+
|
90
|
+
opts.separator "Common options:"
|
91
|
+
|
92
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
93
|
+
puts opts
|
94
|
+
exit
|
95
|
+
end
|
96
|
+
|
97
|
+
opts.on_tail("-v", "--version", "Show version") do
|
98
|
+
puts " v#{Unicorn::Const::UNICORN_VERSION}"
|
99
|
+
exit
|
100
|
+
end
|
101
|
+
|
102
|
+
opts.parse! ARGV
|
103
|
+
end
|
104
|
+
|
105
|
+
require 'pp' if $DEBUG
|
106
|
+
|
107
|
+
# Loads Rails and the private version of Rack it bundles. Returns a
|
108
|
+
# lambda of arity==0 that will return *another* lambda of arity==1
|
109
|
+
# suitable for using inside Rack::Builder.new block.
|
110
|
+
rails_loader = lambda do ||
|
111
|
+
begin
|
112
|
+
require 'config/boot'
|
113
|
+
defined?(::RAILS_ROOT) or abort "RAILS_ROOT not defined by config/boot"
|
114
|
+
defined?(::RAILS_ENV) or abort "RAILS_ENV not defined by config/boot"
|
115
|
+
defined?(::Rails::VERSION::STRING) or
|
116
|
+
abort "Rails::VERSION::STRING not defined by config/boot"
|
117
|
+
rescue LoadError
|
118
|
+
abort "#$0 must be run inside RAILS_ROOT (#{::RAILS_ROOT})"
|
119
|
+
end
|
120
|
+
|
121
|
+
if ENV['UNICORN_RAILS_USE_SYSTEM_RACK'].to_i == 0
|
122
|
+
rails_ver = Rails::VERSION::STRING
|
123
|
+
|
124
|
+
# maps Rails versions to the vendorized Rack version they bundle
|
125
|
+
version_map = {
|
126
|
+
'2.3.2' => '1.0',
|
127
|
+
# 3.0.0 => false, # assuming 3.0.0 doesn't need vendorized Rack anymore
|
128
|
+
}
|
129
|
+
rack_ver = version_map[rails_ver] or
|
130
|
+
warn "Possibly unsupported Rails version: v#{rails_ver}"
|
131
|
+
|
132
|
+
rack_path = nil
|
133
|
+
case rack_ver
|
134
|
+
when String, NilClass
|
135
|
+
version_map.values.find_all { |v| String === v }.sort.each do |v|
|
136
|
+
$LOAD_PATH.grep(%r{/actionpack-[\d\.]+/lib/?\z}).each do |path|
|
137
|
+
rack_path = File.join(path, "action_controller/vendor/rack-#{v}")
|
138
|
+
File.directory?(rack_path) and break
|
139
|
+
rack_path = nil
|
140
|
+
end
|
141
|
+
break if rack_path
|
142
|
+
end
|
143
|
+
rack_path or abort(
|
144
|
+
"Unable to find Rails-vendorized Rack library.\n" \
|
145
|
+
"Perhaps this script is no longer with your" \
|
146
|
+
"Rails version (#{rails_ver}).\n")
|
147
|
+
puts "vendorized Rack load path #{rack_path}"
|
148
|
+
$LOAD_PATH.unshift(rack_path)
|
149
|
+
when FalseClass
|
150
|
+
# using non-vendorized rack library (most likely via gems)
|
151
|
+
end
|
152
|
+
end # Vendorized Rack LOAD_PATH finder
|
153
|
+
|
154
|
+
# require Rack as late as possible in case $LOAD_PATH is modified
|
155
|
+
# in config.ru or command-line
|
156
|
+
require 'rack'
|
157
|
+
|
158
|
+
# return the lambda
|
159
|
+
config = ::ARGV[0] || (File.exist?('config.ru') ? 'config.ru' : nil)
|
160
|
+
case config
|
161
|
+
when nil
|
162
|
+
lambda do ||
|
163
|
+
require "#{RAILS_ROOT}/config/environment"
|
164
|
+
ActionController::Dispatcher.new
|
165
|
+
end
|
166
|
+
when /\.ru$/
|
167
|
+
raw = File.open(config, "rb") { |fp| fp.sysread(fp.stat.size) }
|
168
|
+
# parse embedded command-line options in config.ru comments
|
169
|
+
if raw[/^#\\(.*)/]
|
170
|
+
opts.parse! $1.split(/\s+/)
|
171
|
+
require 'pp' if $DEBUG
|
172
|
+
end
|
173
|
+
lambda { || eval("Rack::Builder.new {(#{raw}\n)}.to_app", nil, config) }
|
174
|
+
else
|
175
|
+
lambda do ||
|
176
|
+
require config
|
177
|
+
Object.const_get(File.basename(config, '.rb').capitalize)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# this won't run until after forking if preload_app is false
|
183
|
+
app = lambda do ||
|
184
|
+
inner_app = rails_loader.call
|
185
|
+
require 'active_support'
|
186
|
+
require 'action_controller'
|
187
|
+
ActionController::Base.relative_url_root = map_path if map_path
|
188
|
+
Rack::Builder.new do
|
189
|
+
use Rails::Rack::LogTailer unless daemonize
|
190
|
+
use Rails::Rack::Debugger if $DEBUG
|
191
|
+
map(map_path || '/') do
|
192
|
+
use Rails::Rack::Static
|
193
|
+
run inner_app.call
|
194
|
+
end
|
195
|
+
end.to_app
|
196
|
+
end
|
197
|
+
|
198
|
+
if listeners.empty?
|
199
|
+
listener = "#{host}:#{port}"
|
200
|
+
listeners << listener
|
201
|
+
end
|
202
|
+
|
203
|
+
if $DEBUG
|
204
|
+
pp({
|
205
|
+
:unicorn_options => options,
|
206
|
+
:app => app,
|
207
|
+
:daemonize => daemonize,
|
208
|
+
})
|
209
|
+
end
|
210
|
+
|
211
|
+
# only daemonize if we're not inheriting file descriptors from our parent
|
212
|
+
if daemonize
|
213
|
+
options[:pid] = rails_pid
|
214
|
+
$stdin.reopen("/dev/null")
|
215
|
+
unless ENV['UNICORN_FD']
|
216
|
+
exit if fork
|
217
|
+
Process.setsid
|
218
|
+
exit if fork
|
219
|
+
end
|
220
|
+
|
221
|
+
# We don't do a lot of standard daemonization stuff:
|
222
|
+
# * $stderr/$stderr can/will be redirected separately
|
223
|
+
# * umask is whatever was set by the parent process at startup
|
224
|
+
# and can be set in config.ru and config_file, so making it
|
225
|
+
# 0000 and potentially exposing sensitive log data can be bad
|
226
|
+
# policy.
|
227
|
+
# * Don't bother to chdir here since Unicorn is designed to
|
228
|
+
# run inside APP_ROOT. Unicorn will also re-chdir() to
|
229
|
+
# the directory it was started in when being re-executed
|
230
|
+
# to pickup code changes if the original deployment directory
|
231
|
+
# is a symlink or otherwise got replaced.
|
232
|
+
end
|
233
|
+
|
234
|
+
# ensure Rails standard tmp paths exist
|
235
|
+
%w(cache pids sessions sockets).each do |dir|
|
236
|
+
FileUtils.mkdir_p("tmp/#{dir}")
|
237
|
+
end
|
238
|
+
Unicorn.run(app, options)
|
data/lib/unicorn.rb
CHANGED
@@ -53,7 +53,7 @@ module Unicorn
|
|
53
53
|
@start_ctx = DEFAULT_START_CTX.dup
|
54
54
|
@start_ctx.merge!(start_ctx) if start_ctx
|
55
55
|
@app = app
|
56
|
-
@
|
56
|
+
@sig_queue = []
|
57
57
|
@master_pid = $$
|
58
58
|
@workers = Hash.new
|
59
59
|
@io_purgatory = [] # prevents IO objects in here from being GC-ed
|
@@ -160,15 +160,17 @@ module Unicorn
|
|
160
160
|
# are trapped. See trap_deferred
|
161
161
|
@rd_sig, @wr_sig = IO.pipe unless (@rd_sig && @wr_sig)
|
162
162
|
@rd_sig.nonblock = @wr_sig.nonblock = true
|
163
|
+
ready = mode = nil
|
163
164
|
|
164
|
-
|
165
|
+
QUEUE_SIGS.each { |sig| trap_deferred(sig) }
|
166
|
+
trap('CHLD') { |sig_nr| awaken_master }
|
165
167
|
$0 = "unicorn master"
|
166
|
-
logger.info "master process ready" #
|
168
|
+
logger.info "master process ready" # test_exec.rb relies on this message
|
167
169
|
begin
|
168
170
|
loop do
|
169
171
|
reap_all_workers
|
170
|
-
case @
|
171
|
-
when
|
172
|
+
case (mode = @sig_queue.shift)
|
173
|
+
when nil
|
172
174
|
murder_lazy_workers
|
173
175
|
spawn_missing_workers
|
174
176
|
when 'QUIT' # graceful shutdown
|
@@ -176,17 +178,14 @@ module Unicorn
|
|
176
178
|
when 'TERM', 'INT' # immediate shutdown
|
177
179
|
stop(false)
|
178
180
|
break
|
179
|
-
when 'USR1' #
|
181
|
+
when 'USR1' # rotate logs
|
180
182
|
kill_each_worker('USR1')
|
181
183
|
Unicorn::Util.reopen_logs
|
182
|
-
reset_master
|
183
184
|
when 'USR2' # exec binary, stay alive in case something went wrong
|
184
185
|
reexec
|
185
|
-
reset_master
|
186
186
|
when 'HUP'
|
187
187
|
if @config.config_file
|
188
188
|
load_config!
|
189
|
-
reset_master
|
190
189
|
redo # immediate reaping since we may have QUIT workers
|
191
190
|
else # exec binary and exit if there's no config file
|
192
191
|
logger.info "config_file not present, reexecuting binary"
|
@@ -194,8 +193,7 @@ module Unicorn
|
|
194
193
|
break
|
195
194
|
end
|
196
195
|
else
|
197
|
-
logger.error "master process in unknown mode: #{
|
198
|
-
reset_master
|
196
|
+
logger.error "master process in unknown mode: #{mode}"
|
199
197
|
end
|
200
198
|
reap_all_workers
|
201
199
|
|
@@ -204,9 +202,10 @@ module Unicorn
|
|
204
202
|
rescue Errno::EINTR # next
|
205
203
|
end
|
206
204
|
ready[0] && ready[0][0] or next
|
207
|
-
begin
|
208
|
-
|
209
|
-
rescue Errno::EAGAIN, Errno::EINTR
|
205
|
+
begin
|
206
|
+
@rd_sig.sysread(1)
|
207
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
208
|
+
# spurious wakeup? ignore it
|
210
209
|
end
|
211
210
|
end
|
212
211
|
rescue Errno::EINTR
|
@@ -214,7 +213,6 @@ module Unicorn
|
|
214
213
|
rescue Object => e
|
215
214
|
logger.error "Unhandled master loop exception #{e.inspect}."
|
216
215
|
logger.error e.backtrace.join("\n")
|
217
|
-
reset_master
|
218
216
|
retry
|
219
217
|
end
|
220
218
|
stop # gracefully shutdown all workers on our way out
|
@@ -241,31 +239,27 @@ module Unicorn
|
|
241
239
|
private
|
242
240
|
|
243
241
|
# list of signals we care about and trap in master.
|
244
|
-
|
242
|
+
QUEUE_SIGS = %w(QUIT INT TERM USR1 USR2 HUP).map { |x| x.freeze }.freeze
|
245
243
|
|
246
244
|
# defer a signal for later processing in #join (master process)
|
247
245
|
def trap_deferred(signal)
|
248
246
|
trap(signal) do |sig_nr|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
@mode = signal
|
255
|
-
begin
|
256
|
-
@wr_sig.syswrite('.') # wakeup master process from IO.select
|
257
|
-
rescue Errno::EAGAIN
|
258
|
-
rescue Errno::EINTR
|
259
|
-
retry
|
260
|
-
end
|
247
|
+
if @sig_queue.size < 5
|
248
|
+
@sig_queue << signal
|
249
|
+
awaken_master
|
250
|
+
else
|
251
|
+
logger.error "ignoring SIG#{signal}, queue=#{@sig_queue.inspect}"
|
261
252
|
end
|
262
253
|
end
|
263
254
|
end
|
264
255
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
256
|
+
def awaken_master
|
257
|
+
begin
|
258
|
+
@wr_sig.syswrite('.') # wakeup master process from IO.select
|
259
|
+
rescue Errno::EAGAIN # pipe is full, master should wake up anyways
|
260
|
+
rescue Errno::EINTR
|
261
|
+
retry
|
262
|
+
end
|
269
263
|
end
|
270
264
|
|
271
265
|
# reaps all unreaped workers
|
@@ -352,6 +346,13 @@ module Unicorn
|
|
352
346
|
return if @workers.size == @worker_processes
|
353
347
|
(0...@worker_processes).each do |worker_nr|
|
354
348
|
@workers.values.include?(worker_nr) and next
|
349
|
+
begin
|
350
|
+
Dir.chdir(@start_ctx[:cwd])
|
351
|
+
rescue Errno::ENOENT => err
|
352
|
+
logger.fatal "#{err.inspect} (#{@start_ctx[:cwd]})"
|
353
|
+
@sig_queue << 'QUIT' # forcibly emulate SIGQUIT
|
354
|
+
return
|
355
|
+
end
|
355
356
|
tempfile = Tempfile.new('') # as short as possible to save dir space
|
356
357
|
tempfile.unlink # don't allow other processes to find or see it
|
357
358
|
tempfile.sync = true
|
@@ -389,7 +390,8 @@ module Unicorn
|
|
389
390
|
# by the user.
|
390
391
|
def init_worker_process(worker)
|
391
392
|
build_app! unless @preload_app
|
392
|
-
|
393
|
+
@sig_queue.clear
|
394
|
+
QUEUE_SIGS.each { |sig| trap(sig, 'IGNORE') }
|
393
395
|
trap('CHLD', 'DEFAULT')
|
394
396
|
trap('USR1') do
|
395
397
|
@logger.info "worker=#{worker.nr} rotating logs..."
|
@@ -403,7 +405,7 @@ module Unicorn
|
|
403
405
|
@workers.values.each { |other| other.tempfile.close rescue nil }
|
404
406
|
@workers.clear
|
405
407
|
@start_ctx.clear
|
406
|
-
@
|
408
|
+
@start_ctx = @workers = @rd_sig = @wr_sig = nil
|
407
409
|
@listeners.each { |sock| set_cloexec(sock) }
|
408
410
|
ENV.delete('UNICORN_FD')
|
409
411
|
@after_fork.call(self, worker.nr) if @after_fork
|
data/lib/unicorn/configurator.rb
CHANGED
@@ -194,6 +194,10 @@ module Unicorn
|
|
194
194
|
# properly close/reopen sockets. Files opened for logging do not
|
195
195
|
# have to be reopened as (unbuffered-in-userspace) files opened with
|
196
196
|
# the File::APPEND flag are written to atomically on UNIX.
|
197
|
+
#
|
198
|
+
# In addition to reloading the unicorn-specific config settings,
|
199
|
+
# SIGHUP will reload application code in the working
|
200
|
+
# directory/symlink when workers are gracefully restarted.
|
197
201
|
def preload_app(bool)
|
198
202
|
case bool
|
199
203
|
when TrueClass, FalseClass
|
data/lib/unicorn/const.rb
CHANGED
data/lib/unicorn/socket.rb
CHANGED
@@ -75,6 +75,7 @@ module Unicorn
|
|
75
75
|
def bind_listen(address = '0.0.0.0:8080', backlog = 1024)
|
76
76
|
return address unless String === address
|
77
77
|
|
78
|
+
address = File.expand_path(address) if address[0..0] == "~"
|
78
79
|
domain, bind_addr = if address[0..0] == "/"
|
79
80
|
if File.exist?(address)
|
80
81
|
if File.socket?(address)
|
data/test/exec/test_exec.rb
CHANGED
@@ -283,6 +283,7 @@ end
|
|
283
283
|
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
284
284
|
assert_equal String, results[0].class
|
285
285
|
wait_master_ready(COMMON_TMP.path)
|
286
|
+
wait_workers_ready(COMMON_TMP.path, 4)
|
286
287
|
bf = File.readlines(COMMON_TMP.path).grep(/\bbefore_fork: worker=/)
|
287
288
|
assert_equal 4, bf.size
|
288
289
|
rotate = Tempfile.new('unicorn_rotate')
|
@@ -496,6 +497,21 @@ end
|
|
496
497
|
assert status.success?, "exited successfully"
|
497
498
|
end
|
498
499
|
|
500
|
+
def wait_workers_ready(path, nr_workers)
|
501
|
+
tries = DEFAULT_TRIES
|
502
|
+
lines = []
|
503
|
+
while (tries -= 1) > 0
|
504
|
+
begin
|
505
|
+
lines = File.readlines(path).grep(/worker=\d+ spawned/)
|
506
|
+
lines.size == nr_workers and return
|
507
|
+
rescue Errno::ENOENT
|
508
|
+
end
|
509
|
+
sleep DEFAULT_RES
|
510
|
+
end
|
511
|
+
raise "#{nr_workers} workers never became ready:" \
|
512
|
+
"\n\t#{lines.join("\n\t")}\n"
|
513
|
+
end
|
514
|
+
|
499
515
|
def wait_master_ready(master_log)
|
500
516
|
tries = DEFAULT_TRIES
|
501
517
|
while (tries -= 1) > 0
|
@@ -555,7 +571,7 @@ end
|
|
555
571
|
while (tries -= 1) > 0 && ! File.exist?(path)
|
556
572
|
sleep DEFAULT_RES
|
557
573
|
end
|
558
|
-
assert File.exist?(path), "path=#{path} exists"
|
574
|
+
assert File.exist?(path), "path=#{path} exists #{caller.inspect}"
|
559
575
|
end
|
560
576
|
|
561
577
|
def xfork(&block)
|
data/test/test_helper.rb
CHANGED
@@ -41,7 +41,18 @@ def redirect_test_io
|
|
41
41
|
STDOUT.reopen(orig_out)
|
42
42
|
end
|
43
43
|
end
|
44
|
-
|
44
|
+
|
45
|
+
# which(1) exit codes cannot be trusted on some systems
|
46
|
+
# We use UNIX shell utilities in some tests because we don't trust
|
47
|
+
# ourselves to write Ruby 100% correctly :)
|
48
|
+
def which(bin)
|
49
|
+
ex = ENV['PATH'].split(/:/).detect do |x|
|
50
|
+
x << "/#{bin}"
|
51
|
+
File.executable?(x)
|
52
|
+
end or warn "`#{bin}' not found in PATH=#{ENV['PATH']}"
|
53
|
+
ex
|
54
|
+
end
|
55
|
+
|
45
56
|
# Either takes a string to do a get request against, or a tuple of [URI, HTTP] where
|
46
57
|
# HTTP is some kind of Net::HTTP request object (POST, HEAD, etc.)
|
47
58
|
def hit(uris)
|
data/test/unit/test_upload.rb
CHANGED
@@ -135,6 +135,34 @@ class UploadTest < Test::Unit::TestCase
|
|
135
135
|
assert_equal resp[:size], new_tmp.stat.size
|
136
136
|
end
|
137
137
|
|
138
|
+
# Despite reading numerous articles and inspecting the 1.9.1-p0 C
|
139
|
+
# source, Eric Wong will never trust that we're always handling
|
140
|
+
# encoding-aware IO objects correctly. Thus this test uses shell
|
141
|
+
# utilities that should always operate on files/sockets on a
|
142
|
+
# byte-level.
|
143
|
+
def test_uncomfortable_with_onenine_encodings
|
144
|
+
# POSIX doesn't require all of these to be present on a system
|
145
|
+
which('curl') or return
|
146
|
+
which('sha1sum') or return
|
147
|
+
which('dd') or return
|
148
|
+
|
149
|
+
start_server(@sha1_app)
|
150
|
+
|
151
|
+
tmp = Tempfile.new('dd_dest')
|
152
|
+
assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}",
|
153
|
+
"bs=#{@bs}", "count=#{@count}"),
|
154
|
+
"dd #@random to #{tmp}")
|
155
|
+
sha1_re = %r!\b([a-f0-9]{40})\b!
|
156
|
+
sha1_out = `sha1sum #{tmp.path}`
|
157
|
+
assert $?.success?, 'sha1sum ran OK'
|
158
|
+
|
159
|
+
assert_match(sha1_re, sha1_out)
|
160
|
+
sha1 = sha1_re.match(sha1_out)[1]
|
161
|
+
resp = `curl -isSfN -T#{tmp.path} http://#@addr:#@port/`
|
162
|
+
assert $?.success?, 'curl ran OK'
|
163
|
+
assert_match(%r!\b#{sha1}\b!, resp)
|
164
|
+
end
|
165
|
+
|
138
166
|
private
|
139
167
|
|
140
168
|
def length
|
data/unicorn.gemspec
CHANGED
@@ -2,18 +2,17 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{unicorn}
|
5
|
-
s.version = "0.1
|
5
|
+
s.version = "0.2.1"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Eric Wong"]
|
9
|
-
s.date = %q{2009-03-
|
10
|
-
s.default_executable = %q{unicorn}
|
9
|
+
s.date = %q{2009-03-18}
|
11
10
|
s.description = %q{A small fast HTTP library and server for Rack applications.}
|
12
11
|
s.email = %q{normalperson@yhbt.net}
|
13
|
-
s.executables = ["unicorn"]
|
12
|
+
s.executables = ["unicorn", "unicorn_rails"]
|
14
13
|
s.extensions = ["ext/unicorn/http11/extconf.rb"]
|
15
|
-
s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README", "TODO", "bin/unicorn", "ext/unicorn/http11/ext_help.h", "ext/unicorn/http11/extconf.rb", "ext/unicorn/http11/http11.c", "ext/unicorn/http11/http11_parser.c", "ext/unicorn/http11/http11_parser.h", "ext/unicorn/http11/http11_parser.rl", "ext/unicorn/http11/http11_parser_common.rl", "lib/unicorn.rb", "lib/unicorn/configurator.rb", "lib/unicorn/const.rb", "lib/unicorn/http_request.rb", "lib/unicorn/http_response.rb", "lib/unicorn/socket.rb", "lib/unicorn/util.rb"]
|
16
|
-
s.files = [".document", ".gitignore", "CHANGELOG", "CONTRIBUTORS", "DESIGN", "GNUmakefile", "LICENSE", "Manifest", "README", "Rakefile", "SIGNALS", "TODO", "bin/unicorn", "ext/unicorn/http11/ext_help.h", "ext/unicorn/http11/extconf.rb", "ext/unicorn/http11/http11.c", "ext/unicorn/http11/http11_parser.c", "ext/unicorn/http11/http11_parser.h", "ext/unicorn/http11/http11_parser.rl", "ext/unicorn/http11/http11_parser_common.rl", "lib/unicorn.rb", "lib/unicorn/configurator.rb", "lib/unicorn/const.rb", "lib/unicorn/http_request.rb", "lib/unicorn/http_response.rb", "lib/unicorn/socket.rb", "lib/unicorn/util.rb", "setup.rb", "test/aggregate.rb", "test/benchmark/previous.rb", "test/benchmark/simple.rb", "test/benchmark/utils.rb", "test/exec/README", "test/exec/test_exec.rb", "test/test_helper.rb", "test/tools/trickletest.rb", "test/unit/test_configurator.rb", "test/unit/test_http_parser.rb", "test/unit/test_response.rb", "test/unit/test_server.rb", "test/unit/test_upload.rb", "unicorn.gemspec"]
|
14
|
+
s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README", "TODO", "bin/unicorn", "bin/unicorn_rails", "ext/unicorn/http11/ext_help.h", "ext/unicorn/http11/extconf.rb", "ext/unicorn/http11/http11.c", "ext/unicorn/http11/http11_parser.c", "ext/unicorn/http11/http11_parser.h", "ext/unicorn/http11/http11_parser.rl", "ext/unicorn/http11/http11_parser_common.rl", "lib/unicorn.rb", "lib/unicorn/configurator.rb", "lib/unicorn/const.rb", "lib/unicorn/http_request.rb", "lib/unicorn/http_response.rb", "lib/unicorn/socket.rb", "lib/unicorn/util.rb"]
|
15
|
+
s.files = [".document", ".gitignore", "CHANGELOG", "CONTRIBUTORS", "DESIGN", "GNUmakefile", "LICENSE", "Manifest", "README", "Rakefile", "SIGNALS", "TODO", "bin/unicorn", "bin/unicorn_rails", "ext/unicorn/http11/ext_help.h", "ext/unicorn/http11/extconf.rb", "ext/unicorn/http11/http11.c", "ext/unicorn/http11/http11_parser.c", "ext/unicorn/http11/http11_parser.h", "ext/unicorn/http11/http11_parser.rl", "ext/unicorn/http11/http11_parser_common.rl", "lib/unicorn.rb", "lib/unicorn/configurator.rb", "lib/unicorn/const.rb", "lib/unicorn/http_request.rb", "lib/unicorn/http_response.rb", "lib/unicorn/socket.rb", "lib/unicorn/util.rb", "setup.rb", "test/aggregate.rb", "test/benchmark/previous.rb", "test/benchmark/simple.rb", "test/benchmark/utils.rb", "test/exec/README", "test/exec/test_exec.rb", "test/test_helper.rb", "test/tools/trickletest.rb", "test/unit/test_configurator.rb", "test/unit/test_http_parser.rb", "test/unit/test_response.rb", "test/unit/test_server.rb", "test/unit/test_upload.rb", "unicorn.gemspec"]
|
17
16
|
s.has_rdoc = true
|
18
17
|
s.homepage = %q{http://unicorn.bogomips.org}
|
19
18
|
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Unicorn", "--main", "README"]
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unicorn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Wong
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-03-
|
12
|
+
date: 2009-03-18 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -17,6 +17,7 @@ description: A small fast HTTP library and server for Rack applications.
|
|
17
17
|
email: normalperson@yhbt.net
|
18
18
|
executables:
|
19
19
|
- unicorn
|
20
|
+
- unicorn_rails
|
20
21
|
extensions:
|
21
22
|
- ext/unicorn/http11/extconf.rb
|
22
23
|
extra_rdoc_files:
|
@@ -25,6 +26,7 @@ extra_rdoc_files:
|
|
25
26
|
- README
|
26
27
|
- TODO
|
27
28
|
- bin/unicorn
|
29
|
+
- bin/unicorn_rails
|
28
30
|
- ext/unicorn/http11/ext_help.h
|
29
31
|
- ext/unicorn/http11/extconf.rb
|
30
32
|
- ext/unicorn/http11/http11.c
|
@@ -53,6 +55,7 @@ files:
|
|
53
55
|
- SIGNALS
|
54
56
|
- TODO
|
55
57
|
- bin/unicorn
|
58
|
+
- bin/unicorn_rails
|
56
59
|
- ext/unicorn/http11/ext_help.h
|
57
60
|
- ext/unicorn/http11/extconf.rb
|
58
61
|
- ext/unicorn/http11/http11.c
|