unicorn 5.0.1 → 6.1.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.
- checksums.yaml +5 -5
- data/.manifest +11 -5
- data/.olddoc.yml +16 -6
- data/Application_Timeouts +4 -4
- data/CONTRIBUTORS +6 -2
- data/Documentation/.gitignore +1 -3
- data/Documentation/unicorn.1 +222 -0
- data/Documentation/unicorn_rails.1 +207 -0
- data/FAQ +1 -1
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +118 -58
- data/HACKING +2 -10
- data/ISSUES +40 -35
- data/KNOWN_ISSUES +2 -2
- data/LATEST +23 -28
- data/LICENSE +2 -2
- data/Links +13 -11
- data/NEWS +612 -0
- data/README +30 -29
- data/SIGNALS +1 -1
- data/Sandbox +8 -7
- data/TODO +0 -2
- data/TUNING +19 -1
- data/archive/slrnpull.conf +1 -1
- data/bin/unicorn +3 -1
- data/bin/unicorn_rails +2 -2
- data/examples/big_app_gc.rb +1 -1
- data/examples/init.sh +36 -8
- data/examples/logrotate.conf +17 -2
- data/examples/nginx.conf +4 -3
- data/examples/unicorn.conf.minimal.rb +2 -2
- data/examples/unicorn.conf.rb +2 -2
- data/examples/unicorn@.service +14 -0
- data/ext/unicorn_http/c_util.h +5 -13
- data/ext/unicorn_http/common_field_optimization.h +22 -5
- data/ext/unicorn_http/epollexclusive.h +124 -0
- data/ext/unicorn_http/ext_help.h +0 -44
- data/ext/unicorn_http/extconf.rb +32 -6
- data/ext/unicorn_http/global_variables.h +2 -2
- data/ext/unicorn_http/httpdate.c +2 -1
- data/ext/unicorn_http/unicorn_http.c +853 -498
- data/ext/unicorn_http/unicorn_http.rl +86 -30
- data/ext/unicorn_http/unicorn_http_common.rl +1 -1
- data/lib/unicorn/configurator.rb +93 -13
- data/lib/unicorn/http_request.rb +101 -11
- data/lib/unicorn/http_response.rb +8 -4
- data/lib/unicorn/http_server.rb +141 -72
- data/lib/unicorn/launcher.rb +1 -1
- data/lib/unicorn/oob_gc.rb +6 -6
- data/lib/unicorn/select_waiter.rb +6 -0
- data/lib/unicorn/socket_helper.rb +23 -7
- data/lib/unicorn/stream_input.rb +5 -4
- data/lib/unicorn/tee_input.rb +8 -10
- data/lib/unicorn/tmpio.rb +8 -2
- data/lib/unicorn/util.rb +3 -3
- data/lib/unicorn/version.rb +1 -1
- data/lib/unicorn/worker.rb +33 -8
- data/lib/unicorn.rb +55 -29
- data/man/man1/unicorn.1 +120 -118
- data/man/man1/unicorn_rails.1 +106 -107
- data/t/GNUmakefile +3 -72
- data/t/README +4 -4
- data/t/t0011-active-unix-socket.sh +1 -1
- data/t/t0012-reload-empty-config.sh +2 -1
- data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
- data/t/t0301.ru +13 -0
- data/t/test-lib.sh +4 -3
- data/test/benchmark/README +14 -4
- data/test/benchmark/ddstream.ru +50 -0
- data/test/benchmark/readinput.ru +40 -0
- data/test/benchmark/uconnect.perl +66 -0
- data/test/exec/test_exec.rb +26 -24
- data/test/test_helper.rb +38 -30
- data/test/unit/test_ccc.rb +91 -0
- data/test/unit/test_droplet.rb +1 -1
- data/test/unit/test_http_parser.rb +46 -16
- data/test/unit/test_http_parser_ng.rb +81 -0
- data/test/unit/test_request.rb +10 -10
- data/test/unit/test_server.rb +86 -12
- data/test/unit/test_signals.rb +8 -8
- data/test/unit/test_socket_helper.rb +13 -9
- data/test/unit/test_upload.rb +9 -14
- data/test/unit/test_util.rb +31 -5
- data/test/unit/test_waiter.rb +34 -0
- data/unicorn.gemspec +21 -22
- metadata +21 -28
- data/Documentation/GNUmakefile +0 -30
- data/Documentation/unicorn.1.txt +0 -188
- data/Documentation/unicorn_rails.1.txt +0 -175
- data/t/hijack.ru +0 -43
- data/t/t0200-rack-hijack.sh +0 -30
data/lib/unicorn/http_server.rb
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
# forked worker children.
|
7
7
|
#
|
8
8
|
# Users do not need to know the internals of this class, but reading the
|
9
|
-
# {source}[
|
9
|
+
# {source}[https://yhbt.net/unicorn.git/tree/lib/unicorn/http_server.rb]
|
10
10
|
# is education for programmers wishing to learn how unicorn works.
|
11
11
|
# See Unicorn::Configurator for information on how to configure unicorn.
|
12
12
|
class Unicorn::HttpServer
|
@@ -14,7 +14,9 @@ class Unicorn::HttpServer
|
|
14
14
|
attr_accessor :app, :timeout, :worker_processes,
|
15
15
|
:before_fork, :after_fork, :before_exec,
|
16
16
|
:listener_opts, :preload_app,
|
17
|
-
:orig_app, :config, :ready_pipe, :user
|
17
|
+
:orig_app, :config, :ready_pipe, :user,
|
18
|
+
:default_middleware, :early_hints
|
19
|
+
attr_writer :after_worker_exit, :after_worker_ready, :worker_exec
|
18
20
|
|
19
21
|
attr_reader :pid, :logger
|
20
22
|
include Unicorn::SocketHelper
|
@@ -36,7 +38,7 @@ class Unicorn::HttpServer
|
|
36
38
|
# or even different installations of the same applications without
|
37
39
|
# downtime. Keys of this constant Hash are described as follows:
|
38
40
|
#
|
39
|
-
# * 0 - the path to the unicorn
|
41
|
+
# * 0 - the path to the unicorn executable
|
40
42
|
# * :argv - a deep copy of the ARGV array the executable originally saw
|
41
43
|
# * :cwd - the working directory of the application, this is where
|
42
44
|
# you originally started Unicorn.
|
@@ -45,7 +47,7 @@ class Unicorn::HttpServer
|
|
45
47
|
# you can set the following in your Unicorn config file, HUP and then
|
46
48
|
# continue with the traditional USR2 + QUIT upgrade steps:
|
47
49
|
#
|
48
|
-
# Unicorn::HttpServer::START_CTX[0] = "/home/bofh/2.
|
50
|
+
# Unicorn::HttpServer::START_CTX[0] = "/home/bofh/2.3.0/bin/unicorn"
|
49
51
|
START_CTX = {
|
50
52
|
:argv => ARGV.map(&:dup),
|
51
53
|
0 => $0.dup,
|
@@ -67,8 +69,8 @@ class Unicorn::HttpServer
|
|
67
69
|
# incoming requests on the socket.
|
68
70
|
def initialize(app, options = {})
|
69
71
|
@app = app
|
70
|
-
@request = Unicorn::HttpRequest.new
|
71
72
|
@reexec_pid = 0
|
73
|
+
@default_middleware = true
|
72
74
|
options = options.dup
|
73
75
|
@ready_pipe = options.delete(:ready_pipe)
|
74
76
|
@init_listeners = options[:listeners] ? options[:listeners].dup : []
|
@@ -81,13 +83,14 @@ class Unicorn::HttpServer
|
|
81
83
|
# * The master process never closes or reinitializes this once
|
82
84
|
# initialized. Signal handlers in the master process will write to
|
83
85
|
# it to wake up the master from IO.select in exactly the same manner
|
84
|
-
# djb describes in
|
86
|
+
# djb describes in https://cr.yp.to/docs/selfpipe.html
|
85
87
|
#
|
86
88
|
# * The workers immediately close the pipe they inherit. See the
|
87
89
|
# Unicorn::Worker class for the pipe workers use.
|
88
90
|
@self_pipe = []
|
89
91
|
@workers = {} # hash maps PIDs to Workers
|
90
92
|
@sig_queue = [] # signal queue used for self-piping
|
93
|
+
@pid = nil
|
91
94
|
|
92
95
|
# we try inheriting listeners first, so we bind them later.
|
93
96
|
# we don't write the pid file until we've bound listeners in case
|
@@ -104,6 +107,14 @@ class Unicorn::HttpServer
|
|
104
107
|
# list of signals we care about and trap in master.
|
105
108
|
@queue_sigs = [
|
106
109
|
:WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ]
|
110
|
+
|
111
|
+
@worker_data = if worker_data = ENV['UNICORN_WORKER']
|
112
|
+
worker_data = worker_data.split(',').map!(&:to_i)
|
113
|
+
worker_data[1] = worker_data.slice!(1..2).map do |i|
|
114
|
+
Kgio::Pipe.for_fd(i)
|
115
|
+
end
|
116
|
+
worker_data
|
117
|
+
end
|
107
118
|
end
|
108
119
|
|
109
120
|
# Runs the thing. Returns self so you can run join on it
|
@@ -112,7 +123,7 @@ class Unicorn::HttpServer
|
|
112
123
|
# this pipe is used to wake us up from select(2) in #join when signals
|
113
124
|
# are trapped. See trap_deferred.
|
114
125
|
@self_pipe.replace(Unicorn.pipe)
|
115
|
-
@master_pid = $$
|
126
|
+
@master_pid = @worker_data ? Process.ppid : $$
|
116
127
|
|
117
128
|
# setup signal handlers before writing pid file in case people get
|
118
129
|
# trigger happy and send signals as soon as the pid file exists.
|
@@ -138,7 +149,7 @@ class Unicorn::HttpServer
|
|
138
149
|
def listeners=(listeners)
|
139
150
|
cur_names, dead_names = [], []
|
140
151
|
listener_names.each do |name|
|
141
|
-
if
|
152
|
+
if name.start_with?('/')
|
142
153
|
# mark unlinked sockets as dead so we can rebind them
|
143
154
|
(File.socket?(name) ? cur_names : dead_names) << name
|
144
155
|
else
|
@@ -370,7 +381,7 @@ class Unicorn::HttpServer
|
|
370
381
|
|
371
382
|
# wait for a signal hander to wake us up and then consume the pipe
|
372
383
|
def master_sleep(sec)
|
373
|
-
@self_pipe[0].
|
384
|
+
@self_pipe[0].wait(sec) or return
|
374
385
|
# 11 bytes is the maximum string length which can be embedded within
|
375
386
|
# the Ruby itself and not require a separate malloc (on 32-bit MRI 1.9+).
|
376
387
|
# Most reads are only one byte here and uncommon, so it's not worth a
|
@@ -395,8 +406,7 @@ class Unicorn::HttpServer
|
|
395
406
|
proc_name 'master'
|
396
407
|
else
|
397
408
|
worker = @workers.delete(wpid) and worker.close rescue nil
|
398
|
-
|
399
|
-
status.success? ? logger.info(m) : logger.error(m)
|
409
|
+
@after_worker_exit.call(self, worker, status)
|
400
410
|
end
|
401
411
|
rescue Errno::ECHILD
|
402
412
|
break
|
@@ -430,11 +440,7 @@ class Unicorn::HttpServer
|
|
430
440
|
end
|
431
441
|
|
432
442
|
@reexec_pid = fork do
|
433
|
-
listener_fds =
|
434
|
-
LISTENERS.each do |sock|
|
435
|
-
sock.close_on_exec = false
|
436
|
-
listener_fds[sock.fileno] = sock
|
437
|
-
end
|
443
|
+
listener_fds = listener_sockets
|
438
444
|
ENV['UNICORN_FD'] = listener_fds.keys.join(',')
|
439
445
|
Dir.chdir(START_CTX[:cwd])
|
440
446
|
cmd = [ START_CTX[0] ].concat(START_CTX[:argv])
|
@@ -442,12 +448,7 @@ class Unicorn::HttpServer
|
|
442
448
|
# avoid leaking FDs we don't know about, but let before_exec
|
443
449
|
# unset FD_CLOEXEC, if anything else in the app eventually
|
444
450
|
# relies on FD inheritence.
|
445
|
-
(
|
446
|
-
next if listener_fds.include?(io)
|
447
|
-
io = IO.for_fd(io) rescue next
|
448
|
-
io.autoclose = false
|
449
|
-
io.close_on_exec = true
|
450
|
-
end
|
451
|
+
close_sockets_on_exec(listener_fds)
|
451
452
|
|
452
453
|
# exec(command, hash) works in at least 1.9.1+, but will only be
|
453
454
|
# required in 1.9.4/2.0.0 at earliest.
|
@@ -459,6 +460,40 @@ class Unicorn::HttpServer
|
|
459
460
|
proc_name 'master (old)'
|
460
461
|
end
|
461
462
|
|
463
|
+
def worker_spawn(worker)
|
464
|
+
listener_fds = listener_sockets
|
465
|
+
env = {}
|
466
|
+
env['UNICORN_FD'] = listener_fds.keys.join(',')
|
467
|
+
|
468
|
+
listener_fds[worker.to_io.fileno] = worker.to_io
|
469
|
+
listener_fds[worker.master.fileno] = worker.master
|
470
|
+
|
471
|
+
worker_info = [worker.nr, worker.to_io.fileno, worker.master.fileno]
|
472
|
+
env['UNICORN_WORKER'] = worker_info.join(',')
|
473
|
+
|
474
|
+
close_sockets_on_exec(listener_fds)
|
475
|
+
|
476
|
+
Process.spawn(env, START_CTX[0], *START_CTX[:argv], listener_fds)
|
477
|
+
end
|
478
|
+
|
479
|
+
def listener_sockets
|
480
|
+
listener_fds = {}
|
481
|
+
LISTENERS.each do |sock|
|
482
|
+
sock.close_on_exec = false
|
483
|
+
listener_fds[sock.fileno] = sock
|
484
|
+
end
|
485
|
+
listener_fds
|
486
|
+
end
|
487
|
+
|
488
|
+
def close_sockets_on_exec(sockets)
|
489
|
+
(3..1024).each do |io|
|
490
|
+
next if sockets.include?(io)
|
491
|
+
io = IO.for_fd(io) rescue next
|
492
|
+
io.autoclose = false
|
493
|
+
io.close_on_exec = true
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
462
497
|
# forcibly terminate all workers that haven't checked in in timeout seconds. The timeout is implemented using an unlinked File
|
463
498
|
def murder_lazy_workers
|
464
499
|
next_sleep = @timeout - 1
|
@@ -486,32 +521,39 @@ class Unicorn::HttpServer
|
|
486
521
|
Unicorn::Configurator::RACKUP.clear
|
487
522
|
@ready_pipe = @init_listeners = @before_exec = @before_fork = nil
|
488
523
|
|
489
|
-
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/36450
|
490
|
-
srand # remove in unicorn 6
|
491
|
-
|
492
524
|
# The OpenSSL PRNG is seeded with only the pid, and apps with frequently
|
493
525
|
# dying workers can recycle pids
|
494
526
|
OpenSSL::Random.seed(rand.to_s) if defined?(OpenSSL::Random)
|
495
527
|
end
|
496
528
|
|
497
529
|
def spawn_missing_workers
|
530
|
+
if @worker_data
|
531
|
+
worker = Unicorn::Worker.new(*@worker_data)
|
532
|
+
after_fork_internal
|
533
|
+
worker_loop(worker)
|
534
|
+
exit
|
535
|
+
end
|
536
|
+
|
498
537
|
worker_nr = -1
|
499
538
|
until (worker_nr += 1) == @worker_processes
|
500
539
|
@workers.value?(worker_nr) and next
|
501
540
|
worker = Unicorn::Worker.new(worker_nr)
|
502
541
|
before_fork.call(self, worker)
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
542
|
+
|
543
|
+
pid = @worker_exec ? worker_spawn(worker) : fork
|
544
|
+
|
545
|
+
unless pid
|
507
546
|
after_fork_internal
|
508
547
|
worker_loop(worker)
|
509
548
|
exit
|
510
549
|
end
|
550
|
+
|
551
|
+
@workers[pid] = worker
|
552
|
+
worker.atfork_parent
|
511
553
|
end
|
512
|
-
|
513
|
-
|
514
|
-
|
554
|
+
rescue => e
|
555
|
+
@logger.error(e) rescue nil
|
556
|
+
exit!
|
515
557
|
end
|
516
558
|
|
517
559
|
def maintain_worker_count
|
@@ -542,13 +584,32 @@ class Unicorn::HttpServer
|
|
542
584
|
client.kgio_trywrite(err_response(code, @request.response_start_sent))
|
543
585
|
end
|
544
586
|
client.close
|
545
|
-
|
587
|
+
rescue
|
588
|
+
end
|
589
|
+
|
590
|
+
def e103_response_write(client, headers)
|
591
|
+
response = if @request.response_start_sent
|
592
|
+
"103 Early Hints\r\n"
|
593
|
+
else
|
594
|
+
"HTTP/1.1 103 Early Hints\r\n"
|
595
|
+
end
|
596
|
+
|
597
|
+
headers.each_pair do |k, vs|
|
598
|
+
next if !vs || vs.empty?
|
599
|
+
values = vs.to_s.split("\n".freeze)
|
600
|
+
values.each do |v|
|
601
|
+
response << "#{k}: #{v}\r\n"
|
602
|
+
end
|
603
|
+
end
|
604
|
+
response << "\r\n".freeze
|
605
|
+
response << "HTTP/1.1 ".freeze if @request.response_start_sent
|
606
|
+
client.write(response)
|
546
607
|
end
|
547
608
|
|
548
609
|
def e100_response_write(client, env)
|
549
610
|
# We use String#freeze to avoid allocations under Ruby 2.1+
|
550
611
|
# Not many users hit this code path, so it's better to reduce the
|
551
|
-
# constant table sizes even for
|
612
|
+
# constant table sizes even for Ruby 2.0 users who'll hit extra
|
552
613
|
# allocations here.
|
553
614
|
client.write(@request.response_start_sent ?
|
554
615
|
"100 Continue\r\n\r\nHTTP/1.1 ".freeze :
|
@@ -559,7 +620,18 @@ class Unicorn::HttpServer
|
|
559
620
|
# once a client is accepted, it is processed in its entirety here
|
560
621
|
# in 3 easy steps: read request, call app, write app response
|
561
622
|
def process_client(client)
|
562
|
-
|
623
|
+
@request = Unicorn::HttpRequest.new
|
624
|
+
env = @request.read(client)
|
625
|
+
|
626
|
+
if early_hints
|
627
|
+
env["rack.early_hints"] = lambda do |headers|
|
628
|
+
e103_response_write(client, headers)
|
629
|
+
end
|
630
|
+
end
|
631
|
+
|
632
|
+
env["rack.after_reply"] = []
|
633
|
+
|
634
|
+
status, headers, body = @app.call(env)
|
563
635
|
|
564
636
|
begin
|
565
637
|
return if @request.hijacked?
|
@@ -570,8 +642,7 @@ class Unicorn::HttpServer
|
|
570
642
|
return if @request.hijacked?
|
571
643
|
end
|
572
644
|
@request.headers? or headers = nil
|
573
|
-
http_response_write(client, status, headers, body,
|
574
|
-
@request.response_start_sent)
|
645
|
+
http_response_write(client, status, headers, body, @request)
|
575
646
|
ensure
|
576
647
|
body.respond_to?(:close) and body.close
|
577
648
|
end
|
@@ -582,6 +653,8 @@ class Unicorn::HttpServer
|
|
582
653
|
end
|
583
654
|
rescue => e
|
584
655
|
handle_error(client, e)
|
656
|
+
ensure
|
657
|
+
env["rack.after_reply"].each(&:call) if env
|
585
658
|
end
|
586
659
|
|
587
660
|
def nuke_listeners!(readers)
|
@@ -612,7 +685,6 @@ class Unicorn::HttpServer
|
|
612
685
|
LISTENERS.each { |sock| sock.close_on_exec = true }
|
613
686
|
|
614
687
|
worker.user(*user) if user.kind_of?(Array) && ! worker.switched
|
615
|
-
self.timeout /= 2.0 # halve it for select()
|
616
688
|
@config = nil
|
617
689
|
build_app! unless preload_app
|
618
690
|
@after_fork = @listener_opts = @orig_app = nil
|
@@ -626,58 +698,55 @@ class Unicorn::HttpServer
|
|
626
698
|
logger.info "worker=#{worker_nr} reopening logs..."
|
627
699
|
Unicorn::Util.reopen_logs
|
628
700
|
logger.info "worker=#{worker_nr} done reopening logs"
|
629
|
-
|
630
|
-
|
631
|
-
|
701
|
+
false
|
702
|
+
rescue => e
|
703
|
+
logger.error(e) rescue nil
|
704
|
+
exit!(77) # EX_NOPERM in sysexits.h
|
705
|
+
end
|
706
|
+
|
707
|
+
def prep_readers(readers)
|
708
|
+
wtr = Unicorn::Waiter.prep_readers(readers)
|
709
|
+
@timeout *= 500 # to milliseconds for epoll, but halved
|
710
|
+
wtr
|
711
|
+
rescue
|
712
|
+
require_relative 'select_waiter'
|
713
|
+
@timeout /= 2.0 # halved for IO.select
|
714
|
+
Unicorn::SelectWaiter.new
|
632
715
|
end
|
633
716
|
|
634
717
|
# runs inside each forked worker, this sits around and waits
|
635
718
|
# for connections and doesn't die until the parent dies (or is
|
636
719
|
# given a INT, QUIT, or TERM signal)
|
637
720
|
def worker_loop(worker)
|
638
|
-
ppid = @master_pid
|
639
721
|
readers = init_worker_process(worker)
|
640
|
-
|
722
|
+
waiter = prep_readers(readers)
|
723
|
+
reopen = false
|
641
724
|
|
642
725
|
# this only works immediately if the master sent us the signal
|
643
726
|
# (which is the normal case)
|
644
|
-
trap(:USR1) {
|
727
|
+
trap(:USR1) { reopen = true }
|
645
728
|
|
646
729
|
ready = readers.dup
|
647
|
-
@
|
730
|
+
@after_worker_ready.call(self, worker)
|
648
731
|
|
649
732
|
begin
|
650
|
-
|
651
|
-
nr = 0
|
733
|
+
reopen = reopen_worker_logs(worker.nr) if reopen
|
652
734
|
worker.tick = time_now.to_i
|
653
|
-
|
654
|
-
while sock = tmp.shift
|
735
|
+
while sock = ready.shift
|
655
736
|
# Unicorn::Worker#kgio_tryaccept is not like accept(2) at all,
|
656
737
|
# but that will return false
|
657
738
|
if client = sock.kgio_tryaccept
|
658
739
|
process_client(client)
|
659
|
-
nr += 1
|
660
740
|
worker.tick = time_now.to_i
|
661
741
|
end
|
662
|
-
break if
|
742
|
+
break if reopen
|
663
743
|
end
|
664
744
|
|
665
|
-
#
|
666
|
-
# we're probably reasonably busy, so avoid calling select()
|
667
|
-
# and do a speculative non-blocking accept() on ready listeners
|
668
|
-
# before we sleep again in select().
|
669
|
-
unless nr == 0
|
670
|
-
tmp = ready.dup
|
671
|
-
redo
|
672
|
-
end
|
673
|
-
|
674
|
-
ppid == Process.ppid or return
|
675
|
-
|
676
|
-
# timeout used so we can detect parent death:
|
745
|
+
# timeout so we can .tick and keep parent from SIGKILL-ing us
|
677
746
|
worker.tick = time_now.to_i
|
678
|
-
|
747
|
+
waiter.get_readers(ready, readers, @timeout)
|
679
748
|
rescue => e
|
680
|
-
redo if
|
749
|
+
redo if reopen && readers[0]
|
681
750
|
Unicorn.log_error(@logger, "listen loop error", e) if readers[0]
|
682
751
|
end while readers[0]
|
683
752
|
end
|
@@ -714,11 +783,11 @@ class Unicorn::HttpServer
|
|
714
783
|
wpid <= 0 and return
|
715
784
|
Process.kill(0, wpid)
|
716
785
|
wpid
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
786
|
+
rescue Errno::EPERM
|
787
|
+
logger.info "pid=#{path} possibly stale, got EPERM signalling PID:#{wpid}"
|
788
|
+
nil
|
789
|
+
rescue Errno::ESRCH, Errno::ENOENT
|
790
|
+
# don't unlink stale pid files, racy without non-portable locking...
|
722
791
|
end
|
723
792
|
|
724
793
|
def load_config!
|
@@ -744,12 +813,12 @@ class Unicorn::HttpServer
|
|
744
813
|
end
|
745
814
|
|
746
815
|
def build_app!
|
747
|
-
if app.respond_to?(:arity) && app.arity == 0
|
816
|
+
if app.respond_to?(:arity) && (app.arity == 0 || app.arity == 2)
|
748
817
|
if defined?(Gem) && Gem.respond_to?(:refresh)
|
749
818
|
logger.info "Refreshing Gem list"
|
750
819
|
Gem.refresh
|
751
820
|
end
|
752
|
-
self.app = app.call
|
821
|
+
self.app = app.arity == 0 ? app.call : app.call(nil, self)
|
753
822
|
end
|
754
823
|
end
|
755
824
|
|
data/lib/unicorn/launcher.rb
CHANGED
data/lib/unicorn/oob_gc.rb
CHANGED
@@ -43,8 +43,9 @@
|
|
43
43
|
# use Unicorn::OobGC, 2, %r{\A/(?:expensive/foo|more_expensive/foo)}
|
44
44
|
#
|
45
45
|
# Feedback from users of early implementations of this module:
|
46
|
-
# *
|
47
|
-
# *
|
46
|
+
# * https://yhbt.net/unicorn-public/0BFC98E9-072B-47EE-9A70-05478C20141B@lukemelia.com/
|
47
|
+
# * https://yhbt.net/unicorn-public/AANLkTilUbgdyDv9W1bi-s_W6kq9sOhWfmuYkKLoKGOLj@mail.gmail.com/
|
48
|
+
|
48
49
|
module Unicorn::OobGC
|
49
50
|
|
50
51
|
# this pretends to be Rack middleware because it used to be
|
@@ -59,18 +60,17 @@ module Unicorn::OobGC
|
|
59
60
|
self.const_set :OOBGC_INTERVAL, interval
|
60
61
|
ObjectSpace.each_object(Unicorn::HttpServer) do |s|
|
61
62
|
s.extend(self)
|
62
|
-
self.const_set :OOBGC_ENV, s.instance_variable_get(:@request).env
|
63
63
|
end
|
64
64
|
app # pretend to be Rack middleware since it was in the past
|
65
65
|
end
|
66
66
|
|
67
67
|
#:stopdoc:
|
68
|
-
PATH_INFO = "PATH_INFO"
|
69
68
|
def process_client(client)
|
70
69
|
super(client) # Unicorn::HttpServer#process_client
|
71
|
-
|
70
|
+
env = instance_variable_get(:@request).env
|
71
|
+
if OOBGC_PATH =~ env['PATH_INFO'] && ((@@nr -= 1) <= 0)
|
72
72
|
@@nr = OOBGC_INTERVAL
|
73
|
-
|
73
|
+
env.clear
|
74
74
|
disabled = GC.enable
|
75
75
|
GC.start
|
76
76
|
GC.disable if disabled
|
@@ -3,6 +3,18 @@
|
|
3
3
|
require 'socket'
|
4
4
|
|
5
5
|
module Unicorn
|
6
|
+
|
7
|
+
# Instead of using a generic Kgio::Socket for everything,
|
8
|
+
# tag TCP sockets so we can use TCP_INFO under Linux without
|
9
|
+
# incurring extra syscalls for Unix domain sockets.
|
10
|
+
# TODO: remove these when we remove kgio
|
11
|
+
TCPClient = Class.new(Kgio::Socket) # :nodoc:
|
12
|
+
class TCPSrv < Kgio::TCPServer # :nodoc:
|
13
|
+
def kgio_tryaccept # :nodoc:
|
14
|
+
super(TCPClient)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
6
18
|
module SocketHelper
|
7
19
|
|
8
20
|
# internal interface
|
@@ -63,12 +75,16 @@ module Unicorn
|
|
63
75
|
elsif respond_to?(:accf_arg)
|
64
76
|
name = opt[:accept_filter]
|
65
77
|
name = DEFAULTS[:accept_filter] if name.nil?
|
78
|
+
sock.listen(opt[:backlog])
|
79
|
+
got = (sock.getsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER) rescue nil).to_s
|
80
|
+
arg = accf_arg(name)
|
66
81
|
begin
|
67
|
-
sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER,
|
82
|
+
sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER, arg)
|
68
83
|
rescue => e
|
69
84
|
logger.error("#{sock_name(sock)} " \
|
70
85
|
"failed to set accept_filter=#{name} (#{e.inspect})")
|
71
|
-
|
86
|
+
logger.error("perhaps accf_http(9) needs to be loaded".freeze)
|
87
|
+
end if arg != got
|
72
88
|
end
|
73
89
|
end
|
74
90
|
|
@@ -85,8 +101,8 @@ module Unicorn
|
|
85
101
|
log_buffer_sizes(sock, " after: ")
|
86
102
|
end
|
87
103
|
sock.listen(opt[:backlog])
|
88
|
-
|
89
|
-
|
104
|
+
rescue => e
|
105
|
+
Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
|
90
106
|
end
|
91
107
|
|
92
108
|
def log_buffer_sizes(sock, pfx = '')
|
@@ -101,7 +117,7 @@ module Unicorn
|
|
101
117
|
def bind_listen(address = '0.0.0.0:8080', opt = {})
|
102
118
|
return address unless String === address
|
103
119
|
|
104
|
-
sock = if address
|
120
|
+
sock = if address.start_with?('/')
|
105
121
|
if File.exist?(address)
|
106
122
|
if File.socket?(address)
|
107
123
|
begin
|
@@ -148,7 +164,7 @@ module Unicorn
|
|
148
164
|
end
|
149
165
|
sock.bind(Socket.pack_sockaddr_in(port, addr))
|
150
166
|
sock.autoclose = false
|
151
|
-
|
167
|
+
TCPSrv.for_fd(sock.fileno)
|
152
168
|
end
|
153
169
|
|
154
170
|
# returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
|
@@ -185,7 +201,7 @@ module Unicorn
|
|
185
201
|
def server_cast(sock)
|
186
202
|
begin
|
187
203
|
Socket.unpack_sockaddr_in(sock.getsockname)
|
188
|
-
|
204
|
+
TCPSrv.for_fd(sock.fileno)
|
189
205
|
rescue ArgumentError
|
190
206
|
Kgio::UNIXServer.for_fd(sock.fileno)
|
191
207
|
end
|
data/lib/unicorn/stream_input.rb
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
|
3
|
-
# When processing uploads,
|
4
|
-
# "rack.input" of the
|
3
|
+
# When processing uploads, unicorn may expose a StreamInput object under
|
4
|
+
# "rack.input" of the Rack environment when
|
5
|
+
# Unicorn::Configurator#rewindable_input is set to +false+
|
5
6
|
class Unicorn::StreamInput
|
6
7
|
# The I/O chunk size (in +bytes+) for I/O operations where
|
7
8
|
# the size cannot be user-specified when a method is called.
|
8
9
|
# The default is 16 kilobytes.
|
9
|
-
@@io_chunk_size = Unicorn::Const::CHUNK_SIZE
|
10
|
+
@@io_chunk_size = Unicorn::Const::CHUNK_SIZE # :nodoc:
|
10
11
|
|
11
12
|
# Initializes a new StreamInput object. You normally do not have to call
|
12
13
|
# this unless you are writing an HTTP server.
|
13
|
-
def initialize(socket, request)
|
14
|
+
def initialize(socket, request) # :nodoc:
|
14
15
|
@chunked = request.content_length.nil?
|
15
16
|
@socket = socket
|
16
17
|
@parser = request
|
data/lib/unicorn/tee_input.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
|
3
|
-
#
|
3
|
+
# Acts like tee(1) on an input input to provide a input-like stream
|
4
4
|
# while providing rewindable semantics through a File/StringIO backing
|
5
5
|
# store. On the first pass, the input is only read on demand so your
|
6
6
|
# Rack application can use input notification (upload progress and
|
@@ -9,22 +9,22 @@
|
|
9
9
|
# strict interpretation of Rack::Lint::InputWrapper functionality and
|
10
10
|
# will not support any deviations from it.
|
11
11
|
#
|
12
|
-
# When processing uploads,
|
13
|
-
# "rack.input" of the Rack environment.
|
12
|
+
# When processing uploads, unicorn exposes a TeeInput object under
|
13
|
+
# "rack.input" of the Rack environment by default.
|
14
14
|
class Unicorn::TeeInput < Unicorn::StreamInput
|
15
15
|
# The maximum size (in +bytes+) to buffer in memory before
|
16
16
|
# resorting to a temporary file. Default is 112 kilobytes.
|
17
|
-
@@client_body_buffer_size = Unicorn::Const::MAX_BODY
|
17
|
+
@@client_body_buffer_size = Unicorn::Const::MAX_BODY # :nodoc:
|
18
18
|
|
19
19
|
# sets the maximum size of request bodies to buffer in memory,
|
20
20
|
# amounts larger than this are buffered to the filesystem
|
21
|
-
def self.client_body_buffer_size=(bytes)
|
21
|
+
def self.client_body_buffer_size=(bytes) # :nodoc:
|
22
22
|
@@client_body_buffer_size = bytes
|
23
23
|
end
|
24
24
|
|
25
25
|
# returns the maximum size of request bodies to buffer in memory,
|
26
26
|
# amounts larger than this are buffered to the filesystem
|
27
|
-
def self.client_body_buffer_size
|
27
|
+
def self.client_body_buffer_size # :nodoc:
|
28
28
|
@@client_body_buffer_size
|
29
29
|
end
|
30
30
|
|
@@ -37,7 +37,7 @@ class Unicorn::TeeInput < Unicorn::StreamInput
|
|
37
37
|
|
38
38
|
# Initializes a new TeeInput object. You normally do not have to call
|
39
39
|
# this unless you are writing an HTTP server.
|
40
|
-
def initialize(socket, request)
|
40
|
+
def initialize(socket, request) # :nodoc:
|
41
41
|
@len = request.content_length
|
42
42
|
super
|
43
43
|
@tmp = @len && @len <= @@client_body_buffer_size ?
|
@@ -125,9 +125,7 @@ private
|
|
125
125
|
end
|
126
126
|
|
127
127
|
def tee(buffer)
|
128
|
-
|
129
|
-
@tmp.write(buffer)
|
130
|
-
end
|
128
|
+
@tmp.write(buffer) if buffer
|
131
129
|
buffer
|
132
130
|
end
|
133
131
|
end
|
data/lib/unicorn/tmpio.rb
CHANGED
@@ -11,12 +11,18 @@ class Unicorn::TmpIO < File
|
|
11
11
|
# immediately, switched to binary mode, and userspace output
|
12
12
|
# buffering is disabled
|
13
13
|
def self.new
|
14
|
+
path = nil
|
15
|
+
|
16
|
+
# workaround File#path being tainted:
|
17
|
+
# https://bugs.ruby-lang.org/issues/14485
|
14
18
|
fp = begin
|
15
|
-
|
19
|
+
path = "#{Dir::tmpdir}/#{rand}"
|
20
|
+
super(path, RDWR|CREAT|EXCL, 0600)
|
16
21
|
rescue Errno::EEXIST
|
17
22
|
retry
|
18
23
|
end
|
19
|
-
|
24
|
+
|
25
|
+
unlink(path)
|
20
26
|
fp.binmode
|
21
27
|
fp.sync = true
|
22
28
|
fp
|
data/lib/unicorn/util.rb
CHANGED
@@ -11,8 +11,8 @@ module Unicorn::Util # :nodoc:
|
|
11
11
|
fp.stat.file? &&
|
12
12
|
fp.sync &&
|
13
13
|
(fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
|
14
|
-
|
15
|
-
|
14
|
+
rescue IOError, Errno::EBADF
|
15
|
+
false
|
16
16
|
end
|
17
17
|
|
18
18
|
def self.chown_logs(uid, gid)
|
@@ -64,7 +64,7 @@ module Unicorn::Util # :nodoc:
|
|
64
64
|
fp.reopen(fp.path, "a")
|
65
65
|
else
|
66
66
|
# We should not need this workaround, Ruby can be fixed:
|
67
|
-
#
|
67
|
+
# https://bugs.ruby-lang.org/issues/9036
|
68
68
|
# MRI will not call call fclose(3) or freopen(3) here
|
69
69
|
# since there's no associated std{in,out,err} FILE * pointer
|
70
70
|
# This should atomically use dup3(2) (or dup2(2)) syscall
|
data/lib/unicorn/version.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
Unicorn::Const::UNICORN_VERSION = '
|
1
|
+
Unicorn::Const::UNICORN_VERSION = '6.1.0'
|