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 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
- v2.0. (WIP) Rack support.
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 would not be possible without Zed and all the contributors to Mongrel.
1
+ Unicorn developers:
2
+ * Eric Wong
3
+ * ... (help wanted)
2
4
 
3
- Eric Wong
4
- Ezra Zygmuntowicz
5
- Zed A. Shaw
6
- Luis Lavena
7
- Wilson Bilkovich
8
- Why the Lucky Stiff
9
- Dan Kubb
10
- MenTaLguY
11
- Filipe Lautert
12
- Rick Olson
13
- Wayne E. Seguin
14
- Kirk Haines
15
- Bradley Taylor
16
- Matt Pelletier
17
- Ry Dahl
18
- Nick Sieger
19
- Evan Weaver
20
- Marc-André Cournoyer
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
@@ -11,6 +11,7 @@ Rakefile
11
11
  SIGNALS
12
12
  TODO
13
13
  bin/unicorn
14
+ bin/unicorn_rails
14
15
  ext/unicorn/http11/ext_help.h
15
16
  ext/unicorn/http11/extconf.rb
16
17
  ext/unicorn/http11/http11.c
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
- Finally, the source includes a setup.rb for those who hate RubyGems.
49
+ You may download the tarball from the Mongrel project page on Rubyforge
50
+ and run setup.rb after unpacking it:
50
51
 
51
- You can get the source via git via the following locations:
52
+ http://rubyforge.org/frs/?group_id=1306
52
53
 
53
- git://git.bogomips.org/unicorn.git
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 specified by the --config-file/-c
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 all Rack applications. Since this
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
- == Contact
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
- Newsgroup and mailing list coming, or it'll be a part of the Mongrel project...
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
- @mode = :idle
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
- reset_master
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" # test relies on this message
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 @mode
171
- when :idle
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' # user-defined (probably something like log reopening)
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: #{@mode}, resetting"
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 # just consume the pipe when we're awakened, @mode is set
208
- loop { @rd_sig.sysread(Const::CHUNK_SIZE) }
209
- rescue Errno::EAGAIN, Errno::EINTR # next
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
- TRAP_SIGS = %w(QUIT INT TERM USR1 USR2 HUP).map { |x| x.freeze }.freeze
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
- # we only handle/defer one signal at a time and ignore all others
250
- # until we're ready again. Queueing signals can lead to more bugs,
251
- # and simplicity is the most important thing
252
- TRAP_SIGS.each { |sig| trap(sig, 'IGNORE') }
253
- if Symbol === @mode
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
- def reset_master
267
- @mode = :idle
268
- TRAP_SIGS.each { |sig| trap_deferred(sig) }
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
- TRAP_SIGS.each { |sig| trap(sig, 'IGNORE') }
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
- @mode = @start_ctx = @workers = @rd_sig = @wr_sig = nil
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
@@ -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
@@ -68,7 +68,7 @@ module Unicorn
68
68
  REQUEST_URI='REQUEST_URI'.freeze
69
69
  REQUEST_PATH='REQUEST_PATH'.freeze
70
70
 
71
- UNICORN_VERSION="0.1.0".freeze
71
+ UNICORN_VERSION="0.2.1".freeze
72
72
 
73
73
  UNICORN_TMP_BASE="unicorn".freeze
74
74
 
@@ -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)
@@ -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)
@@ -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.0"
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}
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.0
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-10 00:00:00 -07:00
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