unicorn 3.6.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|