unicorn-shopify 5.2.0.6 → 5.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.olddoc.yml +0 -1
- data/GIT-VERSION-GEN +1 -1
- data/HACKING +0 -1
- data/ISSUES +12 -6
- data/Links +1 -1
- data/lib/unicorn.rb +1 -1
- data/lib/unicorn/configurator.rb +22 -7
- data/lib/unicorn/http_request.rb +35 -15
- data/lib/unicorn/http_server.rb +61 -17
- data/lib/unicorn/socket_helper.rb +5 -2
- data/lib/unicorn/stream_input.rb +5 -4
- data/lib/unicorn/tee_input.rb +7 -7
- data/lib/unicorn/worker.rb +7 -3
- data/t/test-lib.sh +2 -2
- data/test/exec/test_exec.rb +6 -5
- data/test/unit/test_http_parser.rb +0 -18
- data/test/unit/test_socket_helper.rb +8 -4
- data/test/unit/test_util.rb +2 -2
- data/unicorn.gemspec +10 -12
- metadata +3 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b4f49b8a39233e5173cd9449ac97280d9981fbaf
|
4
|
+
data.tar.gz: 7d59709e0d4a4670a1d549f930e6f1557b3b986e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ef1af3b1aec6f855844103eefef52848fae3af4dd2ecb73dc47a3df0da6d826a436a790a52935a31d9af22d6506148411dc5d08fe981a3ffb9c73500ddfbb240
|
7
|
+
data.tar.gz: 9b6af6386d99ab1cbc9b872b5a296240e11f68e59e12966a6d546c0d373749b884b1ba3bee3c49ef99777937ef879cf3bb0179ee78b3e2bd082c032c4d6deef4
|
data/.olddoc.yml
CHANGED
@@ -12,7 +12,6 @@ noindex:
|
|
12
12
|
- TODO
|
13
13
|
- unicorn_rails_1
|
14
14
|
public_email: unicorn-public@bogomips.org
|
15
|
-
private_email: unicorn@bogomips.org
|
16
15
|
nntp_url:
|
17
16
|
- nntp://news.public-inbox.org/inbox.comp.lang.ruby.unicorn
|
18
17
|
- nntp://news.gmane.org/gmane.comp.lang.ruby.unicorn.general
|
data/GIT-VERSION-GEN
CHANGED
data/HACKING
CHANGED
@@ -104,7 +104,6 @@ don't email the git mailing list or maintainer with Unicorn patches :)
|
|
104
104
|
|
105
105
|
In order to build the gem, you must install the following components:
|
106
106
|
|
107
|
-
* olddoc (RubyGem)
|
108
107
|
* pandoc
|
109
108
|
|
110
109
|
You can build the Unicorn gem with the following command:
|
data/ISSUES
CHANGED
@@ -9,14 +9,16 @@ submit patches and/or obtain support after you have searched the
|
|
9
9
|
* Cc: all participants in a thread or commit, as subscription is optional
|
10
10
|
* Do not {top post}[http://catb.org/jargon/html/T/top-post.html] in replies
|
11
11
|
* Quote as little as possible of the message you're replying to
|
12
|
-
* Do not send HTML mail or images,
|
13
|
-
|
12
|
+
* Do not send HTML mail or images,
|
13
|
+
they hurt reader privacy and will be flagged as spam
|
14
|
+
* Anonymous and pseudonymous messages will ALWAYS be welcome
|
14
15
|
* The email submission port (587) is enabled on the bogomips.org MX:
|
15
16
|
https://bogomips.org/unicorn-public/20141004232241.GA23908@dcvr.yhbt.net/t/
|
16
17
|
|
17
18
|
If your issue is of a sensitive nature or you're just shy in public,
|
18
|
-
|
19
|
-
|
19
|
+
use anonymity tools such as Tor or Mixmaster; and rely on the public
|
20
|
+
mail archives for responses. Be sure to scrub sensitive log messages
|
21
|
+
and such.
|
20
22
|
|
21
23
|
If you don't get a response within a few days, we may have forgotten
|
22
24
|
about it so feel free to ask again.
|
@@ -64,14 +66,14 @@ document distributed with git) on guidelines for patch submission.
|
|
64
66
|
== Contact Info
|
65
67
|
|
66
68
|
* public: mailto:unicorn-public@bogomips.org
|
67
|
-
* private: mailto:unicorn@bogomips.org
|
68
69
|
* nntp://news.gmane.org/gmane.comp.lang.ruby.unicorn.general
|
69
70
|
* nntp://news.public-inbox.org/inbox.comp.lang.ruby.unicorn
|
70
71
|
* https://bogomips.org/unicorn-public/
|
72
|
+
* http://ou63pmih66umazou.onion/unicorn-public/
|
71
73
|
|
72
74
|
Mailing list subscription is optional, so Cc: all participants.
|
73
75
|
|
74
|
-
You can follow along via NNTP:
|
76
|
+
You can follow along via NNTP (read-only):
|
75
77
|
|
76
78
|
nntp://news.public-inbox.org/inbox.comp.lang.ruby.unicorn
|
77
79
|
nntp://news.gmane.org/gmane.comp.lang.ruby.unicorn.general
|
@@ -79,6 +81,7 @@ You can follow along via NNTP:
|
|
79
81
|
Or Atom feeds:
|
80
82
|
|
81
83
|
https://bogomips.org/unicorn-public/new.atom
|
84
|
+
http://ou63pmih66umazou.onion/unicorn-public/new.atom
|
82
85
|
|
83
86
|
The HTML archives at https://bogomips.org/unicorn-public/
|
84
87
|
also has links to per-thread Atom feeds and downloadable
|
@@ -88,3 +91,6 @@ You may optionally subscribe via plain-text email:
|
|
88
91
|
|
89
92
|
mailto:unicorn-public+subscribe@bogomips.org
|
90
93
|
(and confirming the auto-reply)
|
94
|
+
|
95
|
+
Just keep in mind we suck at delivering email, so using NNTP,
|
96
|
+
or Atom feeds might be a better bet...
|
data/Links
CHANGED
@@ -23,7 +23,7 @@ or services behind them.
|
|
23
23
|
* {golden_brindle}[https://github.com/simonoff/golden_brindle] - tool to
|
24
24
|
manage multiple unicorn instances/applications on a single server
|
25
25
|
|
26
|
-
* {raindrops}[
|
26
|
+
* {raindrops}[https://bogomips.org/raindrops/] - real-time stats for
|
27
27
|
preforking Rack servers
|
28
28
|
|
29
29
|
* {UnXF}[https://bogomips.org/unxf/] Un-X-Forward* the Rack environment,
|
data/lib/unicorn.rb
CHANGED
@@ -95,7 +95,7 @@ module Unicorn
|
|
95
95
|
|
96
96
|
# returns an array of strings representing TCP listen socket addresses
|
97
97
|
# and Unix domain socket paths. This is useful for use with
|
98
|
-
# Raindrops::Middleware under Linux:
|
98
|
+
# Raindrops::Middleware under Linux: https://bogomips.org/raindrops/
|
99
99
|
def self.listener_names
|
100
100
|
Unicorn::HttpServer::LISTENERS.map do |io|
|
101
101
|
Unicorn::SocketHelper.sock_name(io)
|
data/lib/unicorn/configurator.rb
CHANGED
@@ -54,9 +54,10 @@ class Unicorn::Configurator
|
|
54
54
|
},
|
55
55
|
:before_murder => nil,
|
56
56
|
:pid => nil,
|
57
|
+
:worker_exec => false,
|
57
58
|
:preload_app => false,
|
58
59
|
:check_client_connection => false,
|
59
|
-
:rewindable_input => true,
|
60
|
+
:rewindable_input => true,
|
60
61
|
:client_body_buffer_size => Unicorn::Const::MAX_BODY,
|
61
62
|
}
|
62
63
|
#:startdoc:
|
@@ -172,6 +173,8 @@ class Unicorn::Configurator
|
|
172
173
|
# server.logger.error("worker process failure: #{status.inspect}")
|
173
174
|
# end
|
174
175
|
# end
|
176
|
+
#
|
177
|
+
# after_worker_exit is only available in unicorn 5.3.0+
|
175
178
|
def after_worker_exit(*args, &block)
|
176
179
|
set_hook(:after_worker_exit, block_given? ? block : args[0], 3)
|
177
180
|
end
|
@@ -187,6 +190,8 @@ class Unicorn::Configurator
|
|
187
190
|
#
|
188
191
|
# Do not use Configurator#user if you rely on changing users in the
|
189
192
|
# after_worker_ready hook.
|
193
|
+
#
|
194
|
+
# after_worker_ready is only available in unicorn 5.3.0+
|
190
195
|
def after_worker_ready(*args, &block)
|
191
196
|
set_hook(:after_worker_ready, block_given? ? block : args[0])
|
192
197
|
end
|
@@ -263,6 +268,17 @@ class Unicorn::Configurator
|
|
263
268
|
set[:timeout] = seconds > max ? max : seconds
|
264
269
|
end
|
265
270
|
|
271
|
+
# Whether to exec in each worker process after forking. This changes the
|
272
|
+
# memory layout of each worker process, which is a security feature designed
|
273
|
+
# to defeat possible address space discovery attacks. Note that using
|
274
|
+
# worker_exec only makes sense if you are not preloading the application,
|
275
|
+
# and will result in higher memory usage.
|
276
|
+
#
|
277
|
+
# worker_exec is only available in unicorn 5.3.0+
|
278
|
+
def worker_exec(bool)
|
279
|
+
set_bool(:worker_exec, bool)
|
280
|
+
end
|
281
|
+
|
266
282
|
# sets the current number of worker_processes to +nr+. Each worker
|
267
283
|
# process will serve exactly one client at a time. You can
|
268
284
|
# increment or decrement this value at runtime by sending SIGTTIN
|
@@ -529,13 +545,12 @@ class Unicorn::Configurator
|
|
529
545
|
# Disabling rewindability can improve performance by lowering
|
530
546
|
# I/O and memory usage for applications that accept uploads.
|
531
547
|
# Keep in mind that the Rack 1.x spec requires
|
532
|
-
# \env[\"rack.input\"] to be rewindable,
|
533
|
-
#
|
548
|
+
# \env[\"rack.input\"] to be rewindable,
|
549
|
+
# but the Rack 2.x spec does not.
|
534
550
|
#
|
535
|
-
# +rewindable_input+ defaults to +true+
|
536
|
-
#
|
537
|
-
#
|
538
|
-
# (less demanding) spec.
|
551
|
+
# +rewindable_input+ defaults to +true+ for compatibility.
|
552
|
+
# Setting it to +false+ may be safe for applications and
|
553
|
+
# frameworks developed for Rack 2.x and later.
|
539
554
|
def rewindable_input(bool)
|
540
555
|
set_bool(:rewindable_input, bool)
|
541
556
|
end
|
data/lib/unicorn/http_request.rb
CHANGED
@@ -29,7 +29,7 @@ class Unicorn::HttpParser
|
|
29
29
|
EMPTY_ARRAY = [].freeze
|
30
30
|
@@input_class = Unicorn::TeeInput
|
31
31
|
@@check_client_connection = false
|
32
|
-
@@tcpi_inspect_ok =
|
32
|
+
@@tcpi_inspect_ok = Socket.const_defined?(:TCP_INFO)
|
33
33
|
|
34
34
|
def self.input_class
|
35
35
|
@@input_class
|
@@ -105,26 +105,47 @@ class Unicorn::HttpParser
|
|
105
105
|
env.include?('rack.hijack_io'.freeze)
|
106
106
|
end
|
107
107
|
|
108
|
-
if
|
108
|
+
if Raindrops.const_defined?(:TCP_Info)
|
109
|
+
TCPI = Raindrops::TCP_Info.allocate
|
110
|
+
|
109
111
|
def check_client_connection(socket) # :nodoc:
|
110
112
|
if Unicorn::TCPClient === socket
|
111
|
-
|
112
|
-
@@tcp_info.get!(socket)
|
113
|
+
# Raindrops::TCP_Info#get!, #state (reads struct tcp_info#tcpi_state)
|
113
114
|
raise Errno::EPIPE, "client closed connection".freeze,
|
114
|
-
EMPTY_ARRAY if closed_state?(
|
115
|
+
EMPTY_ARRAY if closed_state?(TCPI.get!(socket).state)
|
115
116
|
else
|
116
117
|
write_http_header(socket)
|
117
118
|
end
|
118
119
|
end
|
119
120
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
121
|
+
if Raindrops.const_defined?(:TCP)
|
122
|
+
# raindrops 0.18.0+ supports FreeBSD + Linux using the same names
|
123
|
+
# Evaluate these hash lookups at load time so we can
|
124
|
+
# generate an opt_case_dispatch instruction
|
125
|
+
eval <<-EOS
|
126
|
+
def closed_state?(state) # :nodoc:
|
127
|
+
case state
|
128
|
+
when #{Raindrops::TCP[:ESTABLISHED]}
|
129
|
+
false
|
130
|
+
when #{Raindrops::TCP.values_at(
|
131
|
+
:CLOSE_WAIT, :TIME_WAIT, :CLOSE, :LAST_ACK, :CLOSING).join(',')}
|
132
|
+
true
|
133
|
+
else
|
134
|
+
false
|
135
|
+
end
|
136
|
+
end
|
137
|
+
EOS
|
138
|
+
else
|
139
|
+
# raindrops before 0.18 only supported TCP_INFO under Linux
|
140
|
+
def closed_state?(state) # :nodoc:
|
141
|
+
case state
|
142
|
+
when 1 # ESTABLISHED
|
143
|
+
false
|
144
|
+
when 8, 6, 7, 9, 11 # CLOSE_WAIT, TIME_WAIT, CLOSE, LAST_ACK, CLOSING
|
145
|
+
true
|
146
|
+
else
|
147
|
+
false
|
148
|
+
end
|
128
149
|
end
|
129
150
|
end
|
130
151
|
else
|
@@ -134,9 +155,8 @@ class Unicorn::HttpParser
|
|
134
155
|
# work after a client gives up.
|
135
156
|
def check_client_connection(socket) # :nodoc:
|
136
157
|
if Unicorn::TCPClient === socket && @@tcpi_inspect_ok
|
137
|
-
opt = socket.getsockopt(
|
158
|
+
opt = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO).inspect
|
138
159
|
if opt =~ /\bstate=(\S+)/
|
139
|
-
@@tcpi_inspect_ok = true
|
140
160
|
raise Errno::EPIPE, "client closed connection".freeze,
|
141
161
|
EMPTY_ARRAY if closed_state_str?($1)
|
142
162
|
else
|
data/lib/unicorn/http_server.rb
CHANGED
@@ -92,7 +92,8 @@ class Unicorn::HttpServer
|
|
92
92
|
@self_pipe = []
|
93
93
|
@workers = {} # hash maps PIDs to Workers
|
94
94
|
@sig_queue = [] # signal queue used for self-piping
|
95
|
-
@
|
95
|
+
@pid = nil
|
96
|
+
@before_murder_called = Set.new() # ensures before_murder is called only once
|
96
97
|
|
97
98
|
# we try inheriting listeners first, so we bind them later.
|
98
99
|
# we don't write the pid file until we've bound listeners in case
|
@@ -109,6 +110,14 @@ class Unicorn::HttpServer
|
|
109
110
|
# list of signals we care about and trap in master.
|
110
111
|
@queue_sigs = [
|
111
112
|
:WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ]
|
113
|
+
|
114
|
+
@worker_data = if worker_data = ENV['UNICORN_WORKER']
|
115
|
+
worker_data = worker_data.split(',').map!(&:to_i)
|
116
|
+
worker_data[1] = worker_data.slice!(1..2).map do |i|
|
117
|
+
Kgio::Pipe.for_fd(i)
|
118
|
+
end
|
119
|
+
worker_data
|
120
|
+
end
|
112
121
|
end
|
113
122
|
|
114
123
|
# Runs the thing. Returns self so you can run join on it
|
@@ -117,7 +126,7 @@ class Unicorn::HttpServer
|
|
117
126
|
# this pipe is used to wake us up from select(2) in #join when signals
|
118
127
|
# are trapped. See trap_deferred.
|
119
128
|
@self_pipe.replace(Unicorn.pipe)
|
120
|
-
@master_pid = $$
|
129
|
+
@master_pid = @worker_data ? Process.ppid : $$
|
121
130
|
|
122
131
|
# setup signal handlers before writing pid file in case people get
|
123
132
|
# trigger happy and send signals as soon as the pid file exists.
|
@@ -435,11 +444,7 @@ class Unicorn::HttpServer
|
|
435
444
|
end
|
436
445
|
|
437
446
|
@reexec_pid = fork do
|
438
|
-
listener_fds =
|
439
|
-
LISTENERS.each do |sock|
|
440
|
-
sock.close_on_exec = false
|
441
|
-
listener_fds[sock.fileno] = sock
|
442
|
-
end
|
447
|
+
listener_fds = listener_sockets
|
443
448
|
ENV['UNICORN_FD'] = listener_fds.keys.join(',')
|
444
449
|
Dir.chdir(START_CTX[:cwd])
|
445
450
|
cmd = [ START_CTX[0] ].concat(START_CTX[:argv])
|
@@ -447,12 +452,7 @@ class Unicorn::HttpServer
|
|
447
452
|
# avoid leaking FDs we don't know about, but let before_exec
|
448
453
|
# unset FD_CLOEXEC, if anything else in the app eventually
|
449
454
|
# relies on FD inheritence.
|
450
|
-
(
|
451
|
-
next if listener_fds.include?(io)
|
452
|
-
io = IO.for_fd(io) rescue next
|
453
|
-
io.autoclose = false
|
454
|
-
io.close_on_exec = true
|
455
|
-
end
|
455
|
+
close_sockets_on_exec(listener_fds)
|
456
456
|
|
457
457
|
# exec(command, hash) works in at least 1.9.1+, but will only be
|
458
458
|
# required in 1.9.4/2.0.0 at earliest.
|
@@ -464,6 +464,40 @@ class Unicorn::HttpServer
|
|
464
464
|
proc_name 'master (old)'
|
465
465
|
end
|
466
466
|
|
467
|
+
def worker_spawn(worker)
|
468
|
+
listener_fds = listener_sockets
|
469
|
+
env = {}
|
470
|
+
env['UNICORN_FD'] = listener_fds.keys.join(',')
|
471
|
+
|
472
|
+
listener_fds[worker.to_io.fileno] = worker.to_io
|
473
|
+
listener_fds[worker.master.fileno] = worker.master
|
474
|
+
|
475
|
+
worker_info = [worker.nr, worker.to_io.fileno, worker.master.fileno]
|
476
|
+
env['UNICORN_WORKER'] = worker_info.join(',')
|
477
|
+
|
478
|
+
close_sockets_on_exec(listener_fds)
|
479
|
+
|
480
|
+
Process.spawn(env, START_CTX[0], *START_CTX[:argv], listener_fds)
|
481
|
+
end
|
482
|
+
|
483
|
+
def listener_sockets
|
484
|
+
listener_fds = {}
|
485
|
+
LISTENERS.each do |sock|
|
486
|
+
sock.close_on_exec = false
|
487
|
+
listener_fds[sock.fileno] = sock
|
488
|
+
end
|
489
|
+
listener_fds
|
490
|
+
end
|
491
|
+
|
492
|
+
def close_sockets_on_exec(sockets)
|
493
|
+
(3..1024).each do |io|
|
494
|
+
next if sockets.include?(io)
|
495
|
+
io = IO.for_fd(io) rescue next
|
496
|
+
io.autoclose = false
|
497
|
+
io.close_on_exec = true
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
467
501
|
# forcibly terminate all workers that haven't checked in in timeout seconds. The timeout is implemented using an unlinked File
|
468
502
|
def murder_lazy_workers
|
469
503
|
next_sleep = @timeout - 1
|
@@ -502,19 +536,29 @@ class Unicorn::HttpServer
|
|
502
536
|
end
|
503
537
|
|
504
538
|
def spawn_missing_workers
|
539
|
+
if @worker_data
|
540
|
+
worker = Unicorn::Worker.new(*@worker_data)
|
541
|
+
after_fork_internal
|
542
|
+
worker_loop(worker)
|
543
|
+
exit
|
544
|
+
end
|
545
|
+
|
505
546
|
worker_nr = -1
|
506
547
|
until (worker_nr += 1) == @worker_processes
|
507
548
|
@workers.value?(worker_nr) and next
|
508
549
|
worker = Unicorn::Worker.new(worker_nr)
|
509
550
|
before_fork.call(self, worker)
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
551
|
+
|
552
|
+
pid = @worker_exec ? worker_spawn(worker) : fork
|
553
|
+
|
554
|
+
unless pid
|
514
555
|
after_fork_internal
|
515
556
|
worker_loop(worker)
|
516
557
|
exit
|
517
558
|
end
|
559
|
+
|
560
|
+
@workers[pid] = worker
|
561
|
+
worker.atfork_parent
|
518
562
|
end
|
519
563
|
rescue => e
|
520
564
|
@logger.error(e) rescue nil
|
@@ -75,12 +75,15 @@ module Unicorn
|
|
75
75
|
elsif respond_to?(:accf_arg)
|
76
76
|
name = opt[:accept_filter]
|
77
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)
|
78
81
|
begin
|
79
|
-
sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER,
|
82
|
+
sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER, arg)
|
80
83
|
rescue => e
|
81
84
|
logger.error("#{sock_name(sock)} " \
|
82
85
|
"failed to set accept_filter=#{name} (#{e.inspect})")
|
83
|
-
end
|
86
|
+
end if arg != got
|
84
87
|
end
|
85
88
|
end
|
86
89
|
|
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 ?
|
data/lib/unicorn/worker.rb
CHANGED
@@ -12,18 +12,19 @@ class Unicorn::Worker
|
|
12
12
|
# :stopdoc:
|
13
13
|
attr_accessor :nr, :switched
|
14
14
|
attr_reader :to_io # IO.select-compatible
|
15
|
+
attr_reader :master
|
15
16
|
|
16
17
|
PER_DROP = Raindrops::PAGE_SIZE / Raindrops::SIZE
|
17
18
|
DROPS = []
|
18
19
|
|
19
|
-
def initialize(nr)
|
20
|
+
def initialize(nr, pipe=nil)
|
20
21
|
drop_index = nr / PER_DROP
|
21
22
|
@raindrop = DROPS[drop_index] ||= Raindrops.new(PER_DROP)
|
22
23
|
@offset = nr % PER_DROP
|
23
24
|
@raindrop[@offset] = 0
|
24
25
|
@nr = nr
|
25
26
|
@switched = false
|
26
|
-
@to_io, @master = Unicorn.pipe
|
27
|
+
@to_io, @master = pipe || Unicorn.pipe
|
27
28
|
end
|
28
29
|
|
29
30
|
def atfork_child # :nodoc:
|
@@ -124,7 +125,10 @@ class Unicorn::Worker
|
|
124
125
|
# Any and all errors raised within this method will be propagated
|
125
126
|
# directly back to the caller (usually the +after_fork+ hook.
|
126
127
|
# These errors commonly include ArgumentError for specifying an
|
127
|
-
# invalid user/group and Errno::EPERM for insufficient privileges
|
128
|
+
# invalid user/group and Errno::EPERM for insufficient privileges.
|
129
|
+
#
|
130
|
+
# chroot support is only available in unicorn 5.3.0+
|
131
|
+
# user and group switching appeared in unicorn 0.94.0 (2009-11-05)
|
128
132
|
def user(user, group = nil, chroot = false)
|
129
133
|
# we do not protect the caller, checking Process.euid == 0 is
|
130
134
|
# insufficient because modern systems have fine-grained
|
data/t/test-lib.sh
CHANGED
@@ -106,8 +106,8 @@ check_stderr () {
|
|
106
106
|
# unicorn_setup
|
107
107
|
unicorn_setup () {
|
108
108
|
eval $(unused_listen)
|
109
|
-
port=$(expr $listen : '[^:]*:\([0-9]
|
110
|
-
host=$(expr $listen : '\([^:]*\):[0-9]
|
109
|
+
port=$(expr $listen : '[^:]*:\([0-9]*\)')
|
110
|
+
host=$(expr $listen : '\([^:][^:]*\):[0-9][0-9]*')
|
111
111
|
|
112
112
|
rtmpfiles unicorn_config pid r_err r_out fifo tmp ok
|
113
113
|
cat > $unicorn_config <<EOF
|
data/test/exec/test_exec.rb
CHANGED
@@ -97,6 +97,9 @@ run lambda { |env|
|
|
97
97
|
end
|
98
98
|
|
99
99
|
def test_sd_listen_fds_emulation
|
100
|
+
# [ruby-core:69895] [Bug #11336] fixed by r51576
|
101
|
+
return if RUBY_VERSION.to_f < 2.3
|
102
|
+
|
100
103
|
File.open("config.ru", "wb") { |fp| fp.write(HI) }
|
101
104
|
sock = TCPServer.new(@addr, @port)
|
102
105
|
|
@@ -119,14 +122,12 @@ run lambda { |env|
|
|
119
122
|
res = hit(["http://#@addr:#@port/"])
|
120
123
|
assert_equal [ "HI\n" ], res
|
121
124
|
assert_shutdown(pid)
|
122
|
-
|
125
|
+
assert sock.getsockopt(:SOL_SOCKET, :SO_KEEPALIVE).bool,
|
123
126
|
'unicorn should always set SO_KEEPALIVE on inherited sockets'
|
124
127
|
end
|
125
128
|
ensure
|
126
129
|
sock.close if sock
|
127
|
-
|
128
|
-
# [ruby-core:69895] [Bug #11336] fixed by r51576
|
129
|
-
end if RUBY_VERSION.to_f >= 2.3
|
130
|
+
end
|
130
131
|
|
131
132
|
def test_inherit_listener_unspecified
|
132
133
|
File.open("config.ru", "wb") { |fp| fp.write(HI) }
|
@@ -142,7 +143,7 @@ run lambda { |env|
|
|
142
143
|
res = hit(["http://#@addr:#@port/"])
|
143
144
|
assert_equal [ "HI\n" ], res
|
144
145
|
assert_shutdown(pid)
|
145
|
-
|
146
|
+
assert sock.getsockopt(:SOL_SOCKET, :SO_KEEPALIVE).bool,
|
146
147
|
'unicorn should always set SO_KEEPALIVE on inherited sockets'
|
147
148
|
ensure
|
148
149
|
sock.close if sock
|
@@ -851,24 +851,6 @@ class HttpParserTest < Test::Unit::TestCase
|
|
851
851
|
assert_equal '', parser.env['HTTP_HOST']
|
852
852
|
end
|
853
853
|
|
854
|
-
# so we don't care about the portability of this test
|
855
|
-
# if it doesn't leak on Linux, it won't leak anywhere else
|
856
|
-
# unless your C compiler or platform is otherwise broken
|
857
|
-
LINUX_PROC_PID_STATUS = "/proc/self/status"
|
858
|
-
def test_memory_leak
|
859
|
-
match_rss = /^VmRSS:\s+(\d+)/
|
860
|
-
if File.read(LINUX_PROC_PID_STATUS) =~ match_rss
|
861
|
-
before = $1.to_i
|
862
|
-
1000000.times { Unicorn::HttpParser.new }
|
863
|
-
File.read(LINUX_PROC_PID_STATUS) =~ match_rss
|
864
|
-
after = $1.to_i
|
865
|
-
diff = after - before
|
866
|
-
assert(diff < 10000, "memory grew more than 10M: #{diff}")
|
867
|
-
end
|
868
|
-
end if RUBY_PLATFORM =~ /linux/ &&
|
869
|
-
File.readable?(LINUX_PROC_PID_STATUS) &&
|
870
|
-
!defined?(RUBY_ENGINE)
|
871
|
-
|
872
854
|
def test_memsize
|
873
855
|
require 'objspace'
|
874
856
|
if ObjectSpace.respond_to?(:memsize_of)
|
@@ -150,28 +150,31 @@ class TestSocketHelper < Test::Unit::TestCase
|
|
150
150
|
end
|
151
151
|
|
152
152
|
def test_tcp_defer_accept_default
|
153
|
+
return unless defined?(TCP_DEFER_ACCEPT)
|
153
154
|
port = unused_port @test_addr
|
154
155
|
name = "#@test_addr:#{port}"
|
155
156
|
sock = bind_listen(name)
|
156
157
|
cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
|
157
158
|
assert cur >= 1
|
158
|
-
end
|
159
|
+
end
|
159
160
|
|
160
161
|
def test_tcp_defer_accept_disable
|
162
|
+
return unless defined?(TCP_DEFER_ACCEPT)
|
161
163
|
port = unused_port @test_addr
|
162
164
|
name = "#@test_addr:#{port}"
|
163
165
|
sock = bind_listen(name, :tcp_defer_accept => false)
|
164
166
|
cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
|
165
167
|
assert_equal 0, cur
|
166
|
-
end
|
168
|
+
end
|
167
169
|
|
168
170
|
def test_tcp_defer_accept_nr
|
171
|
+
return unless defined?(TCP_DEFER_ACCEPT)
|
169
172
|
port = unused_port @test_addr
|
170
173
|
name = "#@test_addr:#{port}"
|
171
174
|
sock = bind_listen(name, :tcp_defer_accept => 60)
|
172
175
|
cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
|
173
176
|
assert cur > 1
|
174
|
-
end
|
177
|
+
end
|
175
178
|
|
176
179
|
def test_ipv6only
|
177
180
|
port = begin
|
@@ -186,6 +189,7 @@ class TestSocketHelper < Test::Unit::TestCase
|
|
186
189
|
end
|
187
190
|
|
188
191
|
def test_reuseport
|
192
|
+
return unless defined?(Socket::SO_REUSEPORT)
|
189
193
|
port = unused_port @test_addr
|
190
194
|
name = "#@test_addr:#{port}"
|
191
195
|
sock = bind_listen(name, :reuseport => true)
|
@@ -193,5 +197,5 @@ class TestSocketHelper < Test::Unit::TestCase
|
|
193
197
|
assert_operator cur, :>, 0
|
194
198
|
rescue Errno::ENOPROTOOPT
|
195
199
|
# kernel does not support SO_REUSEPORT (older Linux)
|
196
|
-
end
|
200
|
+
end
|
197
201
|
end
|
data/test/unit/test_util.rb
CHANGED
@@ -69,7 +69,7 @@ class TestUtil < Test::Unit::TestCase
|
|
69
69
|
}
|
70
70
|
}
|
71
71
|
tmp.close!
|
72
|
-
end
|
72
|
+
end
|
73
73
|
|
74
74
|
def test_reopen_logs_renamed_with_internal_encoding
|
75
75
|
tmp = Tempfile.new('')
|
@@ -101,5 +101,5 @@ class TestUtil < Test::Unit::TestCase
|
|
101
101
|
}
|
102
102
|
}
|
103
103
|
tmp.close!
|
104
|
-
end
|
104
|
+
end
|
105
105
|
end
|
data/unicorn.gemspec
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
-
|
3
|
-
|
4
|
-
require 'olddoc'
|
5
|
-
extend Olddoc::Gemspec
|
6
|
-
name, summary, title = readme_metadata
|
2
|
+
manifest = File.exist?('.manifest') ?
|
3
|
+
IO.readlines('.manifest').map!(&:chomp!) : `git ls-files`.split("\n")
|
7
4
|
|
8
5
|
# don't bother with tests that fork, not worth our time to get working
|
9
6
|
# with `gem check -t` ... (of course we care for them when testing with
|
@@ -14,16 +11,18 @@ end.compact
|
|
14
11
|
|
15
12
|
Gem::Specification.new do |s|
|
16
13
|
s.name = %q{unicorn-shopify}
|
17
|
-
s.version = ENV[
|
18
|
-
s.authors = [
|
19
|
-
s.summary =
|
20
|
-
s.description =
|
14
|
+
s.version = (ENV['VERSION'] || '5.3.0').dup
|
15
|
+
s.authors = ['unicorn hackers']
|
16
|
+
s.summary = 'Rack HTTP server for fast clients and Unix'
|
17
|
+
s.description = File.read('README').split("\n\n")[1]
|
21
18
|
s.email = %q{unicorn-public@bogomips.org}
|
22
19
|
s.executables = %w(unicorn unicorn_rails)
|
23
20
|
s.extensions = %w(ext/unicorn_http/extconf.rb)
|
24
|
-
s.extra_rdoc_files =
|
21
|
+
s.extra_rdoc_files = IO.readlines('.document').map!(&:chomp!).keep_if do |f|
|
22
|
+
File.exist?(f)
|
23
|
+
end
|
25
24
|
s.files = manifest
|
26
|
-
s.homepage =
|
25
|
+
s.homepage = 'https://bogomips.org/unicorn/'
|
27
26
|
s.test_files = test_files
|
28
27
|
|
29
28
|
# technically we need ">= 1.9.3", too, but avoid the array here since
|
@@ -40,7 +39,6 @@ Gem::Specification.new do |s|
|
|
40
39
|
s.add_dependency(%q<raindrops>, '~> 0.7')
|
41
40
|
|
42
41
|
s.add_development_dependency('test-unit', '~> 3.0')
|
43
|
-
s.add_development_dependency('olddoc', '~> 1.2')
|
44
42
|
|
45
43
|
# Note: To avoid ambiguity, we intentionally avoid the SPDX-compatible
|
46
44
|
# 'Ruby' here since Ruby 1.9.3 switched to BSD-2-Clause, but we
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unicorn-shopify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- unicorn hackers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-04-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -66,20 +66,6 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '3.0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: olddoc
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '1.2'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '1.2'
|
83
69
|
description: |-
|
84
70
|
unicorn is an HTTP server for Rack applications designed to only serve
|
85
71
|
fast clients on low-latency, high-bandwidth connections and take
|
@@ -297,7 +283,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
297
283
|
version: '0'
|
298
284
|
requirements: []
|
299
285
|
rubyforge_project:
|
300
|
-
rubygems_version: 2.6.
|
286
|
+
rubygems_version: 2.6.10
|
301
287
|
signing_key:
|
302
288
|
specification_version: 4
|
303
289
|
summary: Rack HTTP server for fast clients and Unix
|