unicorn 0.9.0 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2 -0
- data/examples/echo.ru +3 -8
- data/lib/unicorn.rb +99 -99
- data/lib/unicorn/app/exec_cgi.rb +24 -23
- data/lib/unicorn/app/inetd.rb +28 -30
- data/lib/unicorn/app/old_rails/static.rb +10 -10
- data/lib/unicorn/configurator.rb +21 -32
- data/lib/unicorn/const.rb +1 -1
- data/lib/unicorn/http_request.rb +3 -13
- data/test/unit/test_configurator.rb +15 -10
- data/test/unit/test_request.rb +1 -1
- data/unicorn.gemspec +3 -4
- metadata +3 -3
data/CHANGELOG
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
+
v0.9.1 - FD_CLOEXEC portability fix (v0.8.2 port)
|
1
2
|
v0.9.0 - bodies: "Transfer-Encoding: chunked", rewindable streaming
|
3
|
+
v0.8.2 - socket handling bugfixes and usability tweaks
|
2
4
|
v0.8.1 - safer timeout handling, more consistent reload behavior
|
3
5
|
v0.8.0 - enforce Rack dependency, minor performance improvements and fixes
|
4
6
|
v0.7.1 - minor fixes, cleanups and documentation improvements
|
data/examples/echo.ru
CHANGED
@@ -8,25 +8,20 @@
|
|
8
8
|
#
|
9
9
|
# Then type random stuff in your terminal to watch it get echoed back!
|
10
10
|
|
11
|
-
class EchoBody
|
12
|
-
def initialize(input)
|
13
|
-
@input = input
|
14
|
-
end
|
11
|
+
class EchoBody < Struct.new(:input)
|
15
12
|
|
16
13
|
def each(&block)
|
17
|
-
while buf =
|
14
|
+
while buf = input.read(4096)
|
18
15
|
yield buf
|
19
16
|
end
|
20
17
|
self
|
21
18
|
end
|
22
19
|
|
23
|
-
def close
|
24
|
-
@input = nil
|
25
|
-
end
|
26
20
|
end
|
27
21
|
|
28
22
|
use Rack::Chunked
|
29
23
|
run lambda { |env|
|
24
|
+
/\A100-continue\z/ =~ env['HTTP_EXPECT'] and return [100, {}, []]
|
30
25
|
[ 200, { 'Content-Type' => 'application/octet-stream' },
|
31
26
|
EchoBody.new(env['rack.input']) ]
|
32
27
|
}
|
data/lib/unicorn.rb
CHANGED
@@ -30,8 +30,12 @@ module Unicorn
|
|
30
30
|
# processes which in turn handle the I/O and application process.
|
31
31
|
# Listener sockets are started in the master process and shared with
|
32
32
|
# forked worker children.
|
33
|
-
|
34
|
-
|
33
|
+
|
34
|
+
class HttpServer < Struct.new(:listener_opts, :timeout, :worker_processes,
|
35
|
+
:before_fork, :after_fork, :before_exec,
|
36
|
+
:logger, :pid, :app, :preload_app,
|
37
|
+
:reexec_pid, :orig_app, :init_listeners,
|
38
|
+
:master_pid, :config)
|
35
39
|
include ::Unicorn::SocketHelper
|
36
40
|
|
37
41
|
# prevents IO objects in here from being GC-ed
|
@@ -59,11 +63,10 @@ module Unicorn
|
|
59
63
|
# don't rely on Dir.pwd here since it's not symlink-aware, and
|
60
64
|
# symlink dirs are the default with Capistrano...
|
61
65
|
:cwd => `/bin/sh -c pwd`.chomp("\n"),
|
62
|
-
|
66
|
+
0 => $0.dup,
|
63
67
|
}
|
64
68
|
|
65
|
-
Worker
|
66
|
-
class Worker
|
69
|
+
class Worker < Struct.new(:nr, :tempfile)
|
67
70
|
# worker objects may be compared to just plain numbers
|
68
71
|
def ==(other_nr)
|
69
72
|
self.nr == other_nr
|
@@ -75,14 +78,13 @@ module Unicorn
|
|
75
78
|
# HttpServer.run.join to join the thread that's processing
|
76
79
|
# incoming requests on the socket.
|
77
80
|
def initialize(app, options = {})
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
@orig_app = app
|
81
|
+
self.app = app
|
82
|
+
self.reexec_pid = 0
|
83
|
+
self.init_listeners = options[:listeners] ? options[:listeners].dup : []
|
84
|
+
self.config = Configurator.new(options.merge(:use_defaults => true))
|
85
|
+
self.listener_opts = {}
|
86
|
+
config.commit!(self, :skip => [:listeners, :pid])
|
87
|
+
self.orig_app = app
|
86
88
|
end
|
87
89
|
|
88
90
|
# Runs the thing. Returns self so you can run join on it
|
@@ -93,13 +95,13 @@ module Unicorn
|
|
93
95
|
# before they become UNIXServer or TCPServer
|
94
96
|
inherited = ENV['UNICORN_FD'].to_s.split(/,/).map do |fd|
|
95
97
|
io = Socket.for_fd(fd.to_i)
|
96
|
-
set_server_sockopt(io,
|
98
|
+
set_server_sockopt(io, listener_opts[sock_name(io)])
|
97
99
|
IO_PURGATORY << io
|
98
100
|
logger.info "inherited addr=#{sock_name(io)} fd=#{fd}"
|
99
101
|
server_cast(io)
|
100
102
|
end
|
101
103
|
|
102
|
-
config_listeners =
|
104
|
+
config_listeners = config[:listeners].dup
|
103
105
|
LISTENERS.replace(inherited)
|
104
106
|
|
105
107
|
# we start out with generic Socket objects that get cast to either
|
@@ -112,8 +114,9 @@ module Unicorn
|
|
112
114
|
end
|
113
115
|
config_listeners.each { |addr| listen(addr) }
|
114
116
|
raise ArgumentError, "no listeners" if LISTENERS.empty?
|
115
|
-
self.pid =
|
116
|
-
|
117
|
+
self.pid = config[:pid]
|
118
|
+
self.master_pid = $$
|
119
|
+
build_app! if preload_app
|
117
120
|
maintain_worker_count
|
118
121
|
self
|
119
122
|
end
|
@@ -131,8 +134,7 @@ module Unicorn
|
|
131
134
|
end
|
132
135
|
end
|
133
136
|
set_names = listener_names(listeners)
|
134
|
-
dead_names
|
135
|
-
dead_names.uniq!
|
137
|
+
dead_names.concat(cur_names - set_names).uniq!
|
136
138
|
|
137
139
|
LISTENERS.delete_if do |io|
|
138
140
|
if dead_names.include?(sock_name(io))
|
@@ -141,7 +143,7 @@ module Unicorn
|
|
141
143
|
end
|
142
144
|
(io.close rescue nil).nil? # true
|
143
145
|
else
|
144
|
-
set_server_sockopt(io,
|
146
|
+
set_server_sockopt(io, listener_opts[sock_name(io)])
|
145
147
|
false
|
146
148
|
end
|
147
149
|
end
|
@@ -152,28 +154,27 @@ module Unicorn
|
|
152
154
|
def stdout_path=(path); redirect_io($stdout, path); end
|
153
155
|
def stderr_path=(path); redirect_io($stderr, path); end
|
154
156
|
|
155
|
-
|
156
|
-
|
157
|
-
end
|
157
|
+
alias_method :set_pid, :pid=
|
158
|
+
undef_method :pid=
|
158
159
|
|
159
160
|
# sets the path for the PID file of the master process
|
160
161
|
def pid=(path)
|
161
162
|
if path
|
162
163
|
if x = valid_pid?(path)
|
163
|
-
return path if
|
164
|
+
return path if pid && path == pid && x == $$
|
164
165
|
raise ArgumentError, "Already running on PID:#{x} " \
|
165
166
|
"(or pid=#{path} is stale)"
|
166
167
|
end
|
167
168
|
end
|
168
|
-
unlink_pid_safe(
|
169
|
+
unlink_pid_safe(pid) if pid
|
169
170
|
File.open(path, 'wb') { |fp| fp.syswrite("#$$\n") } if path
|
170
|
-
|
171
|
+
self.set_pid(path)
|
171
172
|
end
|
172
173
|
|
173
174
|
# add a given address to the +listeners+ set, idempotently
|
174
175
|
# Allows workers to add a private, per-process listener via the
|
175
|
-
#
|
176
|
-
def listen(address, opt = {}.merge(
|
176
|
+
# after_fork hook. Very useful for debugging and testing.
|
177
|
+
def listen(address, opt = {}.merge(listener_opts[address] || {}))
|
177
178
|
return if String === address && listener_names.include?(address)
|
178
179
|
|
179
180
|
delay, tries = 0.5, 5
|
@@ -239,12 +240,12 @@ module Unicorn
|
|
239
240
|
logger.info "SIGWINCH ignored because we're not daemonized"
|
240
241
|
end
|
241
242
|
when :TTIN
|
242
|
-
|
243
|
+
self.worker_processes += 1
|
243
244
|
when :TTOU
|
244
|
-
|
245
|
+
self.worker_processes -= 1 if self.worker_processes > 0
|
245
246
|
when :HUP
|
246
247
|
respawn = true
|
247
|
-
if
|
248
|
+
if config.config_file
|
248
249
|
load_config!
|
249
250
|
redo # immediate reaping since we may have QUIT workers
|
250
251
|
else # exec binary and exit if there's no config file
|
@@ -263,14 +264,14 @@ module Unicorn
|
|
263
264
|
end
|
264
265
|
stop # gracefully shutdown all workers on our way out
|
265
266
|
logger.info "master complete"
|
266
|
-
unlink_pid_safe(
|
267
|
+
unlink_pid_safe(pid) if pid
|
267
268
|
end
|
268
269
|
|
269
270
|
# Terminates all workers, but does not exit master process
|
270
271
|
def stop(graceful = true)
|
271
272
|
self.listeners = []
|
272
273
|
kill_each_worker(graceful ? :QUIT : :TERM)
|
273
|
-
timeleft =
|
274
|
+
timeleft = timeout
|
274
275
|
step = 0.2
|
275
276
|
reap_all_workers
|
276
277
|
until WORKERS.empty?
|
@@ -323,15 +324,15 @@ module Unicorn
|
|
323
324
|
def reap_all_workers
|
324
325
|
begin
|
325
326
|
loop do
|
326
|
-
|
327
|
-
|
328
|
-
if
|
327
|
+
wpid, status = Process.waitpid2(-1, Process::WNOHANG)
|
328
|
+
wpid or break
|
329
|
+
if reexec_pid == wpid
|
329
330
|
logger.error "reaped #{status.inspect} exec()-ed"
|
330
|
-
|
331
|
-
self.pid =
|
331
|
+
self.reexec_pid = 0
|
332
|
+
self.pid = pid.chomp('.oldbin') if pid
|
332
333
|
proc_name 'master'
|
333
334
|
else
|
334
|
-
worker = WORKERS.delete(
|
335
|
+
worker = WORKERS.delete(wpid) and worker.tempfile.close rescue nil
|
335
336
|
logger.info "reaped #{status.inspect} " \
|
336
337
|
"worker=#{worker.nr rescue 'unknown'}"
|
337
338
|
end
|
@@ -342,19 +343,19 @@ module Unicorn
|
|
342
343
|
|
343
344
|
# reexecutes the START_CTX with a new binary
|
344
345
|
def reexec
|
345
|
-
if
|
346
|
+
if reexec_pid > 0
|
346
347
|
begin
|
347
|
-
Process.kill(0,
|
348
|
-
logger.error "reexec-ed child already running PID:#{
|
348
|
+
Process.kill(0, reexec_pid)
|
349
|
+
logger.error "reexec-ed child already running PID:#{reexec_pid}"
|
349
350
|
return
|
350
351
|
rescue Errno::ESRCH
|
351
|
-
|
352
|
+
reexec_pid = 0
|
352
353
|
end
|
353
354
|
end
|
354
355
|
|
355
|
-
if
|
356
|
-
old_pid = "#{
|
357
|
-
prev_pid =
|
356
|
+
if pid
|
357
|
+
old_pid = "#{pid}.oldbin"
|
358
|
+
prev_pid = pid.dup
|
358
359
|
begin
|
359
360
|
self.pid = old_pid # clear the path for a new pid file
|
360
361
|
rescue ArgumentError
|
@@ -367,11 +368,11 @@ module Unicorn
|
|
367
368
|
end
|
368
369
|
end
|
369
370
|
|
370
|
-
|
371
|
+
self.reexec_pid = fork do
|
371
372
|
listener_fds = LISTENERS.map { |sock| sock.fileno }
|
372
373
|
ENV['UNICORN_FD'] = listener_fds.join(',')
|
373
374
|
Dir.chdir(START_CTX[:cwd])
|
374
|
-
cmd = [ START_CTX[
|
375
|
+
cmd = [ START_CTX[0] ].concat(START_CTX[:argv])
|
375
376
|
|
376
377
|
# avoid leaking FDs we don't know about, but let before_exec
|
377
378
|
# unset FD_CLOEXEC, if anything else in the app eventually
|
@@ -384,38 +385,38 @@ module Unicorn
|
|
384
385
|
io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
385
386
|
end
|
386
387
|
logger.info "executing #{cmd.inspect} (in #{Dir.pwd})"
|
387
|
-
|
388
|
+
before_exec.call(self)
|
388
389
|
exec(*cmd)
|
389
390
|
end
|
390
391
|
proc_name 'master (old)'
|
391
392
|
end
|
392
393
|
|
393
|
-
# forcibly terminate all workers that haven't checked in in
|
394
|
+
# forcibly terminate all workers that haven't checked in in timeout
|
394
395
|
# seconds. The timeout is implemented using an unlinked tempfile
|
395
396
|
# shared between the parent process and each worker. The worker
|
396
397
|
# runs File#chmod to modify the ctime of the tempfile. If the ctime
|
397
|
-
# is stale for
|
398
|
+
# is stale for >timeout seconds, then we'll kill the corresponding
|
398
399
|
# worker.
|
399
400
|
def murder_lazy_workers
|
400
401
|
diff = stat = nil
|
401
|
-
WORKERS.dup.each_pair do |
|
402
|
+
WORKERS.dup.each_pair do |wpid, worker|
|
402
403
|
stat = begin
|
403
404
|
worker.tempfile.stat
|
404
405
|
rescue => e
|
405
|
-
logger.warn "worker=#{worker.nr} PID:#{
|
406
|
-
kill_worker(:QUIT,
|
406
|
+
logger.warn "worker=#{worker.nr} PID:#{wpid} stat error: #{e.inspect}"
|
407
|
+
kill_worker(:QUIT, wpid)
|
407
408
|
next
|
408
409
|
end
|
409
410
|
stat.mode == 0100000 and next
|
410
|
-
(diff = (Time.now - stat.ctime)) <=
|
411
|
-
logger.error "worker=#{worker.nr} PID:#{
|
412
|
-
"(#{diff}s > #{
|
413
|
-
kill_worker(:KILL,
|
411
|
+
(diff = (Time.now - stat.ctime)) <= timeout and next
|
412
|
+
logger.error "worker=#{worker.nr} PID:#{wpid} timeout " \
|
413
|
+
"(#{diff}s > #{timeout}s), killing"
|
414
|
+
kill_worker(:KILL, wpid) # take no prisoners for timeout violations
|
414
415
|
end
|
415
416
|
end
|
416
417
|
|
417
418
|
def spawn_missing_workers
|
418
|
-
(0
|
419
|
+
(0...worker_processes).each do |worker_nr|
|
419
420
|
WORKERS.values.include?(worker_nr) and next
|
420
421
|
begin
|
421
422
|
Dir.chdir(START_CTX[:cwd])
|
@@ -427,23 +428,23 @@ module Unicorn
|
|
427
428
|
tempfile = Tempfile.new(nil) # as short as possible to save dir space
|
428
429
|
tempfile.unlink # don't allow other processes to find or see it
|
429
430
|
worker = Worker.new(worker_nr, tempfile)
|
430
|
-
|
431
|
-
|
432
|
-
WORKERS[pid] = worker
|
431
|
+
before_fork.call(self, worker)
|
432
|
+
WORKERS[fork { worker_loop(worker) }] = worker
|
433
433
|
end
|
434
434
|
end
|
435
435
|
|
436
436
|
def maintain_worker_count
|
437
|
-
(off = WORKERS.size -
|
437
|
+
(off = WORKERS.size - worker_processes) == 0 and return
|
438
438
|
off < 0 and return spawn_missing_workers
|
439
|
-
WORKERS.dup.each_pair { |
|
440
|
-
w.nr >=
|
439
|
+
WORKERS.dup.each_pair { |wpid,w|
|
440
|
+
w.nr >= worker_processes and kill_worker(:QUIT, wpid) rescue nil
|
441
441
|
}
|
442
442
|
end
|
443
443
|
|
444
444
|
# once a client is accepted, it is processed in its entirety here
|
445
445
|
# in 3 easy steps: read request, call app, write app response
|
446
|
-
def process_client(
|
446
|
+
def process_client(client)
|
447
|
+
client.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
447
448
|
response = app.call(env = REQUEST.read(client))
|
448
449
|
|
449
450
|
if 100 == response.first.to_i
|
@@ -472,10 +473,10 @@ module Unicorn
|
|
472
473
|
|
473
474
|
# gets rid of stuff the worker has no business keeping track of
|
474
475
|
# to free some resources and drops all sig handlers.
|
475
|
-
# traps for USR1, USR2, and HUP may be set in the
|
476
|
+
# traps for USR1, USR2, and HUP may be set in the after_fork Proc
|
476
477
|
# by the user.
|
477
478
|
def init_worker_process(worker)
|
478
|
-
QUEUE_SIGS.each { |sig| trap(sig,
|
479
|
+
QUEUE_SIGS.each { |sig| trap(sig, nil) }
|
479
480
|
trap(:CHLD, 'DEFAULT')
|
480
481
|
SIG_QUEUE.clear
|
481
482
|
proc_name "worker[#{worker.nr}]"
|
@@ -485,15 +486,15 @@ module Unicorn
|
|
485
486
|
WORKERS.clear
|
486
487
|
LISTENERS.each { |sock| sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
|
487
488
|
worker.tempfile.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
488
|
-
|
489
|
-
|
490
|
-
build_app! unless
|
489
|
+
after_fork.call(self, worker) # can drop perms
|
490
|
+
self.timeout /= 2.0 # halve it for select()
|
491
|
+
build_app! unless preload_app
|
491
492
|
end
|
492
493
|
|
493
494
|
def reopen_worker_logs(worker_nr)
|
494
|
-
|
495
|
+
logger.info "worker=#{worker_nr} reopening logs..."
|
495
496
|
Unicorn::Util.reopen_logs
|
496
|
-
|
497
|
+
logger.info "worker=#{worker_nr} done reopening logs"
|
497
498
|
init_self_pipe!
|
498
499
|
end
|
499
500
|
|
@@ -501,7 +502,7 @@ module Unicorn
|
|
501
502
|
# for connections and doesn't die until the parent dies (or is
|
502
503
|
# given a INT, QUIT, or TERM signal)
|
503
504
|
def worker_loop(worker)
|
504
|
-
|
505
|
+
ppid = master_pid
|
505
506
|
init_worker_process(worker)
|
506
507
|
nr = 0 # this becomes negative if we need to reopen logs
|
507
508
|
alive = worker.tempfile # tempfile is our lifeline to the master process
|
@@ -512,14 +513,13 @@ module Unicorn
|
|
512
513
|
trap(:USR1) { nr = -65536; SELF_PIPE.first.close rescue nil }
|
513
514
|
trap(:QUIT) { alive = nil; LISTENERS.each { |s| s.close rescue nil } }
|
514
515
|
[:TERM, :INT].each { |sig| trap(sig) { exit!(0) } } # instant shutdown
|
515
|
-
|
516
|
-
app = @app
|
516
|
+
logger.info "worker=#{worker.nr} ready"
|
517
517
|
|
518
518
|
begin
|
519
519
|
nr < 0 and reopen_worker_logs(worker.nr)
|
520
520
|
nr = 0
|
521
521
|
|
522
|
-
# we're a goner in
|
522
|
+
# we're a goner in timeout seconds anyways if alive.chmod
|
523
523
|
# breaks, so don't trap the exception. Using fchmod() since
|
524
524
|
# futimes() is not available in base Ruby and I very strongly
|
525
525
|
# prefer temporary files to be unlinked for security,
|
@@ -531,7 +531,7 @@ module Unicorn
|
|
531
531
|
|
532
532
|
ready.each do |sock|
|
533
533
|
begin
|
534
|
-
process_client(
|
534
|
+
process_client(sock.accept_nonblock)
|
535
535
|
nr += 1
|
536
536
|
t == (ti = Time.now.to_i) or alive.chmod(t = ti)
|
537
537
|
rescue Errno::EAGAIN, Errno::ECONNABORTED
|
@@ -545,11 +545,11 @@ module Unicorn
|
|
545
545
|
# before we sleep again in select().
|
546
546
|
redo unless nr == 0 # (nr < 0) => reopen logs
|
547
547
|
|
548
|
-
|
548
|
+
ppid == Process.ppid or return
|
549
549
|
alive.chmod(t = 0)
|
550
550
|
begin
|
551
551
|
# timeout used so we can detect parent death:
|
552
|
-
ret = IO.select(LISTENERS, nil, SELF_PIPE,
|
552
|
+
ret = IO.select(LISTENERS, nil, SELF_PIPE, timeout) or redo
|
553
553
|
ready = ret.first
|
554
554
|
rescue Errno::EINTR
|
555
555
|
ready = LISTENERS
|
@@ -566,17 +566,17 @@ module Unicorn
|
|
566
566
|
|
567
567
|
# delivers a signal to a worker and fails gracefully if the worker
|
568
568
|
# is no longer running.
|
569
|
-
def kill_worker(signal,
|
569
|
+
def kill_worker(signal, wpid)
|
570
570
|
begin
|
571
|
-
Process.kill(signal,
|
571
|
+
Process.kill(signal, wpid)
|
572
572
|
rescue Errno::ESRCH
|
573
|
-
worker = WORKERS.delete(
|
573
|
+
worker = WORKERS.delete(wpid) and worker.tempfile.close rescue nil
|
574
574
|
end
|
575
575
|
end
|
576
576
|
|
577
577
|
# delivers a signal to each worker
|
578
578
|
def kill_each_worker(signal)
|
579
|
-
WORKERS.keys.each { |
|
579
|
+
WORKERS.keys.each { |wpid| kill_worker(signal, wpid) }
|
580
580
|
end
|
581
581
|
|
582
582
|
# unlinks a PID file at given +path+ if it contains the current PID
|
@@ -588,10 +588,10 @@ module Unicorn
|
|
588
588
|
# returns a PID if a given path contains a non-stale PID file,
|
589
589
|
# nil otherwise.
|
590
590
|
def valid_pid?(path)
|
591
|
-
if File.exist?(path) && (
|
591
|
+
if File.exist?(path) && (wpid = File.read(path).to_i) > 1
|
592
592
|
begin
|
593
|
-
Process.kill(0,
|
594
|
-
return
|
593
|
+
Process.kill(0, wpid)
|
594
|
+
return wpid
|
595
595
|
rescue Errno::ESRCH
|
596
596
|
end
|
597
597
|
end
|
@@ -600,17 +600,17 @@ module Unicorn
|
|
600
600
|
|
601
601
|
def load_config!
|
602
602
|
begin
|
603
|
-
logger.info "reloading config_file=#{
|
604
|
-
|
605
|
-
|
606
|
-
|
603
|
+
logger.info "reloading config_file=#{config.config_file}"
|
604
|
+
config[:listeners].replace(init_listeners)
|
605
|
+
config.reload
|
606
|
+
config.commit!(self)
|
607
607
|
kill_each_worker(:QUIT)
|
608
608
|
Unicorn::Util.reopen_logs
|
609
|
-
|
610
|
-
build_app! if
|
611
|
-
logger.info "done reloading config_file=#{
|
609
|
+
self.app = orig_app
|
610
|
+
build_app! if preload_app
|
611
|
+
logger.info "done reloading config_file=#{config.config_file}"
|
612
612
|
rescue Object => e
|
613
|
-
logger.error "error reloading config_file=#{
|
613
|
+
logger.error "error reloading config_file=#{config.config_file}: " \
|
614
614
|
"#{e.class} #{e.message}"
|
615
615
|
end
|
616
616
|
end
|
@@ -621,18 +621,18 @@ module Unicorn
|
|
621
621
|
end
|
622
622
|
|
623
623
|
def build_app!
|
624
|
-
if
|
624
|
+
if app.respond_to?(:arity) && app.arity == 0
|
625
625
|
if defined?(Gem) && Gem.respond_to?(:refresh)
|
626
626
|
logger.info "Refreshing Gem list"
|
627
627
|
Gem.refresh
|
628
628
|
end
|
629
|
-
|
629
|
+
self.app = app.call
|
630
630
|
end
|
631
631
|
end
|
632
632
|
|
633
633
|
def proc_name(tag)
|
634
|
-
$0 = ([ File.basename(START_CTX[
|
635
|
-
|
634
|
+
$0 = ([ File.basename(START_CTX[0]), tag
|
635
|
+
]).concat(START_CTX[:argv]).join(' ')
|
636
636
|
end
|
637
637
|
|
638
638
|
def redirect_io(io, path)
|
data/lib/unicorn/app/exec_cgi.rb
CHANGED
@@ -5,7 +5,7 @@ module Unicorn::App
|
|
5
5
|
|
6
6
|
# This class is highly experimental (even more so than the rest of Unicorn)
|
7
7
|
# and has never run anything other than cgit.
|
8
|
-
class ExecCgi
|
8
|
+
class ExecCgi < Struct.new(:args)
|
9
9
|
|
10
10
|
CHUNK_SIZE = 16384
|
11
11
|
PASS_VARS = %w(
|
@@ -32,12 +32,12 @@ module Unicorn::App
|
|
32
32
|
# run Unicorn::App::ExecCgi.new("/path/to/cgit.cgi")
|
33
33
|
# end
|
34
34
|
def initialize(*args)
|
35
|
-
|
36
|
-
first =
|
35
|
+
self.args = args
|
36
|
+
first = args[0] or
|
37
37
|
raise ArgumentError, "need path to executable"
|
38
|
-
first[0..0] == "/" or
|
39
|
-
File.executable?(
|
40
|
-
raise ArgumentError, "#{
|
38
|
+
first[0..0] == "/" or args[0] = ::File.expand_path(first)
|
39
|
+
File.executable?(args[0]) or
|
40
|
+
raise ArgumentError, "#{args[0]} is not executable"
|
41
41
|
end
|
42
42
|
|
43
43
|
# Calls the app
|
@@ -65,14 +65,14 @@ module Unicorn::App
|
|
65
65
|
val = env[key] or next
|
66
66
|
ENV[key] = val
|
67
67
|
end
|
68
|
-
ENV['SCRIPT_NAME'] =
|
68
|
+
ENV['SCRIPT_NAME'] = args[0]
|
69
69
|
ENV['GATEWAY_INTERFACE'] = 'CGI/1.1'
|
70
70
|
env.keys.grep(/^HTTP_/) { |key| ENV[key] = env[key] }
|
71
71
|
|
72
72
|
a = IO.new(0).reopen(inp)
|
73
73
|
b = IO.new(1).reopen(out)
|
74
74
|
c = IO.new(2).reopen(err)
|
75
|
-
exec(
|
75
|
+
exec(*args)
|
76
76
|
end
|
77
77
|
|
78
78
|
# Extracts headers from CGI out, will change the offset of out.
|
@@ -89,23 +89,24 @@ module Unicorn::App
|
|
89
89
|
offset = 4
|
90
90
|
end
|
91
91
|
offset += head.length
|
92
|
-
out.instance_variable_set('@unicorn_app_exec_cgi_offset', offset)
|
93
|
-
size -= offset
|
94
92
|
|
95
93
|
# Allows +out+ to be used as a Rack body.
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
94
|
+
out.instance_eval { class << self; self; end }.instance_eval {
|
95
|
+
define_method(:each) { |&blk|
|
96
|
+
sysseek(offset)
|
97
|
+
|
98
|
+
# don't use a preallocated buffer for sysread since we can't
|
99
|
+
# guarantee an actual socket is consuming the yielded string
|
100
|
+
# (or if somebody is pushing to an array for eventual concatenation
|
101
|
+
begin
|
102
|
+
blk.call(sysread(CHUNK_SIZE))
|
103
|
+
rescue EOFError
|
104
|
+
break
|
105
|
+
end while true
|
106
|
+
}
|
107
|
+
}
|
108
108
|
|
109
|
+
size -= offset
|
109
110
|
prev = nil
|
110
111
|
headers = Rack::Utils::HeaderHash.new
|
111
112
|
head.split(/\r?\n/).each do |line|
|
@@ -144,7 +145,7 @@ module Unicorn::App
|
|
144
145
|
err.seek(0)
|
145
146
|
dst = env['rack.errors']
|
146
147
|
pid = status.pid
|
147
|
-
dst.write("#{pid}: #{
|
148
|
+
dst.write("#{pid}: #{args.inspect} status=#{status} stderr:\n")
|
148
149
|
err.each_line { |line| dst.write("#{pid}: #{line}") }
|
149
150
|
dst.flush
|
150
151
|
end
|
data/lib/unicorn/app/inetd.rb
CHANGED
@@ -4,65 +4,66 @@
|
|
4
4
|
# this class *must* be used with Rack::Chunked
|
5
5
|
|
6
6
|
module Unicorn::App
|
7
|
-
class Inetd
|
7
|
+
class Inetd < Struct.new(:cmd)
|
8
8
|
|
9
|
-
class CatBody
|
9
|
+
class CatBody < Struct.new(:errors, :err_rd, :out_rd, :pid_map)
|
10
10
|
def initialize(env, cmd)
|
11
|
-
|
12
|
-
@input, @errors = env['rack.input'], env['rack.errors']
|
11
|
+
self.errors = env['rack.errors']
|
13
12
|
in_rd, in_wr = IO.pipe
|
14
|
-
|
15
|
-
|
13
|
+
self.err_rd, err_wr = IO.pipe
|
14
|
+
self.out_rd, out_wr = IO.pipe
|
16
15
|
|
17
|
-
|
16
|
+
cmd_pid = fork {
|
18
17
|
inp, out, err = (0..2).map { |i| IO.new(i) }
|
19
18
|
inp.reopen(in_rd)
|
20
19
|
out.reopen(out_wr)
|
21
20
|
err.reopen(err_wr)
|
22
|
-
[ in_rd, in_wr,
|
23
|
-
io.close
|
24
|
-
}
|
21
|
+
[ in_rd, in_wr, err_rd, err_wr, out_rd, out_wr ].each { |i| i.close }
|
25
22
|
exec(*cmd)
|
26
23
|
}
|
27
24
|
[ in_rd, err_wr, out_wr ].each { |io| io.close }
|
28
|
-
[ in_wr,
|
25
|
+
[ in_wr, err_rd, out_rd ].each { |io| io.binmode }
|
29
26
|
in_wr.sync = true
|
30
27
|
|
31
28
|
# Unfortunately, input here must be processed inside a seperate
|
32
29
|
# thread/process using blocking I/O since env['rack.input'] is not
|
33
30
|
# IO.select-able and attempting to make it so would trip Rack::Lint
|
34
|
-
|
35
|
-
|
31
|
+
inp_pid = fork {
|
32
|
+
input = env['rack.input']
|
33
|
+
[ err_rd, out_rd ].each { |io| io.close }
|
36
34
|
buf = Unicorn::Z.dup
|
37
35
|
|
38
|
-
# this is dependent on
|
39
|
-
while
|
36
|
+
# this is dependent on input.read having readpartial semantics:
|
37
|
+
while input.read(16384, buf)
|
40
38
|
in_wr.write(buf)
|
41
39
|
end
|
42
40
|
in_wr.close
|
43
41
|
}
|
44
42
|
in_wr.close
|
43
|
+
self.pid_map = {
|
44
|
+
inp_pid => 'input streamer',
|
45
|
+
cmd_pid => cmd.inspect,
|
46
|
+
}
|
45
47
|
end
|
46
48
|
|
47
49
|
def each(&block)
|
48
|
-
buf = Unicorn::Z.dup
|
49
50
|
begin
|
50
|
-
rd, = IO.select([
|
51
|
+
rd, = IO.select([err_rd, out_rd])
|
51
52
|
rd && rd.first or next
|
52
53
|
|
53
|
-
if rd.include?(
|
54
|
+
if rd.include?(err_rd)
|
54
55
|
begin
|
55
|
-
|
56
|
+
errors.write(err_rd.read_nonblock(16384))
|
56
57
|
rescue Errno::EINTR
|
57
58
|
rescue Errno::EAGAIN
|
58
59
|
break
|
59
60
|
end while true
|
60
61
|
end
|
61
62
|
|
62
|
-
rd.include?(
|
63
|
+
rd.include?(out_rd) or next
|
63
64
|
|
64
65
|
begin
|
65
|
-
yield
|
66
|
+
yield out_rd.read_nonblock(16384)
|
66
67
|
rescue Errno::EINTR
|
67
68
|
rescue Errno::EAGAIN
|
68
69
|
break
|
@@ -75,15 +76,13 @@ module Unicorn::App
|
|
75
76
|
end
|
76
77
|
|
77
78
|
def close
|
78
|
-
|
79
|
-
[ [ @cmd.inspect, @cmd_pid ], [ 'input streamer', @inp_pid ]
|
80
|
-
].each { |str, pid|
|
79
|
+
pid_map.each { |pid, str|
|
81
80
|
begin
|
82
81
|
pid, status = Process.waitpid2(pid)
|
83
82
|
status.success? or
|
84
|
-
|
83
|
+
errors.write("#{str}: #{status.inspect} (PID:#{pid})\n")
|
85
84
|
rescue Errno::ECHILD
|
86
|
-
|
85
|
+
errors.write("Failed to reap #{str} (PID:#{pid})\n")
|
87
86
|
end
|
88
87
|
}
|
89
88
|
end
|
@@ -91,16 +90,15 @@ module Unicorn::App
|
|
91
90
|
end
|
92
91
|
|
93
92
|
def initialize(*cmd)
|
94
|
-
|
93
|
+
self.cmd = cmd
|
95
94
|
end
|
96
95
|
|
97
96
|
def call(env)
|
98
|
-
|
99
|
-
/\A100-continue\z/i =~ expect and
|
97
|
+
/\A100-continue\z/i =~ env[Unicorn::Const::HTTP_EXPECT] and
|
100
98
|
return [ 100, {} , [] ]
|
101
99
|
|
102
100
|
[ 200, { 'Content-Type' => 'application/octet-stream' },
|
103
|
-
CatBody.new(env,
|
101
|
+
CatBody.new(env, cmd) ]
|
104
102
|
end
|
105
103
|
|
106
104
|
end
|
@@ -19,28 +19,28 @@ require 'rack/file'
|
|
19
19
|
# This means that if you are using page caching it will actually work
|
20
20
|
# with Unicorn and you should see a decent speed boost (but not as
|
21
21
|
# fast as if you use a static server like nginx).
|
22
|
-
class Unicorn::App::OldRails::Static
|
22
|
+
class Unicorn::App::OldRails::Static < Struct.new(:app, :root, :file_server)
|
23
23
|
FILE_METHODS = { 'GET' => true, 'HEAD' => true }.freeze
|
24
24
|
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
25
25
|
REQUEST_URI = 'REQUEST_URI'.freeze
|
26
26
|
PATH_INFO = 'PATH_INFO'.freeze
|
27
27
|
|
28
28
|
def initialize(app)
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
self.app = app
|
30
|
+
self.root = "#{::RAILS_ROOT}/public"
|
31
|
+
self.file_server = ::Rack::File.new(root)
|
32
32
|
end
|
33
33
|
|
34
34
|
def call(env)
|
35
35
|
# short circuit this ASAP if serving non-file methods
|
36
|
-
FILE_METHODS.include?(env[REQUEST_METHOD]) or return
|
36
|
+
FILE_METHODS.include?(env[REQUEST_METHOD]) or return app.call(env)
|
37
37
|
|
38
38
|
# first try the path as-is
|
39
39
|
path_info = env[PATH_INFO].chomp("/")
|
40
|
-
if File.file?("
|
40
|
+
if File.file?("#{root}/#{::Rack::Utils.unescape(path_info)}")
|
41
41
|
# File exists as-is so serve it up
|
42
42
|
env[PATH_INFO] = path_info
|
43
|
-
return
|
43
|
+
return file_server.call(env)
|
44
44
|
end
|
45
45
|
|
46
46
|
# then try the cached version:
|
@@ -50,11 +50,11 @@ class Unicorn::App::OldRails::Static
|
|
50
50
|
env[REQUEST_URI] =~ /^#{Regexp.escape(path_info)}(;[^\?]+)/
|
51
51
|
path_info << "#$1#{ActionController::Base.page_cache_extension}"
|
52
52
|
|
53
|
-
if File.file?("
|
53
|
+
if File.file?("#{root}/#{::Rack::Utils.unescape(path_info)}")
|
54
54
|
env[PATH_INFO] = path_info
|
55
|
-
return
|
55
|
+
return file_server.call(env)
|
56
56
|
end
|
57
57
|
|
58
|
-
|
58
|
+
app.call(env) # call OldRails
|
59
59
|
end
|
60
60
|
end if defined?(Unicorn::App::OldRails)
|
data/lib/unicorn/configurator.rb
CHANGED
@@ -14,14 +14,13 @@ module Unicorn
|
|
14
14
|
# after_fork do |server,worker|
|
15
15
|
# server.listen("127.0.0.1:#{9293 + worker.nr}") rescue nil
|
16
16
|
# end
|
17
|
-
class Configurator
|
17
|
+
class Configurator < Struct.new(:set, :config_file)
|
18
18
|
# The default logger writes its output to $stderr
|
19
|
-
DEFAULT_LOGGER = Logger.new($stderr)
|
19
|
+
DEFAULT_LOGGER = Logger.new($stderr)
|
20
20
|
|
21
21
|
# Default settings for Unicorn
|
22
22
|
DEFAULTS = {
|
23
23
|
:timeout => 60,
|
24
|
-
:listeners => [],
|
25
24
|
:logger => DEFAULT_LOGGER,
|
26
25
|
:worker_processes => 1,
|
27
26
|
:after_fork => lambda { |server, worker|
|
@@ -42,42 +41,32 @@ module Unicorn
|
|
42
41
|
},
|
43
42
|
:pid => nil,
|
44
43
|
:preload_app => false,
|
45
|
-
:stderr_path => nil,
|
46
|
-
:stdout_path => nil,
|
47
44
|
}
|
48
45
|
|
49
|
-
attr_reader :config_file #:nodoc:
|
50
|
-
|
51
46
|
def initialize(defaults = {}) #:nodoc:
|
52
|
-
|
47
|
+
self.set = Hash.new(:unset)
|
53
48
|
use_defaults = defaults.delete(:use_defaults)
|
54
|
-
|
55
|
-
|
56
|
-
@set.merge!(DEFAULTS) if use_defaults
|
49
|
+
self.config_file = defaults.delete(:config_file)
|
50
|
+
set.merge!(DEFAULTS) if use_defaults
|
57
51
|
defaults.each { |key, value| self.send(key, value) }
|
58
52
|
reload
|
59
53
|
end
|
60
54
|
|
61
55
|
def reload #:nodoc:
|
62
|
-
instance_eval(File.read(
|
56
|
+
instance_eval(File.read(config_file)) if config_file
|
63
57
|
end
|
64
58
|
|
65
59
|
def commit!(server, options = {}) #:nodoc:
|
66
60
|
skip = options[:skip] || []
|
67
|
-
|
61
|
+
set.each do |key, value|
|
68
62
|
value == :unset and next
|
69
63
|
skip.include?(key) and next
|
70
|
-
|
71
|
-
if server.respond_to?(setter)
|
72
|
-
server.send(setter, value)
|
73
|
-
else
|
74
|
-
server.instance_variable_set("@#{key}", value)
|
75
|
-
end
|
64
|
+
server.__send__("#{key}=", value)
|
76
65
|
end
|
77
66
|
end
|
78
67
|
|
79
68
|
def [](key) # :nodoc:
|
80
|
-
|
69
|
+
set[key]
|
81
70
|
end
|
82
71
|
|
83
72
|
# sets object to the +new+ Logger-like object. The new logger-like
|
@@ -89,7 +78,7 @@ module Unicorn
|
|
89
78
|
raise ArgumentError, "logger=#{new} does not respond to method=#{m}"
|
90
79
|
end
|
91
80
|
|
92
|
-
|
81
|
+
set[:logger] = new
|
93
82
|
end
|
94
83
|
|
95
84
|
# sets after_fork hook to a given block. This block will be called by
|
@@ -151,7 +140,7 @@ module Unicorn
|
|
151
140
|
"not numeric: timeout=#{seconds.inspect}"
|
152
141
|
seconds >= 3 or raise ArgumentError,
|
153
142
|
"too low: timeout=#{seconds.inspect}"
|
154
|
-
|
143
|
+
set[:timeout] = seconds
|
155
144
|
end
|
156
145
|
|
157
146
|
# sets the current number of worker_processes to +nr+. Each worker
|
@@ -161,7 +150,7 @@ module Unicorn
|
|
161
150
|
"not an integer: worker_processes=#{nr.inspect}"
|
162
151
|
nr >= 0 or raise ArgumentError,
|
163
152
|
"not non-negative: worker_processes=#{nr.inspect}"
|
164
|
-
|
153
|
+
set[:worker_processes] = nr
|
165
154
|
end
|
166
155
|
|
167
156
|
# sets listeners to the given +addresses+, replacing or augmenting the
|
@@ -172,7 +161,7 @@ module Unicorn
|
|
172
161
|
def listeners(addresses) # :nodoc:
|
173
162
|
Array === addresses or addresses = Array(addresses)
|
174
163
|
addresses.map! { |addr| expand_addr(addr) }
|
175
|
-
|
164
|
+
set[:listeners] = addresses
|
176
165
|
end
|
177
166
|
|
178
167
|
# adds an +address+ to the existing listener set.
|
@@ -227,8 +216,8 @@ module Unicorn
|
|
227
216
|
def listen(address, opt = {})
|
228
217
|
address = expand_addr(address)
|
229
218
|
if String === address
|
230
|
-
Hash ===
|
231
|
-
|
219
|
+
Hash === set[:listener_opts] or
|
220
|
+
set[:listener_opts] = Hash.new { |hash,key| hash[key] = {} }
|
232
221
|
[ :backlog, :sndbuf, :rcvbuf ].each do |key|
|
233
222
|
value = opt[key] or next
|
234
223
|
Integer === value or
|
@@ -239,11 +228,11 @@ module Unicorn
|
|
239
228
|
TrueClass === value || FalseClass === value or
|
240
229
|
raise ArgumentError, "not boolean: #{key}=#{value.inspect}"
|
241
230
|
end
|
242
|
-
|
231
|
+
set[:listener_opts][address].merge!(opt)
|
243
232
|
end
|
244
233
|
|
245
|
-
|
246
|
-
|
234
|
+
set[:listeners] = [] unless Array === set[:listeners]
|
235
|
+
set[:listeners] << address
|
247
236
|
end
|
248
237
|
|
249
238
|
# sets the +path+ for the PID file of the unicorn master process
|
@@ -265,7 +254,7 @@ module Unicorn
|
|
265
254
|
def preload_app(bool)
|
266
255
|
case bool
|
267
256
|
when TrueClass, FalseClass
|
268
|
-
|
257
|
+
set[:preload_app] = bool
|
269
258
|
else
|
270
259
|
raise ArgumentError, "preload_app=#{bool.inspect} not a boolean"
|
271
260
|
end
|
@@ -298,7 +287,7 @@ module Unicorn
|
|
298
287
|
else
|
299
288
|
raise ArgumentError
|
300
289
|
end
|
301
|
-
|
290
|
+
set[var] = path
|
302
291
|
end
|
303
292
|
|
304
293
|
def set_hook(var, my_proc, req_arity = 2) #:nodoc:
|
@@ -314,7 +303,7 @@ module Unicorn
|
|
314
303
|
else
|
315
304
|
raise ArgumentError, "invalid type: #{var}=#{my_proc.inspect}"
|
316
305
|
end
|
317
|
-
|
306
|
+
set[var] = my_proc
|
318
307
|
end
|
319
308
|
|
320
309
|
# expands "unix:path/to/foo" to a socket relative to the current path
|
data/lib/unicorn/const.rb
CHANGED
@@ -5,7 +5,7 @@ module Unicorn
|
|
5
5
|
# gave about a 3% to 10% performance improvement over using the strings directly.
|
6
6
|
# Symbols did not really improve things much compared to constants.
|
7
7
|
module Const
|
8
|
-
UNICORN_VERSION="0.9.
|
8
|
+
UNICORN_VERSION="0.9.1".freeze
|
9
9
|
|
10
10
|
DEFAULT_HOST = "0.0.0.0".freeze # default TCP listen host address
|
11
11
|
DEFAULT_PORT = "8080".freeze # default TCP listen port
|
data/lib/unicorn/http_request.rb
CHANGED
@@ -6,8 +6,6 @@ require 'unicorn/http11'
|
|
6
6
|
module Unicorn
|
7
7
|
class HttpRequest
|
8
8
|
|
9
|
-
attr_accessor :logger
|
10
|
-
|
11
9
|
# default parameters we merge into the request env for Rack handlers
|
12
10
|
DEFAULTS = {
|
13
11
|
"rack.errors" => $stderr,
|
@@ -24,6 +22,9 @@ module Unicorn
|
|
24
22
|
NULL_IO = StringIO.new(Z)
|
25
23
|
LOCALHOST = '127.0.0.1'.freeze
|
26
24
|
|
25
|
+
def initialize
|
26
|
+
end
|
27
|
+
|
27
28
|
# Being explicitly single-threaded, we have certain advantages in
|
28
29
|
# not having to worry about variables being clobbered :)
|
29
30
|
BUFFER = ' ' * Const::CHUNK_SIZE # initial size, may grow
|
@@ -31,10 +32,6 @@ module Unicorn
|
|
31
32
|
PARSER = HttpParser.new
|
32
33
|
PARAMS = Hash.new
|
33
34
|
|
34
|
-
def initialize(logger = Configurator::DEFAULT_LOGGER)
|
35
|
-
@logger = logger
|
36
|
-
end
|
37
|
-
|
38
35
|
# Does the majority of the IO processing. It has been written in
|
39
36
|
# Ruby using about 8 different IO processing strategies.
|
40
37
|
#
|
@@ -74,13 +71,6 @@ module Unicorn
|
|
74
71
|
data << socket.readpartial(Const::CHUNK_SIZE, BUFFER)
|
75
72
|
PARSER.execute(PARAMS, data) and return handle_body(socket)
|
76
73
|
end while true
|
77
|
-
rescue HttpParserError => e
|
78
|
-
@logger.error "HTTP parse error, malformed request " \
|
79
|
-
"(#{PARAMS[Const::HTTP_X_FORWARDED_FOR] ||
|
80
|
-
PARAMS[Const::REMOTE_ADDR]}): #{e.inspect}"
|
81
|
-
@logger.error "REQUEST DATA: #{data.inspect}\n---\n" \
|
82
|
-
"PARAMS: #{PARAMS.inspect}\n---\n"
|
83
|
-
raise e
|
84
74
|
end
|
85
75
|
|
86
76
|
private
|
@@ -2,6 +2,8 @@ require 'test/unit'
|
|
2
2
|
require 'tempfile'
|
3
3
|
require 'unicorn'
|
4
4
|
|
5
|
+
TestStruct = Struct.new(
|
6
|
+
*(Unicorn::Configurator::DEFAULTS.keys + %w(listener_opts listeners)))
|
5
7
|
class TestConfigurator < Test::Unit::TestCase
|
6
8
|
|
7
9
|
def test_config_init
|
@@ -51,22 +53,23 @@ class TestConfigurator < Test::Unit::TestCase
|
|
51
53
|
|
52
54
|
def test_config_defaults
|
53
55
|
cfg = Unicorn::Configurator.new(:use_defaults => true)
|
54
|
-
|
56
|
+
test_struct = TestStruct.new
|
57
|
+
assert_nothing_raised { cfg.commit!(test_struct) }
|
55
58
|
Unicorn::Configurator::DEFAULTS.each do |key,value|
|
56
|
-
assert_equal value,
|
59
|
+
assert_equal value, test_struct.__send__(key)
|
57
60
|
end
|
58
61
|
end
|
59
62
|
|
60
63
|
def test_config_defaults_skip
|
61
64
|
cfg = Unicorn::Configurator.new(:use_defaults => true)
|
62
65
|
skip = [ :logger ]
|
63
|
-
|
64
|
-
|
66
|
+
test_struct = TestStruct.new
|
67
|
+
assert_nothing_raised { cfg.commit!(test_struct, :skip => skip) }
|
65
68
|
Unicorn::Configurator::DEFAULTS.each do |key,value|
|
66
69
|
next if skip.include?(key)
|
67
|
-
assert_equal value,
|
70
|
+
assert_equal value, test_struct.__send__(key)
|
68
71
|
end
|
69
|
-
assert_nil
|
72
|
+
assert_nil test_struct.logger
|
70
73
|
end
|
71
74
|
|
72
75
|
def test_listen_options
|
@@ -78,8 +81,9 @@ class TestConfigurator < Test::Unit::TestCase
|
|
78
81
|
assert_nothing_raised do
|
79
82
|
cfg = Unicorn::Configurator.new(:config_file => tmp.path)
|
80
83
|
end
|
81
|
-
|
82
|
-
|
84
|
+
test_struct = TestStruct.new
|
85
|
+
assert_nothing_raised { cfg.commit!(test_struct) }
|
86
|
+
assert(listener_opts = test_struct.listener_opts)
|
83
87
|
assert_equal expect, listener_opts[listener]
|
84
88
|
end
|
85
89
|
|
@@ -94,9 +98,10 @@ class TestConfigurator < Test::Unit::TestCase
|
|
94
98
|
end
|
95
99
|
|
96
100
|
def test_after_fork_proc
|
101
|
+
test_struct = TestStruct.new
|
97
102
|
[ proc { |a,b| }, Proc.new { |a,b| }, lambda { |a,b| } ].each do |my_proc|
|
98
|
-
Unicorn::Configurator.new(:after_fork => my_proc).commit!(
|
99
|
-
assert_equal my_proc,
|
103
|
+
Unicorn::Configurator.new(:after_fork => my_proc).commit!(test_struct)
|
104
|
+
assert_equal my_proc, test_struct.after_fork
|
100
105
|
end
|
101
106
|
end
|
102
107
|
|
data/test/unit/test_request.rb
CHANGED
data/unicorn.gemspec
CHANGED
@@ -2,23 +2,22 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{unicorn}
|
5
|
-
s.version = "0.9.
|
5
|
+
s.version = "0.9.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-07-
|
9
|
+
s.date = %q{2009-07-09}
|
10
10
|
s.description = %q{Rack HTTP server for Unix, fast clients and nothing else}
|
11
11
|
s.email = %q{normalperson@yhbt.net}
|
12
12
|
s.executables = ["unicorn", "unicorn_rails"]
|
13
13
|
s.extensions = ["ext/unicorn/http11/extconf.rb"]
|
14
14
|
s.extra_rdoc_files = ["CHANGELOG", "COPYING", "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.h", "ext/unicorn/http11/http11_parser.rl", "ext/unicorn/http11/http11_parser_common.rl", "lib/unicorn.rb", "lib/unicorn/app/exec_cgi.rb", "lib/unicorn/app/inetd.rb", "lib/unicorn/app/old_rails.rb", "lib/unicorn/app/old_rails/static.rb", "lib/unicorn/cgi_wrapper.rb", "lib/unicorn/chunked_reader.rb", "lib/unicorn/configurator.rb", "lib/unicorn/const.rb", "lib/unicorn/http_request.rb", "lib/unicorn/http_response.rb", "lib/unicorn/launcher.rb", "lib/unicorn/socket_helper.rb", "lib/unicorn/tee_input.rb", "lib/unicorn/trailer_parser.rb", "lib/unicorn/util.rb"]
|
15
15
|
s.files = [".document", ".gitignore", "CHANGELOG", "CONTRIBUTORS", "COPYING", "DESIGN", "GNUmakefile", "LICENSE", "Manifest", "PHILOSOPHY", "README", "Rakefile", "SIGNALS", "TODO", "TUNING", "bin/unicorn", "bin/unicorn_rails", "examples/echo.ru", "examples/git.ru", "examples/init.sh", "ext/unicorn/http11/ext_help.h", "ext/unicorn/http11/extconf.rb", "ext/unicorn/http11/http11.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/app/exec_cgi.rb", "lib/unicorn/app/inetd.rb", "lib/unicorn/app/old_rails.rb", "lib/unicorn/app/old_rails/static.rb", "lib/unicorn/cgi_wrapper.rb", "lib/unicorn/chunked_reader.rb", "lib/unicorn/configurator.rb", "lib/unicorn/const.rb", "lib/unicorn/http_request.rb", "lib/unicorn/http_response.rb", "lib/unicorn/launcher.rb", "lib/unicorn/socket_helper.rb", "lib/unicorn/tee_input.rb", "lib/unicorn/trailer_parser.rb", "lib/unicorn/util.rb", "local.mk.sample", "setup.rb", "test/aggregate.rb", "test/benchmark/README", "test/benchmark/big_request.rb", "test/benchmark/dd.ru", "test/benchmark/request.rb", "test/benchmark/response.rb", "test/exec/README", "test/exec/test_exec.rb", "test/rails/app-1.2.3/.gitignore", "test/rails/app-1.2.3/Rakefile", "test/rails/app-1.2.3/app/controllers/application.rb", "test/rails/app-1.2.3/app/controllers/foo_controller.rb", "test/rails/app-1.2.3/app/helpers/application_helper.rb", "test/rails/app-1.2.3/config/boot.rb", "test/rails/app-1.2.3/config/database.yml", "test/rails/app-1.2.3/config/environment.rb", "test/rails/app-1.2.3/config/environments/development.rb", "test/rails/app-1.2.3/config/environments/production.rb", "test/rails/app-1.2.3/config/routes.rb", "test/rails/app-1.2.3/db/.gitignore", "test/rails/app-1.2.3/log/.gitignore", "test/rails/app-1.2.3/public/404.html", "test/rails/app-1.2.3/public/500.html", "test/rails/app-2.0.2/.gitignore", "test/rails/app-2.0.2/Rakefile", "test/rails/app-2.0.2/app/controllers/application.rb", "test/rails/app-2.0.2/app/controllers/foo_controller.rb", "test/rails/app-2.0.2/app/helpers/application_helper.rb", "test/rails/app-2.0.2/config/boot.rb", "test/rails/app-2.0.2/config/database.yml", "test/rails/app-2.0.2/config/environment.rb", "test/rails/app-2.0.2/config/environments/development.rb", "test/rails/app-2.0.2/config/environments/production.rb", "test/rails/app-2.0.2/config/routes.rb", "test/rails/app-2.0.2/db/.gitignore", "test/rails/app-2.0.2/log/.gitignore", "test/rails/app-2.0.2/public/404.html", "test/rails/app-2.0.2/public/500.html", "test/rails/app-2.1.2/.gitignore", "test/rails/app-2.1.2/Rakefile", "test/rails/app-2.1.2/app/controllers/application.rb", "test/rails/app-2.1.2/app/controllers/foo_controller.rb", "test/rails/app-2.1.2/app/helpers/application_helper.rb", "test/rails/app-2.1.2/config/boot.rb", "test/rails/app-2.1.2/config/database.yml", "test/rails/app-2.1.2/config/environment.rb", "test/rails/app-2.1.2/config/environments/development.rb", "test/rails/app-2.1.2/config/environments/production.rb", "test/rails/app-2.1.2/config/routes.rb", "test/rails/app-2.1.2/db/.gitignore", "test/rails/app-2.1.2/log/.gitignore", "test/rails/app-2.1.2/public/404.html", "test/rails/app-2.1.2/public/500.html", "test/rails/app-2.2.2/.gitignore", "test/rails/app-2.2.2/Rakefile", "test/rails/app-2.2.2/app/controllers/application.rb", "test/rails/app-2.2.2/app/controllers/foo_controller.rb", "test/rails/app-2.2.2/app/helpers/application_helper.rb", "test/rails/app-2.2.2/config/boot.rb", "test/rails/app-2.2.2/config/database.yml", "test/rails/app-2.2.2/config/environment.rb", "test/rails/app-2.2.2/config/environments/development.rb", "test/rails/app-2.2.2/config/environments/production.rb", "test/rails/app-2.2.2/config/routes.rb", "test/rails/app-2.2.2/db/.gitignore", "test/rails/app-2.2.2/log/.gitignore", "test/rails/app-2.2.2/public/404.html", "test/rails/app-2.2.2/public/500.html", "test/rails/app-2.3.2.1/.gitignore", "test/rails/app-2.3.2.1/Rakefile", "test/rails/app-2.3.2.1/app/controllers/application_controller.rb", "test/rails/app-2.3.2.1/app/controllers/foo_controller.rb", "test/rails/app-2.3.2.1/app/helpers/application_helper.rb", "test/rails/app-2.3.2.1/config/boot.rb", "test/rails/app-2.3.2.1/config/database.yml", "test/rails/app-2.3.2.1/config/environment.rb", "test/rails/app-2.3.2.1/config/environments/development.rb", "test/rails/app-2.3.2.1/config/environments/production.rb", "test/rails/app-2.3.2.1/config/routes.rb", "test/rails/app-2.3.2.1/db/.gitignore", "test/rails/app-2.3.2.1/log/.gitignore", "test/rails/app-2.3.2.1/public/404.html", "test/rails/app-2.3.2.1/public/500.html", "test/rails/test_rails.rb", "test/test_helper.rb", "test/unit/test_chunked_reader.rb", "test/unit/test_configurator.rb", "test/unit/test_http_parser.rb", "test/unit/test_request.rb", "test/unit/test_response.rb", "test/unit/test_server.rb", "test/unit/test_signals.rb", "test/unit/test_socket_helper.rb", "test/unit/test_trailer_parser.rb", "test/unit/test_upload.rb", "test/unit/test_util.rb", "unicorn.gemspec"]
|
16
|
-
s.has_rdoc = true
|
17
16
|
s.homepage = %q{http://unicorn.bogomips.org/}
|
18
17
|
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Unicorn", "--main", "README"]
|
19
18
|
s.require_paths = ["lib", "ext"]
|
20
19
|
s.rubyforge_project = %q{unicorn}
|
21
|
-
s.rubygems_version = %q{1.3.
|
20
|
+
s.rubygems_version = %q{1.3.4}
|
22
21
|
s.summary = %q{Rack HTTP server for Unix, fast clients and nothing else}
|
23
22
|
s.test_files = ["test/unit/test_trailer_parser.rb", "test/unit/test_configurator.rb", "test/unit/test_chunked_reader.rb", "test/unit/test_response.rb", "test/unit/test_request.rb", "test/unit/test_signals.rb", "test/unit/test_upload.rb", "test/unit/test_http_parser.rb", "test/unit/test_socket_helper.rb", "test/unit/test_util.rb", "test/unit/test_server.rb"]
|
24
23
|
|
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.9.
|
4
|
+
version: 0.9.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-07-
|
12
|
+
date: 2009-07-09 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -231,7 +231,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
231
231
|
requirements: []
|
232
232
|
|
233
233
|
rubyforge_project: unicorn
|
234
|
-
rubygems_version: 1.3.
|
234
|
+
rubygems_version: 1.3.4
|
235
235
|
signing_key:
|
236
236
|
specification_version: 3
|
237
237
|
summary: Rack HTTP server for Unix, fast clients and nothing else
|