unicorn 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|