yahns 1.14.1 → 1.18.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/.document +2 -0
- data/.gitignore +0 -1
- data/.olddoc.yml +3 -2
- data/Documentation/GNUmakefile +1 -1
- data/Documentation/design_notes.txt +6 -3
- data/Documentation/yahns-rackup.pod +7 -3
- data/Documentation/yahns.pod +1 -1
- data/Documentation/yahns_config.pod +10 -10
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +3 -3
- data/HACKING +13 -13
- data/NEWS +982 -829
- data/README +11 -12
- data/Rakefile +121 -5
- data/examples/https_proxy_pass.conf.rb +36 -0
- data/examples/logrotate.conf +1 -1
- data/examples/proxy_pass.ru +11 -0
- data/extras/autoindex.rb +20 -4
- data/extras/exec_cgi.rb +38 -24
- data/extras/proxy_pass.rb +7 -6
- data/extras/try_gzip_static.rb +4 -1
- data/lib/yahns/acceptor.rb +3 -3
- data/lib/yahns/chunk_body.rb +2 -1
- data/lib/yahns/config.rb +10 -5
- data/lib/yahns/daemon.rb +0 -1
- data/lib/yahns/http_client.rb +28 -18
- data/lib/yahns/http_response.rb +3 -4
- data/lib/yahns/openssl_client.rb +33 -11
- data/lib/yahns/proxy_http_response.rb +3 -1
- data/lib/yahns/proxy_pass.rb +68 -10
- data/lib/yahns/queue_epoll.rb +4 -0
- data/lib/yahns/queue_kqueue.rb +0 -6
- data/lib/yahns/queue_quitter_pipe.rb +4 -1
- data/lib/yahns/rackup_handler.rb +3 -7
- data/lib/yahns/server.rb +47 -27
- data/lib/yahns/server_mp.rb +3 -4
- data/lib/yahns/sigevent_efd.rb +0 -1
- data/lib/yahns/sigevent_pipe.rb +13 -6
- data/lib/yahns/socket_helper.rb +1 -1
- data/lib/yahns/stream_input.rb +3 -2
- data/lib/yahns/tee_input.rb +1 -3
- data/lib/yahns/version.rb +1 -1
- data/lib/yahns/wbuf.rb +10 -3
- data/lib/yahns/worker.rb +8 -0
- data/lib/yahns.rb +12 -7
- data/man/yahns-rackup.1 +17 -17
- data/man/yahns.1 +11 -15
- data/man/yahns_config.5 +31 -31
- data/test/helper.rb +6 -2
- data/test/server_helper.rb +20 -5
- data/test/test_bin.rb +33 -30
- data/test/test_config.rb +2 -2
- data/test/test_extras_exec_cgi.rb +24 -1
- data/test/test_extras_try_gzip_static.rb +1 -1
- data/test/test_mt_accept.rb +0 -2
- data/test/test_proxy_pass.rb +1 -2
- data/test/test_proxy_pass_no_buffering.rb +1 -1
- data/test/test_rack_env.rb +58 -0
- data/test/test_serve_static.rb +0 -1
- data/test/test_server.rb +1 -4
- data/test/test_ssl.rb +2 -0
- data/test/test_unix_socket.rb +1 -3
- data/test/test_wbuf.rb +1 -1
- data/yahns.gemspec +8 -5
- metadata +12 -9
data/lib/yahns/http_response.rb
CHANGED
@@ -46,10 +46,9 @@ module Yahns::HttpResponse # :nodoc:
|
|
46
46
|
@hs.response_start_sent ? ''.freeze : 'HTTP/1.1 '.freeze
|
47
47
|
end
|
48
48
|
|
49
|
-
def response_wait_write(rv)
|
50
|
-
# call the kgio_wait_readable or kgio_wait_writable method
|
51
|
-
ok = __send__("kgio_#{rv}") and return ok
|
49
|
+
def response_wait_write(rv) # rv = [:wait_writable | :wait_readable ]
|
52
50
|
k = self.class
|
51
|
+
ok = __send__(rv, k.client_timeout) and return ok
|
53
52
|
k.logger.info("fd=#{fileno} ip=#@kgio_addr timeout on :#{rv} after "\
|
54
53
|
"#{k.client_timeout}s")
|
55
54
|
false
|
@@ -200,7 +199,7 @@ module Yahns::HttpResponse # :nodoc:
|
|
200
199
|
return step_write
|
201
200
|
end
|
202
201
|
|
203
|
-
wbuf = rv = nil
|
202
|
+
headers = wbuf = rv = nil
|
204
203
|
body.each do |x|
|
205
204
|
if wbuf
|
206
205
|
rv = wbuf.wbuf_write(self, x)
|
data/lib/yahns/openssl_client.rb
CHANGED
@@ -40,15 +40,31 @@ module Yahns::OpenSSLClient # :nodoc:
|
|
40
40
|
def kgio_trywrite(buf)
|
41
41
|
len = buf.bytesize
|
42
42
|
return if len == 0
|
43
|
-
|
43
|
+
|
44
|
+
case @ssl_blocked
|
45
|
+
when nil # likely
|
46
|
+
buf = @ssl_blocked = buf.dup
|
47
|
+
when Exception
|
48
|
+
raise @ssl_blocked
|
49
|
+
when String
|
50
|
+
if @ssl_blocked != buf
|
51
|
+
pfx = object_id
|
52
|
+
warn("#{pfx} BUG: ssl_blocked != buf\n" \
|
53
|
+
"#{pfx} ssl_blocked=#{@ssl_blocked.inspect}\n" \
|
54
|
+
"#{pfx} buf=#{buf.inspect}\n")
|
55
|
+
raise 'BUG: ssl_blocked} != buf'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
44
59
|
case rv = @ssl.write_nonblock(buf, exception: false)
|
45
60
|
when :wait_readable, :wait_writable
|
46
|
-
|
61
|
+
rv # do not clear ssl_blocked
|
47
62
|
when Integer
|
48
|
-
|
63
|
+
@ssl_blocked = len == rv ? nil : buf.byteslice(rv, len - rv)
|
49
64
|
end
|
50
|
-
|
51
|
-
|
65
|
+
rescue SystemCallError => e # ECONNRESET/EPIPE
|
66
|
+
e.set_backtrace([])
|
67
|
+
raise(@ssl_blocked = e)
|
52
68
|
end
|
53
69
|
|
54
70
|
def kgio_trywritev(buf)
|
@@ -75,22 +91,28 @@ module Yahns::OpenSSLClient # :nodoc:
|
|
75
91
|
def trysendio(io, offset, count)
|
76
92
|
return 0 if count == 0
|
77
93
|
|
78
|
-
|
79
|
-
|
80
|
-
buf =
|
81
|
-
io.pos = offset
|
82
|
-
buf = io.read(count, buf) or return # nil for EOF
|
94
|
+
case buf = @ssl_blocked
|
95
|
+
when nil
|
96
|
+
buf = do_pread(io, count, offset) or return # nil for EOF
|
83
97
|
buf = @ssl_blocked = buf.dup
|
98
|
+
when Exception
|
99
|
+
raise buf
|
100
|
+
# when String # just use it as-is
|
84
101
|
end
|
85
102
|
|
86
103
|
# call write_nonblock directly since kgio_trywrite allocates
|
87
104
|
# an unnecessary string
|
105
|
+
len = buf.size
|
88
106
|
case rv = @ssl.write_nonblock(buf, exception: false)
|
89
107
|
when :wait_readable, :wait_writable
|
90
108
|
return rv # do not clear ssl_blocked
|
109
|
+
when Integer
|
110
|
+
@ssl_blocked = len == rv ? nil : buf.byteslice(rv, len - rv)
|
91
111
|
end
|
92
|
-
@ssl_blocked = nil
|
93
112
|
rv
|
113
|
+
rescue SystemCallError => e # ECONNRESET/EPIPE
|
114
|
+
e.set_backtrace([])
|
115
|
+
raise(@ssl_blocked = e)
|
94
116
|
end
|
95
117
|
|
96
118
|
def shutdown # we never call this with a how=SHUT_* arg
|
@@ -132,7 +132,9 @@ module Yahns::HttpResponse # :nodoc:
|
|
132
132
|
|
133
133
|
# send the headers
|
134
134
|
case rv = kgio_syssend(res, flags)
|
135
|
-
when nil
|
135
|
+
when nil # all done, likely
|
136
|
+
res.clear
|
137
|
+
break
|
136
138
|
when String # partial write, highly unlikely
|
137
139
|
flags = MSG_DONTWAIT
|
138
140
|
res = rv # hope the skb grows
|
data/lib/yahns/proxy_pass.rb
CHANGED
@@ -1,18 +1,76 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
-
# Copyright (C) 2013-
|
3
|
-
# License: GPL-3.0+
|
2
|
+
# Copyright (C) 2013-2019 all contributors <yahns-public@yhbt.net>
|
3
|
+
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
4
4
|
# frozen_string_literal: true
|
5
5
|
require 'socket'
|
6
6
|
require 'rack/request'
|
7
|
-
require 'timeout'
|
8
|
-
|
7
|
+
require 'timeout' # only for Timeout::Error
|
9
8
|
require_relative 'proxy_http_response'
|
10
9
|
require_relative 'req_res'
|
11
10
|
|
12
|
-
|
13
|
-
|
11
|
+
# Yahns::ProxyPass is a Rack (hijack) app which allows yahns to
|
12
|
+
# act as a fully-buffering reverse proxy to protect backends
|
13
|
+
# from slow HTTP clients.
|
14
|
+
#
|
15
|
+
# Yahns::ProxyPass relies on the default behavior of yahns to do
|
16
|
+
# full input and output buffering. Output buffering is lazy,
|
17
|
+
# meaning it allows streaming output in the best case and
|
18
|
+
# will only buffer if the client cannot keep up with the server.
|
19
|
+
#
|
20
|
+
# The goal of this reverse proxy is to act as a sponge on the same LAN
|
21
|
+
# or host to any backend HTTP server not optimized for slow clients.
|
22
|
+
# Yahns::ProxyPass accomplishes this by handling all the slow clients
|
23
|
+
# internally within yahns itself to minimize time spent in the backend
|
24
|
+
# HTTP server waiting on slow clients.
|
25
|
+
#
|
26
|
+
# It does not do load balancing (we rely on Varnish for that).
|
27
|
+
# Here is the exact config we use with Varnish, which uses
|
28
|
+
# the +:response_headers+ option to hide some Varnish headers
|
29
|
+
# from clients:
|
30
|
+
#
|
31
|
+
# run Yahns::ProxyPass.new('http://127.0.0.1:6081',
|
32
|
+
# response_headers: {
|
33
|
+
# 'Age' => :ignore,
|
34
|
+
# 'X-Varnish' => :ignore,
|
35
|
+
# 'Via' => :ignore
|
36
|
+
# })
|
37
|
+
#
|
38
|
+
# This is NOT a generic Rack app and must be run with yahns.
|
39
|
+
# It uses +rack.hijack+, so compatibility with logging
|
40
|
+
# middlewares (e.g. Rack::CommonLogger) is not great and
|
41
|
+
# timing information gets lost.
|
42
|
+
#
|
43
|
+
# This provides HTTPS termination for our mail archives:
|
44
|
+
# https://yhbt.net/yahns-public/
|
45
|
+
#
|
46
|
+
# See https://yhbt.net/yahns.git/tree/examples/https_proxy_pass.conf.rb
|
47
|
+
# and https://yhbt.net/yahns.git/tree/examples/proxy_pass.ru for examples
|
48
|
+
class Yahns::ProxyPass
|
49
|
+
attr_reader :proxy_buffering, :response_headers # :nodoc:
|
14
50
|
|
15
|
-
|
51
|
+
# +dest+ must be an HTTP URL with optional variables prefixed with '$'.
|
52
|
+
# +dest+ may refer to the path to a Unix domain socket in the form:
|
53
|
+
#
|
54
|
+
# unix:/absolute/path/to/socket
|
55
|
+
#
|
56
|
+
# Variables which may be used in the +dest+ parameter include:
|
57
|
+
#
|
58
|
+
# - $url - the entire URL used to make the request
|
59
|
+
# - $path - the unescaped PATH_INFO of the HTTP request
|
60
|
+
# - $fullpath - $path with QUERY_STRING
|
61
|
+
# - $host - the hostname in the Host: header
|
62
|
+
#
|
63
|
+
# For Unix domain sockets, variables may be separated from the
|
64
|
+
# socket path via: ":/". For example:
|
65
|
+
#
|
66
|
+
# unix:/absolute/path/to/socket:/$host/$fullpath
|
67
|
+
#
|
68
|
+
# Currently :response_headers is the only +opts+ supported.
|
69
|
+
# :response_headers is a Hash containing a "from => to" mapping
|
70
|
+
# of response headers. The special value of +:ignore+ indicates
|
71
|
+
# the header from the backend HTTP server will be ignored instead
|
72
|
+
# of being blindly passed on to the client.
|
73
|
+
def initialize(dest, opts = { response_headers: { 'Server' => :ignore } })
|
16
74
|
case dest
|
17
75
|
when %r{\Aunix:([^:]+)(?::(/.*))?\z}
|
18
76
|
path = $2
|
@@ -35,7 +93,7 @@ class Yahns::ProxyPass # :nodoc:
|
|
35
93
|
init_path_vars(path)
|
36
94
|
end
|
37
95
|
|
38
|
-
def init_path_vars(path)
|
96
|
+
def init_path_vars(path) # :nodoc:
|
39
97
|
path ||= '$fullpath'
|
40
98
|
# methods from Rack::Request we want:
|
41
99
|
allow = %w(fullpath host_with_port host port url path)
|
@@ -48,10 +106,10 @@ class Yahns::ProxyPass # :nodoc:
|
|
48
106
|
@path = path.gsub(%r{\A/(\$(?:fullpath|path))}, '\1')
|
49
107
|
end
|
50
108
|
|
51
|
-
def call(env)
|
109
|
+
def call(env) # :nodoc:
|
52
110
|
# 3-way handshake for TCP backends while we generate the request header
|
53
111
|
rr = Yahns::ReqRes.start(@sockaddr)
|
54
|
-
c = env['rack.hijack'].call
|
112
|
+
c = env['rack.hijack'].call # Yahns::HttpClient#call
|
55
113
|
|
56
114
|
req = Rack::Request.new(env)
|
57
115
|
req = @path.gsub(/\$(\w+)/) { req.__send__($1) }
|
data/lib/yahns/queue_epoll.rb
CHANGED
@@ -32,6 +32,10 @@ class Yahns::Queue < SleepyPenguin::Epoll::IO # :nodoc:
|
|
32
32
|
epoll_ctl(Epoll::CTL_MOD, io, flags)
|
33
33
|
end
|
34
34
|
|
35
|
+
def queue_del(io)
|
36
|
+
epoll_ctl(Epoll::CTL_DEL, io, 0)
|
37
|
+
end
|
38
|
+
|
35
39
|
def thr_init
|
36
40
|
Thread.current[:yahns_rbuf] = ''.dup
|
37
41
|
Thread.current[:yahns_fdmap] = @fdmap
|
data/lib/yahns/queue_kqueue.rb
CHANGED
@@ -17,12 +17,6 @@ class Yahns::Queue < SleepyPenguin::Kqueue::IO # :nodoc:
|
|
17
17
|
|
18
18
|
ADD_ONESHOT = Ev::ADD | Ev::ONESHOT # private
|
19
19
|
|
20
|
-
def self.new
|
21
|
-
rv = super
|
22
|
-
rv.close_on_exec = true
|
23
|
-
rv
|
24
|
-
end
|
25
|
-
|
26
20
|
# for HTTP and HTTPS servers, we rely on the io writing to us, first
|
27
21
|
# flags: QEV_RD/QEV_WR (usually QEV_RD)
|
28
22
|
def queue_add(io, flags)
|
@@ -7,7 +7,6 @@ class Yahns::QueueQuitter # :nodoc:
|
|
7
7
|
attr_reader :to_io
|
8
8
|
def initialize
|
9
9
|
@reader, @to_io = IO.pipe
|
10
|
-
@to_io.close_on_exec = true
|
11
10
|
end
|
12
11
|
|
13
12
|
def yahns_step
|
@@ -22,4 +21,8 @@ class Yahns::QueueQuitter # :nodoc:
|
|
22
21
|
@reader.close
|
23
22
|
@to_io.close
|
24
23
|
end
|
24
|
+
|
25
|
+
def closed?
|
26
|
+
@to_io.closed?
|
27
|
+
end
|
25
28
|
end
|
data/lib/yahns/rackup_handler.rb
CHANGED
@@ -16,14 +16,10 @@ module Yahns::RackupHandler # :nodoc:
|
|
16
16
|
# fine for most apps, but we have SIGUSR2 restarts to support
|
17
17
|
working_directory(Yahns::START[:cwd])
|
18
18
|
|
19
|
-
app(:rack, app) do
|
19
|
+
app(:rack, app) do # Yahns::Config#app
|
20
20
|
addr = o[:listen] || "#{o[:Host]||default_host}:#{o[:Port]||8080}"
|
21
|
-
# allow listening to multiple addresses
|
22
|
-
|
23
|
-
addr.split(',').each { |l| listen(l) }
|
24
|
-
else
|
25
|
-
listen addr
|
26
|
-
end
|
21
|
+
# allow listening to multiple addresses (Yahns::Config#listen)
|
22
|
+
addr.split(',').each { |l| listen(l) } unless addr == 'inherit'
|
27
23
|
|
28
24
|
val = o[:client_timeout] and client_timeout(val)
|
29
25
|
end
|
data/lib/yahns/server.rb
CHANGED
@@ -237,24 +237,23 @@ class Yahns::Server # :nodoc:
|
|
237
237
|
end
|
238
238
|
end
|
239
239
|
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
240
|
+
opt = {}
|
241
|
+
@listeners.each { |sock| opt[sock.fileno] = sock }
|
242
|
+
env = ENV.to_hash
|
243
|
+
env['YAHNS_FD'] = opt.keys.join(',')
|
244
|
+
opt[:close_others] = true
|
245
|
+
cmd = [ Yahns::START[0] ].concat(Yahns::START[:argv])
|
246
|
+
dir = @config.value(:working_directory) || Yahns::START[:cwd]
|
247
|
+
@logger.info "spawning #{cmd.inspect} (in #{dir})"
|
248
|
+
@reexec_pid = if @before_exec
|
249
|
+
fork do
|
250
|
+
Dir.chdir(dir)
|
251
|
+
@before_exec.call(cmd)
|
252
|
+
exec(env, *cmd, opt)
|
249
253
|
end
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
cmd = [ Yahns::START[0] ].concat(Yahns::START[:argv])
|
254
|
-
@logger.info "executing #{cmd.inspect} (in #{Dir.pwd})"
|
255
|
-
@before_exec.call(cmd) if @before_exec
|
256
|
-
cmd << redirects
|
257
|
-
exec(*cmd)
|
254
|
+
else
|
255
|
+
opt[:chdir] = dir
|
256
|
+
spawn(env, *cmd, opt)
|
258
257
|
end
|
259
258
|
end
|
260
259
|
|
@@ -328,7 +327,9 @@ class Yahns::Server # :nodoc:
|
|
328
327
|
opts = sock_opts(io)
|
329
328
|
io = server_cast(io, opts)
|
330
329
|
set_server_sockopt(io, opts)
|
331
|
-
|
330
|
+
name = sock_name(io)
|
331
|
+
@logger.info "inherited addr=#{name} fd=#{io.fileno}"
|
332
|
+
@config.register_inherited(name)
|
332
333
|
io
|
333
334
|
end
|
334
335
|
|
@@ -395,7 +396,9 @@ class Yahns::Server # :nodoc:
|
|
395
396
|
|
396
397
|
# call OpenSSL::SSL::SSLContext#setup explicitly here to detect
|
397
398
|
# errors and avoid race conditions. We avoid calling this in the
|
398
|
-
# parent process
|
399
|
+
# parent process (if we have multiple workers) in case the
|
400
|
+
# setup code starts TCP connections to memcached or similar
|
401
|
+
# for session caching.
|
399
402
|
ssl_ctx.setup
|
400
403
|
end
|
401
404
|
ctx_list << ctx
|
@@ -435,25 +438,28 @@ class Yahns::Server # :nodoc:
|
|
435
438
|
# This just injects the QueueQuitter object which acts like a
|
436
439
|
# monkey wrench thrown into a perfectly good engine :)
|
437
440
|
def quit_finish
|
438
|
-
|
441
|
+
# we must not let quitters get GC-ed if we have any worker threads leftover
|
442
|
+
@quitter = Yahns::QueueQuitter.new
|
439
443
|
|
440
444
|
# throw the monkey wrench into the worker threads
|
441
|
-
@queues.each { |q| q.queue_add(quitter, Yahns::Queue::QEV_QUIT) }
|
445
|
+
@queues.each { |q| q.queue_add(@quitter, Yahns::Queue::QEV_QUIT) }
|
442
446
|
|
443
447
|
# watch the monkey wrench destroy all the threads!
|
444
448
|
# Ugh, this may fail if we have dedicated threads trickling
|
445
449
|
# response bodies out (e.g. "tail -F") Oh well, have a timeout
|
446
450
|
begin
|
447
451
|
@wthr.delete_if { |t| t.join(0.01) }
|
452
|
+
# Workaround Linux 5.5+ bug (fixed in 5.13+)
|
453
|
+
# https://yhbt.net/lore/lkml/20210405231025.33829-1-dave@stgolabs.net/
|
454
|
+
@wthr[0] && @queues[0].respond_to?(:queue_del) and @queues.each do |q|
|
455
|
+
q.queue_del(@quitter)
|
456
|
+
q.queue_add(@quitter, Yahns::Queue::QEV_QUIT)
|
457
|
+
end
|
448
458
|
end while @wthr[0] && Yahns.now <= @shutdown_expire
|
449
459
|
|
450
460
|
# cleanup, our job is done
|
451
461
|
@queues.each(&:close).clear
|
452
|
-
|
453
|
-
# we must not let quitter get GC-ed if we have any worker threads leftover
|
454
|
-
@wthr.each { |t| t[:yahns_quitter] = quitter }
|
455
|
-
|
456
|
-
quitter.close
|
462
|
+
@quitter.close # keep object around in case @wthr isn't empty
|
457
463
|
rescue => e
|
458
464
|
Yahns::Log.exception(@logger, "quit finish", e)
|
459
465
|
ensure
|
@@ -473,7 +479,8 @@ class Yahns::Server # :nodoc:
|
|
473
479
|
end
|
474
480
|
|
475
481
|
def sp_sig_handle(alive)
|
476
|
-
@
|
482
|
+
tout = alive ? (@sig_queue.empty? ? nil : 0) : 0.01
|
483
|
+
@sev.wait_readable(tout)
|
477
484
|
@sev.yahns_step
|
478
485
|
case sig = @sig_queue.shift
|
479
486
|
when :QUIT, :TERM, :INT
|
@@ -497,6 +504,19 @@ class Yahns::Server # :nodoc:
|
|
497
504
|
if drop_acceptors[0] || fdmap.size > 0
|
498
505
|
timeout = @shutdown_expire < Yahns.now ? -1 : @shutdown_timeout
|
499
506
|
n = fdmap.desperate_expire(timeout)
|
507
|
+
return false if n == 0 && @listeners.empty? # all done!
|
508
|
+
|
509
|
+
# FIXME: sometimes shutdowns take a long time when using proxy_pass
|
510
|
+
# Still not sure what's going on and it takes a while to reproduce..
|
511
|
+
if timeout == -1
|
512
|
+
@logger.error(
|
513
|
+
"exiting on shutdown_timeout=#@shutdown_timeout #{fdmap.size} FD(s) remain"
|
514
|
+
)
|
515
|
+
|
516
|
+
system('lsof', '-n', '-p', "#$$") if RUBY_PLATFORM =~ /linux/
|
517
|
+
return false
|
518
|
+
end
|
519
|
+
|
500
520
|
$0 = "yahns quitting, #{n} FD(s) remain"
|
501
521
|
true
|
502
522
|
else
|
data/lib/yahns/server_mp.rb
CHANGED
@@ -31,8 +31,6 @@ module Yahns::ServerMP # :nodoc:
|
|
31
31
|
# daemon_pipe may be true for non-initial workers
|
32
32
|
@daemon_pipe = @daemon_pipe.close if @daemon_pipe.respond_to?(:close)
|
33
33
|
|
34
|
-
srand # in case this pops up again: https://bugs.ruby-lang.org/issues/4338
|
35
|
-
|
36
34
|
# The OpenSSL PRNG is seeded with only the pid, and apps with frequently
|
37
35
|
# dying workers can recycle pids
|
38
36
|
OpenSSL::Random.seed(rand.to_s) if defined?(OpenSSL::Random)
|
@@ -91,7 +89,7 @@ module Yahns::ServerMP # :nodoc:
|
|
91
89
|
@logger.info "master process ready"
|
92
90
|
daemon_ready
|
93
91
|
begin
|
94
|
-
@sev.
|
92
|
+
@sev.wait_readable
|
95
93
|
@sev.yahns_step
|
96
94
|
reap_all
|
97
95
|
case @sig_queue.shift
|
@@ -159,7 +157,8 @@ module Yahns::ServerMP # :nodoc:
|
|
159
157
|
def mp_sig_handle(watch, alive)
|
160
158
|
# not performance critical
|
161
159
|
watch.delete_if { |io| io.to_io.closed? }
|
162
|
-
|
160
|
+
tout = alive ? (@sig_queue.empty? ? nil : 0) : 0.01
|
161
|
+
if r = select(watch, nil, nil, tout)
|
163
162
|
r[0].each(&:yahns_step)
|
164
163
|
end
|
165
164
|
case @sig_queue.shift
|
data/lib/yahns/sigevent_efd.rb
CHANGED
data/lib/yahns/sigevent_pipe.rb
CHANGED
@@ -5,21 +5,24 @@
|
|
5
5
|
class Yahns::Sigevent # :nodoc:
|
6
6
|
attr_reader :to_io
|
7
7
|
def initialize
|
8
|
-
@to_io, @wr =
|
9
|
-
@to_io.close_on_exec = @wr.close_on_exec = true
|
8
|
+
@to_io, @wr = IO.pipe
|
10
9
|
end
|
11
10
|
|
12
|
-
def
|
13
|
-
@to_io.
|
11
|
+
def wait_readable(*args)
|
12
|
+
@to_io.wait_readable(*args)
|
13
|
+
end
|
14
|
+
|
15
|
+
def fileno
|
16
|
+
@to_io.fileno
|
14
17
|
end
|
15
18
|
|
16
19
|
def sev_signal
|
17
|
-
@wr.
|
20
|
+
@wr.write_nonblock(".", exception: false)
|
18
21
|
end
|
19
22
|
|
20
23
|
def yahns_step
|
21
24
|
# 11 byte strings -> no malloc on YARV
|
22
|
-
while String === @to_io.
|
25
|
+
while String === @to_io.read_nonblock(11, exception: false)
|
23
26
|
end
|
24
27
|
:wait_readable
|
25
28
|
end
|
@@ -28,4 +31,8 @@ class Yahns::Sigevent # :nodoc:
|
|
28
31
|
@to_io.close
|
29
32
|
@wr.close
|
30
33
|
end
|
34
|
+
|
35
|
+
def closed?
|
36
|
+
@to_io.closed?
|
37
|
+
end
|
31
38
|
end
|
data/lib/yahns/socket_helper.rb
CHANGED
@@ -19,7 +19,7 @@ module Yahns::SocketHelper # :nodoc:
|
|
19
19
|
|
20
20
|
def set_server_sockopt(sock, opt)
|
21
21
|
opt = {backlog: 1024}.merge!(opt)
|
22
|
-
sock.close_on_exec = true
|
22
|
+
sock.close_on_exec = true # needed for inherited sockets
|
23
23
|
|
24
24
|
TCPSocket === sock and sock.setsockopt(:IPPROTO_TCP, :TCP_NODELAY, 1)
|
25
25
|
sock.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, 1)
|
data/lib/yahns/stream_input.rb
CHANGED
@@ -43,8 +43,9 @@ class Yahns::StreamInput # :nodoc:
|
|
43
43
|
length < 0 and raise ArgumentError, "negative length #{length} given"
|
44
44
|
rv.replace(@rbuf.slice!(0, length))
|
45
45
|
else
|
46
|
-
|
47
|
-
|
46
|
+
cur = @rbuf.size
|
47
|
+
to_read = length - cur
|
48
|
+
cur == 0 ? rv.clear : rv.replace(@rbuf.slice!(0, cur))
|
48
49
|
until to_read == 0 || eof? || (rv.size > 0 && @chunked)
|
49
50
|
@client.yahns_read(to_read, @buf) or eof!
|
50
51
|
filter_body(@rbuf, @buf)
|
data/lib/yahns/tee_input.rb
CHANGED
data/lib/yahns/version.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
Yahns::VERSION = '1.
|
1
|
+
Yahns::VERSION = '1.18.0'.freeze # :nodoc:
|
data/lib/yahns/wbuf.rb
CHANGED
@@ -31,6 +31,7 @@ require_relative 'wbuf_common'
|
|
31
31
|
class Yahns::Wbuf # :nodoc:
|
32
32
|
include Yahns::WbufCommon
|
33
33
|
attr_reader :busy
|
34
|
+
IO_WRITEV = RUBY_VERSION.to_r >= 2.5 # IO#write uses writev
|
34
35
|
|
35
36
|
def initialize(body, persist)
|
36
37
|
@tmpio = nil
|
@@ -40,9 +41,15 @@ class Yahns::Wbuf # :nodoc:
|
|
40
41
|
@busy = false
|
41
42
|
end
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
44
|
+
if IO_WRITEV
|
45
|
+
def wbuf_writev(buf)
|
46
|
+
@tmpio.write(*buf)
|
47
|
+
end
|
48
|
+
else
|
49
|
+
def wbuf_writev(buf)
|
50
|
+
@tmpio.kgio_writev(buf)
|
51
|
+
buf.inject(0) { |n, s| n += s.size }
|
52
|
+
end
|
46
53
|
end
|
47
54
|
|
48
55
|
def wbuf_write(c, buf)
|
data/lib/yahns/worker.rb
CHANGED
@@ -9,6 +9,14 @@ class Yahns::Worker # :nodoc:
|
|
9
9
|
def initialize(nr)
|
10
10
|
@nr = nr
|
11
11
|
@to_io, @wr = Kgio::Pipe.new
|
12
|
+
|
13
|
+
begin
|
14
|
+
# F_SETPIPE_SZ = 1031, PAGE_SIZE = 4096
|
15
|
+
# (fcntl will handle minimum size on platforms where PAGE_SIZE > 4096)
|
16
|
+
@to_io.fcntl(1031, 4096)
|
17
|
+
rescue SystemCallError
|
18
|
+
# old kernel (EINVAL, EPERM)
|
19
|
+
end if RUBY_PLATFORM =~ /\blinux\b/
|
12
20
|
end
|
13
21
|
|
14
22
|
def atfork_child
|
data/lib/yahns.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
-
# Copyright (C) 2013-
|
2
|
-
# License: GPL-3.0+
|
1
|
+
# Copyright (C) 2013-2019 all contributors <yahns-public@yhbt.net>
|
2
|
+
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
3
3
|
# frozen_string_literal: true
|
4
4
|
$stdout.sync = $stderr.sync = true
|
5
5
|
|
6
6
|
require 'unicorn' # pulls in raindrops, kgio, fcntl, etc, stringio, and logger
|
7
7
|
require 'sleepy_penguin'
|
8
|
+
require 'io/wait'
|
8
9
|
|
9
10
|
# kill off some unicorn internals we don't need
|
10
11
|
# we'll probably just make kcar into a server parser so we don't depend
|
@@ -15,11 +16,15 @@ require 'sleepy_penguin'
|
|
15
16
|
Unicorn.__send__(:remove_const, sym) if Unicorn.const_defined?(sym)
|
16
17
|
end
|
17
18
|
|
18
|
-
# yahns exposes
|
19
|
-
# See https://yhbt.net/yahns/yahns_config.txt
|
20
|
-
#
|
21
|
-
#
|
22
|
-
|
19
|
+
# yahns exposes little user-visible API outside of the config file.
|
20
|
+
# See https://yhbt.net/yahns/yahns_config.txt
|
21
|
+
# for the config documentation (or yahns_config(5) manpage)
|
22
|
+
# and https://yhbt.net/yahns.git/about/ for the homepage.
|
23
|
+
#
|
24
|
+
# Yahns::ProxyPass is currently the only public API.
|
25
|
+
#
|
26
|
+
# Documented APIs and options are supported forever,
|
27
|
+
# internals are subject to change.
|
23
28
|
module Yahns
|
24
29
|
# :stopdoc:
|
25
30
|
# We populate this at startup so we can figure out how to reexecute
|