unicorn 0.9.0 → 0.9.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 +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
|