unicorn 3.6.0 → 4.0.0
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/.document +1 -0
- data/.manifest +13 -0
- data/ChangeLog +783 -1
- data/DESIGN +0 -8
- data/Documentation/GNUmakefile +1 -1
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +2 -2
- data/HACKING +11 -0
- data/KNOWN_ISSUES +2 -2
- data/LATEST +24 -24
- data/Links +53 -0
- data/NEWS +66 -0
- data/PHILOSOPHY +49 -49
- data/Sandbox +13 -4
- data/TODO +0 -2
- data/TUNING +31 -9
- data/bin/unicorn +2 -1
- data/bin/unicorn_rails +2 -1
- data/examples/big_app_gc.rb +2 -33
- data/examples/nginx.conf +17 -4
- data/ext/unicorn_http/ext_help.h +16 -0
- data/ext/unicorn_http/extconf.rb +1 -0
- data/ext/unicorn_http/global_variables.h +9 -3
- data/ext/unicorn_http/unicorn_http.c +357 -259
- data/ext/unicorn_http/unicorn_http.rl +148 -50
- data/lib/unicorn/configurator.rb +36 -8
- data/lib/unicorn/const.rb +5 -3
- data/lib/unicorn/http_request.rb +1 -3
- data/lib/unicorn/http_server.rb +82 -95
- data/lib/unicorn/oob_gc.rb +61 -50
- data/lib/unicorn/socket_helper.rb +23 -8
- data/lib/unicorn/worker.rb +45 -4
- data/lib/unicorn.rb +8 -6
- data/script/isolate_for_tests +4 -2
- data/t/broken-app.ru +12 -0
- data/t/heartbeat-timeout.ru +12 -0
- data/t/oob_gc.ru +21 -0
- data/t/oob_gc_path.ru +21 -0
- data/t/t0001-reload-bad-config.sh +1 -0
- data/t/t0002-parser-error.sh +64 -1
- data/t/t0004-heartbeat-timeout.sh +69 -0
- data/t/t0009-broken-app.sh +56 -0
- data/t/t0019-max_header_len.sh +49 -0
- data/t/t0020-at_exit-handler.sh +49 -0
- data/t/t9001-oob_gc.sh +47 -0
- data/t/t9002-oob_gc-path.sh +75 -0
- data/test/benchmark/stack.ru +8 -0
- data/test/unit/test_droplet.rb +28 -0
- data/test/unit/test_http_parser.rb +60 -4
- data/test/unit/test_http_parser_ng.rb +54 -0
- data/test/unit/test_response.rb +1 -1
- data/test/unit/test_server.rb +1 -1
- data/test/unit/test_signals.rb +1 -1
- data/test/unit/test_socket_helper.rb +8 -0
- data/test/unit/test_upload.rb +1 -1
- data/unicorn.gemspec +3 -2
- metadata +44 -16
data/lib/unicorn/http_server.rb
CHANGED
@@ -23,9 +23,6 @@ class Unicorn::HttpServer
|
|
23
23
|
# backwards compatibility with 1.x
|
24
24
|
Worker = Unicorn::Worker
|
25
25
|
|
26
|
-
# prevents IO objects in here from being GC-ed
|
27
|
-
IO_PURGATORY = []
|
28
|
-
|
29
26
|
# all bound listener sockets
|
30
27
|
LISTENERS = []
|
31
28
|
|
@@ -100,7 +97,7 @@ class Unicorn::HttpServer
|
|
100
97
|
self.reexec_pid = 0
|
101
98
|
options = options.dup
|
102
99
|
@ready_pipe = options.delete(:ready_pipe)
|
103
|
-
|
100
|
+
@init_listeners = options[:listeners] ? options[:listeners].dup : []
|
104
101
|
options[:use_defaults] = true
|
105
102
|
self.config = Unicorn::Configurator.new(options)
|
106
103
|
self.listener_opts = {}
|
@@ -121,35 +118,7 @@ class Unicorn::HttpServer
|
|
121
118
|
|
122
119
|
# Runs the thing. Returns self so you can run join on it
|
123
120
|
def start
|
124
|
-
|
125
|
-
|
126
|
-
# inherit sockets from parents, they need to be plain Socket objects
|
127
|
-
# before they become Kgio::UNIXServer or Kgio::TCPServer
|
128
|
-
inherited = ENV['UNICORN_FD'].to_s.split(/,/).map do |fd|
|
129
|
-
io = Socket.for_fd(fd.to_i)
|
130
|
-
set_server_sockopt(io, listener_opts[sock_name(io)])
|
131
|
-
IO_PURGATORY << io
|
132
|
-
logger.info "inherited addr=#{sock_name(io)} fd=#{fd}"
|
133
|
-
server_cast(io)
|
134
|
-
end
|
135
|
-
|
136
|
-
config_listeners = config[:listeners].dup
|
137
|
-
LISTENERS.replace(inherited)
|
138
|
-
|
139
|
-
# we start out with generic Socket objects that get cast to either
|
140
|
-
# Kgio::TCPServer or Kgio::UNIXServer objects; but since the Socket
|
141
|
-
# objects share the same OS-level file descriptor as the higher-level
|
142
|
-
# *Server objects; we need to prevent Socket objects from being
|
143
|
-
# garbage-collected
|
144
|
-
config_listeners -= listener_names
|
145
|
-
if config_listeners.empty? && LISTENERS.empty?
|
146
|
-
config_listeners << Unicorn::Const::DEFAULT_LISTEN
|
147
|
-
init_listeners << Unicorn::Const::DEFAULT_LISTEN
|
148
|
-
START_CTX[:argv] << "-l#{Unicorn::Const::DEFAULT_LISTEN}"
|
149
|
-
end
|
150
|
-
config_listeners.each { |addr| listen(addr) }
|
151
|
-
raise ArgumentError, "no listeners" if LISTENERS.empty?
|
152
|
-
|
121
|
+
inherit_listeners!
|
153
122
|
# this pipe is used to wake us up from select(2) in #join when signals
|
154
123
|
# are trapped. See trap_deferred.
|
155
124
|
init_self_pipe!
|
@@ -163,7 +132,7 @@ class Unicorn::HttpServer
|
|
163
132
|
|
164
133
|
self.master_pid = $$
|
165
134
|
build_app! if preload_app
|
166
|
-
|
135
|
+
spawn_missing_workers
|
167
136
|
self
|
168
137
|
end
|
169
138
|
|
@@ -284,8 +253,7 @@ class Unicorn::HttpServer
|
|
284
253
|
logger.info "master process ready" # test_exec.rb relies on this message
|
285
254
|
if @ready_pipe
|
286
255
|
@ready_pipe.syswrite($$.to_s)
|
287
|
-
@ready_pipe.close rescue nil
|
288
|
-
@ready_pipe = nil
|
256
|
+
@ready_pipe = @ready_pipe.close rescue nil
|
289
257
|
end
|
290
258
|
begin
|
291
259
|
reap_all_workers
|
@@ -336,10 +304,8 @@ class Unicorn::HttpServer
|
|
336
304
|
reexec
|
337
305
|
end
|
338
306
|
end
|
339
|
-
rescue Errno::EINTR
|
340
307
|
rescue => e
|
341
|
-
logger
|
342
|
-
logger.error e.backtrace.join("\n")
|
308
|
+
Unicorn.log_error(@logger, "master loop error", e)
|
343
309
|
end while true
|
344
310
|
stop # gracefully shutdown all workers on our way out
|
345
311
|
logger.info "master complete"
|
@@ -406,7 +372,7 @@ class Unicorn::HttpServer
|
|
406
372
|
self.pid = pid.chomp('.oldbin') if pid
|
407
373
|
proc_name 'master'
|
408
374
|
else
|
409
|
-
worker = WORKERS.delete(wpid) and worker.
|
375
|
+
worker = WORKERS.delete(wpid) and worker.close rescue nil
|
410
376
|
m = "reaped #{status.inspect} worker=#{worker.nr rescue 'unknown'}"
|
411
377
|
status.success? ? logger.info(m) : logger.error(m)
|
412
378
|
end
|
@@ -463,22 +429,17 @@ class Unicorn::HttpServer
|
|
463
429
|
proc_name 'master (old)'
|
464
430
|
end
|
465
431
|
|
466
|
-
# forcibly terminate all workers that haven't checked in in timeout
|
467
|
-
# seconds. The timeout is implemented using an unlinked File
|
468
|
-
# shared between the parent process and each worker. The worker
|
469
|
-
# runs File#chmod to modify the ctime of the File. If the ctime
|
470
|
-
# is stale for >timeout seconds, then we'll kill the corresponding
|
471
|
-
# worker.
|
432
|
+
# forcibly terminate all workers that haven't checked in in timeout seconds. The timeout is implemented using an unlinked File
|
472
433
|
def murder_lazy_workers
|
473
434
|
t = @timeout
|
474
435
|
next_sleep = 1
|
436
|
+
now = Time.now.to_i
|
475
437
|
WORKERS.dup.each_pair do |wpid, worker|
|
476
|
-
|
477
|
-
# skip workers that
|
478
|
-
|
479
|
-
|
480
|
-
if
|
481
|
-
tmp = t - diff
|
438
|
+
tick = worker.tick
|
439
|
+
0 == tick and next # skip workers that are sleeping
|
440
|
+
diff = now - tick
|
441
|
+
tmp = t - diff
|
442
|
+
if tmp >= 0
|
482
443
|
next_sleep < tmp and next_sleep = tmp
|
483
444
|
next
|
484
445
|
end
|
@@ -491,24 +452,33 @@ class Unicorn::HttpServer
|
|
491
452
|
|
492
453
|
def after_fork_internal
|
493
454
|
@ready_pipe.close if @ready_pipe
|
494
|
-
|
495
|
-
|
455
|
+
Unicorn::Configurator::RACKUP.clear
|
456
|
+
@ready_pipe = @init_listeners = @config = @before_exec = @before_fork = nil
|
457
|
+
|
458
|
+
srand # http://redmine.ruby-lang.org/issues/4338
|
496
459
|
|
497
460
|
# The OpenSSL PRNG is seeded with only the pid, and apps with frequently
|
498
461
|
# dying workers can recycle pids
|
499
|
-
OpenSSL::Random.seed(
|
462
|
+
OpenSSL::Random.seed(rand.to_s) if defined?(OpenSSL::Random)
|
500
463
|
end
|
501
464
|
|
502
465
|
def spawn_missing_workers
|
503
|
-
|
504
|
-
|
505
|
-
|
466
|
+
worker_nr = -1
|
467
|
+
until (worker_nr += 1) == @worker_processes
|
468
|
+
WORKERS.value?(worker_nr) and next
|
469
|
+
worker = Worker.new(worker_nr)
|
506
470
|
before_fork.call(self, worker)
|
507
|
-
|
471
|
+
if pid = fork
|
472
|
+
WORKERS[pid] = worker
|
473
|
+
else
|
508
474
|
after_fork_internal
|
509
475
|
worker_loop(worker)
|
510
|
-
|
476
|
+
exit
|
477
|
+
end
|
511
478
|
end
|
479
|
+
rescue => e
|
480
|
+
@logger.error(e) rescue nil
|
481
|
+
exit!
|
512
482
|
end
|
513
483
|
|
514
484
|
def maintain_worker_count
|
@@ -527,11 +497,14 @@ class Unicorn::HttpServer
|
|
527
497
|
msg = case e
|
528
498
|
when EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
|
529
499
|
Unicorn::Const::ERROR_500_RESPONSE
|
500
|
+
when Unicorn::RequestURITooLongError
|
501
|
+
Unicorn::Const::ERROR_414_RESPONSE
|
502
|
+
when Unicorn::RequestEntityTooLargeError
|
503
|
+
Unicorn::Const::ERROR_413_RESPONSE
|
530
504
|
when Unicorn::HttpParserError # try to tell the client they're bad
|
531
505
|
Unicorn::Const::ERROR_400_RESPONSE
|
532
506
|
else
|
533
|
-
logger
|
534
|
-
logger.error e.backtrace.join("\n")
|
507
|
+
Unicorn.log_error(@logger, "app error", e)
|
535
508
|
Unicorn::Const::ERROR_500_RESPONSE
|
536
509
|
end
|
537
510
|
client.kgio_trywrite(msg)
|
@@ -567,10 +540,8 @@ class Unicorn::HttpServer
|
|
567
540
|
proc_name "worker[#{worker.nr}]"
|
568
541
|
START_CTX.clear
|
569
542
|
init_self_pipe!
|
570
|
-
WORKERS.values.each { |other| other.tmp.close rescue nil }
|
571
543
|
WORKERS.clear
|
572
544
|
LISTENERS.each { |sock| sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
|
573
|
-
worker.tmp.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
574
545
|
after_fork.call(self, worker) # can drop perms
|
575
546
|
worker.user(*user) if user.kind_of?(Array) && ! worker.switched
|
576
547
|
self.timeout /= 2.0 # halve it for select()
|
@@ -594,35 +565,25 @@ class Unicorn::HttpServer
|
|
594
565
|
ppid = master_pid
|
595
566
|
init_worker_process(worker)
|
596
567
|
nr = 0 # this becomes negative if we need to reopen logs
|
597
|
-
|
598
|
-
ready =
|
568
|
+
l = LISTENERS.dup
|
569
|
+
ready = l.dup
|
599
570
|
|
600
571
|
# closing anything we IO.select on will raise EBADF
|
601
572
|
trap(:USR1) { nr = -65536; SELF_PIPE[0].close rescue nil }
|
602
|
-
trap(:QUIT) {
|
573
|
+
trap(:QUIT) { worker = nil; LISTENERS.each { |s| s.close rescue nil }.clear }
|
603
574
|
[:TERM, :INT].each { |sig| trap(sig) { exit!(0) } } # instant shutdown
|
604
575
|
logger.info "worker=#{worker.nr} ready"
|
605
|
-
m = 0
|
606
576
|
|
607
577
|
begin
|
608
578
|
nr < 0 and reopen_worker_logs(worker.nr)
|
609
579
|
nr = 0
|
610
580
|
|
611
|
-
|
612
|
-
|
613
|
-
# futimes() is not available in base Ruby and I very strongly
|
614
|
-
# prefer temporary files to be unlinked for security,
|
615
|
-
# performance and reliability reasons, so utime is out. No-op
|
616
|
-
# changes with chmod doesn't update ctime on all filesystems; so
|
617
|
-
# we change our counter each and every time (after process_client
|
618
|
-
# and before IO.select).
|
619
|
-
alive.chmod(m = 0 == m ? 1 : 0)
|
620
|
-
|
621
|
-
ready.each do |sock|
|
581
|
+
worker.tick = Time.now.to_i
|
582
|
+
while sock = ready.shift
|
622
583
|
if client = sock.kgio_tryaccept
|
623
584
|
process_client(client)
|
624
585
|
nr += 1
|
625
|
-
|
586
|
+
worker.tick = Time.now.to_i
|
626
587
|
end
|
627
588
|
break if nr < 0
|
628
589
|
end
|
@@ -631,23 +592,21 @@ class Unicorn::HttpServer
|
|
631
592
|
# we're probably reasonably busy, so avoid calling select()
|
632
593
|
# and do a speculative non-blocking accept() on ready listeners
|
633
594
|
# before we sleep again in select().
|
634
|
-
|
595
|
+
unless nr == 0 # (nr < 0) => reopen logs (unlikely)
|
596
|
+
ready = l.dup
|
597
|
+
redo
|
598
|
+
end
|
635
599
|
|
636
600
|
ppid == Process.ppid or return
|
637
|
-
alive.chmod(m = 0 == m ? 1 : 0)
|
638
601
|
|
639
602
|
# timeout used so we can detect parent death:
|
640
|
-
|
641
|
-
|
642
|
-
ready = LISTENERS
|
603
|
+
worker.tick = Time.now.to_i
|
604
|
+
ret = IO.select(l, nil, SELF_PIPE, @timeout) and ready = ret[0]
|
643
605
|
rescue Errno::EBADF
|
644
606
|
nr < 0 or return
|
645
607
|
rescue => e
|
646
|
-
if
|
647
|
-
|
648
|
-
logger.error e.backtrace.join("\n")
|
649
|
-
end
|
650
|
-
end while alive
|
608
|
+
Unicorn.log_error(@logger, "listen loop error", e) if worker
|
609
|
+
end while worker
|
651
610
|
end
|
652
611
|
|
653
612
|
# delivers a signal to a worker and fails gracefully if the worker
|
@@ -655,7 +614,7 @@ class Unicorn::HttpServer
|
|
655
614
|
def kill_worker(signal, wpid)
|
656
615
|
Process.kill(signal, wpid)
|
657
616
|
rescue Errno::ESRCH
|
658
|
-
worker = WORKERS.delete(wpid) and worker.
|
617
|
+
worker = WORKERS.delete(wpid) and worker.close rescue nil
|
659
618
|
end
|
660
619
|
|
661
620
|
# delivers a signal to each worker
|
@@ -685,7 +644,7 @@ class Unicorn::HttpServer
|
|
685
644
|
def load_config!
|
686
645
|
loaded_app = app
|
687
646
|
logger.info "reloading config_file=#{config.config_file}"
|
688
|
-
config[:listeners].replace(init_listeners)
|
647
|
+
config[:listeners].replace(@init_listeners)
|
689
648
|
config.reload
|
690
649
|
config.commit!(self)
|
691
650
|
kill_each_worker(:QUIT)
|
@@ -694,8 +653,8 @@ class Unicorn::HttpServer
|
|
694
653
|
build_app! if preload_app
|
695
654
|
logger.info "done reloading config_file=#{config.config_file}"
|
696
655
|
rescue StandardError, LoadError, SyntaxError => e
|
697
|
-
logger
|
698
|
-
|
656
|
+
Unicorn.log_error(@logger,
|
657
|
+
"error reloading config_file=#{config.config_file}", e)
|
699
658
|
self.app = loaded_app
|
700
659
|
end
|
701
660
|
|
@@ -729,5 +688,33 @@ class Unicorn::HttpServer
|
|
729
688
|
SELF_PIPE.replace(Kgio::Pipe.new)
|
730
689
|
SELF_PIPE.each { |io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
|
731
690
|
end
|
732
|
-
end
|
733
691
|
|
692
|
+
def inherit_listeners!
|
693
|
+
# inherit sockets from parents, they need to be plain Socket objects
|
694
|
+
# before they become Kgio::UNIXServer or Kgio::TCPServer
|
695
|
+
inherited = ENV['UNICORN_FD'].to_s.split(/,/).map do |fd|
|
696
|
+
io = Socket.for_fd(fd.to_i)
|
697
|
+
set_server_sockopt(io, listener_opts[sock_name(io)])
|
698
|
+
IO_PURGATORY << io
|
699
|
+
logger.info "inherited addr=#{sock_name(io)} fd=#{fd}"
|
700
|
+
server_cast(io)
|
701
|
+
end
|
702
|
+
|
703
|
+
config_listeners = config[:listeners].dup
|
704
|
+
LISTENERS.replace(inherited)
|
705
|
+
|
706
|
+
# we start out with generic Socket objects that get cast to either
|
707
|
+
# Kgio::TCPServer or Kgio::UNIXServer objects; but since the Socket
|
708
|
+
# objects share the same OS-level file descriptor as the higher-level
|
709
|
+
# *Server objects; we need to prevent Socket objects from being
|
710
|
+
# garbage-collected
|
711
|
+
config_listeners -= listener_names
|
712
|
+
if config_listeners.empty? && LISTENERS.empty?
|
713
|
+
config_listeners << Unicorn::Const::DEFAULT_LISTEN
|
714
|
+
@init_listeners << Unicorn::Const::DEFAULT_LISTEN
|
715
|
+
START_CTX[:argv] << "-l#{Unicorn::Const::DEFAULT_LISTEN}"
|
716
|
+
end
|
717
|
+
config_listeners.each { |addr| listen(addr) }
|
718
|
+
raise ArgumentError, "no listeners" if LISTENERS.empty?
|
719
|
+
end
|
720
|
+
end
|
data/lib/unicorn/oob_gc.rb
CHANGED
@@ -1,58 +1,69 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
-
module Unicorn
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
3
|
+
# Runs GC after requests, after closing the client socket and
|
4
|
+
# before attempting to accept more connections.
|
5
|
+
#
|
6
|
+
# This shouldn't hurt overall performance as long as the server cluster
|
7
|
+
# is at <50% CPU capacity, and improves the performance of most memory
|
8
|
+
# intensive requests. This serves to improve _client-visible_
|
9
|
+
# performance (possibly at the cost of overall performance).
|
10
|
+
#
|
11
|
+
# Increasing the number of +worker_processes+ may be necessary to
|
12
|
+
# improve average client response times because some of your workers
|
13
|
+
# will be busy doing GC and unable to service clients. Think of
|
14
|
+
# using more workers with this module as a poor man's concurrent GC.
|
15
|
+
#
|
16
|
+
# We'll call GC after each request is been written out to the socket, so
|
17
|
+
# the client never sees the extra GC hit it.
|
18
|
+
#
|
19
|
+
# This middleware is _only_ effective for applications that use a lot
|
20
|
+
# of memory, and will hurt simpler apps/endpoints that can process
|
21
|
+
# multiple requests before incurring GC.
|
22
|
+
#
|
23
|
+
# This middleware is only designed to work with unicorn, as it harms
|
24
|
+
# performance with keepalive-enabled servers.
|
25
|
+
#
|
26
|
+
# Example (in config.ru):
|
27
|
+
#
|
28
|
+
# require 'unicorn/oob_gc'
|
29
|
+
#
|
30
|
+
# # GC ever two requests that hit /expensive/foo or /more_expensive/foo
|
31
|
+
# # in your app. By default, this will GC once every 5 requests
|
32
|
+
# # for all endpoints in your app
|
33
|
+
# use Unicorn::OobGC, 2, %r{\A/(?:expensive/foo|more_expensive/foo)}
|
34
|
+
#
|
35
|
+
# Feedback from users of early implementations of this module:
|
36
|
+
# * http://comments.gmane.org/gmane.comp.lang.ruby.unicorn.general/486
|
37
|
+
# * http://article.gmane.org/gmane.comp.lang.ruby.unicorn.general/596
|
38
|
+
module Unicorn::OobGC
|
40
39
|
|
41
|
-
|
42
|
-
|
40
|
+
# this pretends to be Rack middleware because it used to be
|
41
|
+
# But we need to hook into unicorn internals so we need to close
|
42
|
+
# the socket before clearing the request env.
|
43
|
+
#
|
44
|
+
# +interval+ is the number of requests matching the +path+ regular
|
45
|
+
# expression before invoking GC.
|
46
|
+
def self.new(app, interval = 5, path = %r{\A/})
|
47
|
+
@@nr = interval
|
48
|
+
self.const_set :OOBGC_PATH, path
|
49
|
+
self.const_set :OOBGC_INTERVAL, interval
|
50
|
+
ObjectSpace.each_object(Unicorn::HttpServer) do |s|
|
51
|
+
s.extend(self)
|
52
|
+
self.const_set :OOBGC_ENV, s.instance_variable_get(:@request).env
|
43
53
|
end
|
54
|
+
app # pretend to be Rack middleware since it was in the past
|
55
|
+
end
|
44
56
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
GC.start
|
54
|
-
end
|
57
|
+
#:stopdoc:
|
58
|
+
PATH_INFO = "PATH_INFO"
|
59
|
+
def process_client(client)
|
60
|
+
super(client) # Unicorn::HttpServer#process_client
|
61
|
+
if OOBGC_PATH =~ OOBGC_ENV[PATH_INFO] && ((@@nr -= 1) <= 0)
|
62
|
+
@@nr = OOBGC_INTERVAL
|
63
|
+
OOBGC_ENV.clear
|
64
|
+
GC.start
|
55
65
|
end
|
56
|
-
|
57
66
|
end
|
67
|
+
|
68
|
+
# :startdoc:
|
58
69
|
end
|
@@ -4,9 +4,12 @@ require 'socket'
|
|
4
4
|
|
5
5
|
module Unicorn
|
6
6
|
module SocketHelper
|
7
|
+
# :stopdoc:
|
7
8
|
include Socket::Constants
|
8
9
|
|
9
|
-
#
|
10
|
+
# prevents IO objects in here from being GC-ed
|
11
|
+
IO_PURGATORY = []
|
12
|
+
|
10
13
|
# internal interface, only used by Rainbows!/Zbatery
|
11
14
|
DEFAULTS = {
|
12
15
|
# The semantics for TCP_DEFER_ACCEPT changed in Linux 2.6.32+
|
@@ -24,9 +27,9 @@ module Unicorn
|
|
24
27
|
# same default value as Mongrel
|
25
28
|
:backlog => 1024,
|
26
29
|
|
27
|
-
#
|
28
|
-
|
29
|
-
:
|
30
|
+
# favor latency over bandwidth savings
|
31
|
+
:tcp_nopush => false,
|
32
|
+
:tcp_nodelay => true,
|
30
33
|
}
|
31
34
|
#:startdoc:
|
32
35
|
|
@@ -98,8 +101,7 @@ module Unicorn
|
|
98
101
|
end
|
99
102
|
sock.listen(opt[:backlog])
|
100
103
|
rescue => e
|
101
|
-
logger
|
102
|
-
logger.error e.backtrace.join("\n")
|
104
|
+
Unicorn.log_error(logger, message, e)
|
103
105
|
end
|
104
106
|
|
105
107
|
def log_buffer_sizes(sock, pfx = '')
|
@@ -136,8 +138,9 @@ module Unicorn
|
|
136
138
|
ensure
|
137
139
|
File.umask(old_umask)
|
138
140
|
end
|
139
|
-
elsif /\A(
|
140
|
-
|
141
|
+
elsif /\A\[([a-fA-F0-9:]+)\]:(\d+)\z/ =~ address
|
142
|
+
new_ipv6_server($1, $2.to_i, opt)
|
143
|
+
elsif /\A(\d+\.\d+\.\d+\.\d+):(\d+)\z/ =~ address
|
141
144
|
Kgio::TCPServer.new($1, $2.to_i)
|
142
145
|
else
|
143
146
|
raise ArgumentError, "Don't know how to bind: #{address}"
|
@@ -146,6 +149,18 @@ module Unicorn
|
|
146
149
|
sock
|
147
150
|
end
|
148
151
|
|
152
|
+
def new_ipv6_server(addr, port, opt)
|
153
|
+
opt.key?(:ipv6only) or return Kgio::TCPServer.new(addr, port)
|
154
|
+
defined?(IPV6_V6ONLY) or
|
155
|
+
abort "Socket::IPV6_V6ONLY not defined, upgrade Ruby and/or your OS"
|
156
|
+
sock = Socket.new(AF_INET6, SOCK_STREAM, 0)
|
157
|
+
sock.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
|
158
|
+
sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
|
159
|
+
sock.bind(Socket.pack_sockaddr_in(port, addr))
|
160
|
+
IO_PURGATORY << sock
|
161
|
+
Kgio::TCPServer.for_fd(sock.fileno)
|
162
|
+
end
|
163
|
+
|
149
164
|
# returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
|
150
165
|
def tcp_name(sock)
|
151
166
|
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
|
data/lib/unicorn/worker.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
+
require "raindrops"
|
2
3
|
|
3
4
|
# This class and its members can be considered a stable interface
|
4
5
|
# and will not change in a backwards-incompatible fashion between
|
@@ -7,13 +8,53 @@
|
|
7
8
|
#
|
8
9
|
# Some users may want to access it in the before_fork/after_fork hooks.
|
9
10
|
# See the Unicorn::Configurator RDoc for examples.
|
10
|
-
class Unicorn::Worker
|
11
|
+
class Unicorn::Worker
|
12
|
+
# :stopdoc:
|
13
|
+
attr_accessor :nr, :switched
|
14
|
+
attr_writer :tmp
|
15
|
+
|
16
|
+
PER_DROP = Raindrops::PAGE_SIZE / Raindrops::SIZE
|
17
|
+
DROPS = []
|
18
|
+
|
19
|
+
def initialize(nr)
|
20
|
+
drop_index = nr / PER_DROP
|
21
|
+
@raindrop = DROPS[drop_index] ||= Raindrops.new(PER_DROP)
|
22
|
+
@offset = nr % PER_DROP
|
23
|
+
@raindrop[@offset] = 0
|
24
|
+
@nr = nr
|
25
|
+
@tmp = @switched = false
|
26
|
+
end
|
11
27
|
|
12
28
|
# worker objects may be compared to just plain Integers
|
13
29
|
def ==(other_nr) # :nodoc:
|
14
|
-
nr == other_nr
|
30
|
+
@nr == other_nr
|
31
|
+
end
|
32
|
+
|
33
|
+
# called in the worker process
|
34
|
+
def tick=(value) # :nodoc:
|
35
|
+
@raindrop[@offset] = value
|
36
|
+
end
|
37
|
+
|
38
|
+
# called in the master process
|
39
|
+
def tick # :nodoc:
|
40
|
+
@raindrop[@offset]
|
15
41
|
end
|
16
42
|
|
43
|
+
# only exists for compatibility
|
44
|
+
def tmp # :nodoc:
|
45
|
+
@tmp ||= begin
|
46
|
+
tmp = Unicorn::TmpIO.new
|
47
|
+
tmp.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
48
|
+
tmp
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def close # :nodoc:
|
53
|
+
@tmp.close if @tmp
|
54
|
+
end
|
55
|
+
|
56
|
+
# :startdoc:
|
57
|
+
|
17
58
|
# In most cases, you should be using the Unicorn::Configurator#user
|
18
59
|
# directive instead. This method should only be used if you need
|
19
60
|
# fine-grained control of exactly when you want to change permissions
|
@@ -36,12 +77,12 @@ class Unicorn::Worker < Struct.new(:nr, :tmp, :switched)
|
|
36
77
|
uid = Etc.getpwnam(user).uid
|
37
78
|
gid = Etc.getgrnam(group).gid if group
|
38
79
|
Unicorn::Util.chown_logs(uid, gid)
|
39
|
-
tmp.chown(uid, gid)
|
80
|
+
@tmp.chown(uid, gid) if @tmp
|
40
81
|
if gid && Process.egid != gid
|
41
82
|
Process.initgroups(user, gid)
|
42
83
|
Process::GID.change_privilege(gid)
|
43
84
|
end
|
44
85
|
Process.euid != uid and Process::UID.change_privilege(uid)
|
45
|
-
|
86
|
+
@switched = true
|
46
87
|
end
|
47
88
|
end
|
data/lib/unicorn.rb
CHANGED
@@ -26,17 +26,14 @@ module Unicorn
|
|
26
26
|
end
|
27
27
|
|
28
28
|
# :stopdoc:
|
29
|
-
def self.run(app, options = {})
|
30
|
-
Unicorn::HttpServer.new(app, options).start.join
|
31
|
-
end
|
32
29
|
|
33
30
|
# This returns a lambda to pass in as the app, this does not "build" the
|
34
31
|
# app (which we defer based on the outcome of "preload_app" in the
|
35
32
|
# Unicorn config). The returned lambda will be called when it is
|
36
33
|
# time to build the app.
|
37
|
-
def self.builder(ru,
|
34
|
+
def self.builder(ru, op)
|
38
35
|
# allow Configurator to parse cli switches embedded in the ru file
|
39
|
-
Unicorn::Configurator::RACKUP.
|
36
|
+
op = Unicorn::Configurator::RACKUP.merge!(:file => ru, :optparse => op)
|
40
37
|
|
41
38
|
# always called after config file parsing, may be called after forking
|
42
39
|
lambda do ||
|
@@ -44,7 +41,7 @@ module Unicorn
|
|
44
41
|
when /\.ru$/
|
45
42
|
raw = File.read(ru)
|
46
43
|
raw.sub!(/^__END__\n.*/, '')
|
47
|
-
eval("Rack::Builder.new {(#{raw}\n)}.to_app", TOPLEVEL_BINDING, ru)
|
44
|
+
eval("Rack::Builder.new {(\n#{raw}\n)}.to_app", TOPLEVEL_BINDING, ru)
|
48
45
|
else
|
49
46
|
require ru
|
50
47
|
Object.const_get(File.basename(ru, '.rb').capitalize)
|
@@ -80,6 +77,11 @@ module Unicorn
|
|
80
77
|
Unicorn::SocketHelper.sock_name(io)
|
81
78
|
end
|
82
79
|
end
|
80
|
+
|
81
|
+
def self.log_error(logger, message, exc)
|
82
|
+
logger.error "#{message}: #{exc.message} (#{exc.class})"
|
83
|
+
exc.backtrace.each { |line| logger.error(line) }
|
84
|
+
end
|
83
85
|
# :startdoc:
|
84
86
|
end
|
85
87
|
# :enddoc:
|
data/script/isolate_for_tests
CHANGED
@@ -17,8 +17,9 @@ opts = {
|
|
17
17
|
pid = fork do
|
18
18
|
Isolate.now!(opts) do
|
19
19
|
gem 'sqlite3-ruby', '1.2.5'
|
20
|
-
gem '
|
21
|
-
gem '
|
20
|
+
gem 'raindrops', '0.6.1'
|
21
|
+
gem 'kgio', '2.5.0'
|
22
|
+
gem 'rack', '1.3.0'
|
22
23
|
end
|
23
24
|
end
|
24
25
|
_, status = Process.waitpid2(pid)
|
@@ -35,6 +36,7 @@ File.rename("#{dst}.#$$", dst)
|
|
35
36
|
opts[:path] = "tmp/isolate/rails-#{rails_ver}"
|
36
37
|
pid = fork do
|
37
38
|
Isolate.now!(opts) do
|
39
|
+
gem 'rake', '0.8.7'
|
38
40
|
gem 'rails', rails_ver
|
39
41
|
end
|
40
42
|
end
|
data/t/broken-app.ru
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# we do not want Rack::Lint or anything to protect us
|
2
|
+
use Rack::ContentLength
|
3
|
+
use Rack::ContentType, "text/plain"
|
4
|
+
map "/" do
|
5
|
+
run lambda { |env| [ 200, {}, [ "OK\n" ] ] }
|
6
|
+
end
|
7
|
+
map "/raise" do
|
8
|
+
run lambda { |env| raise "BAD" }
|
9
|
+
end
|
10
|
+
map "/nil" do
|
11
|
+
run lambda { |env| nil }
|
12
|
+
end
|