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.
Files changed (66) hide show
  1. checksums.yaml +5 -5
  2. data/.document +2 -0
  3. data/.gitignore +0 -1
  4. data/.olddoc.yml +3 -2
  5. data/Documentation/GNUmakefile +1 -1
  6. data/Documentation/design_notes.txt +6 -3
  7. data/Documentation/yahns-rackup.pod +7 -3
  8. data/Documentation/yahns.pod +1 -1
  9. data/Documentation/yahns_config.pod +10 -10
  10. data/GIT-VERSION-FILE +1 -1
  11. data/GIT-VERSION-GEN +3 -3
  12. data/HACKING +13 -13
  13. data/NEWS +982 -829
  14. data/README +11 -12
  15. data/Rakefile +121 -5
  16. data/examples/https_proxy_pass.conf.rb +36 -0
  17. data/examples/logrotate.conf +1 -1
  18. data/examples/proxy_pass.ru +11 -0
  19. data/extras/autoindex.rb +20 -4
  20. data/extras/exec_cgi.rb +38 -24
  21. data/extras/proxy_pass.rb +7 -6
  22. data/extras/try_gzip_static.rb +4 -1
  23. data/lib/yahns/acceptor.rb +3 -3
  24. data/lib/yahns/chunk_body.rb +2 -1
  25. data/lib/yahns/config.rb +10 -5
  26. data/lib/yahns/daemon.rb +0 -1
  27. data/lib/yahns/http_client.rb +28 -18
  28. data/lib/yahns/http_response.rb +3 -4
  29. data/lib/yahns/openssl_client.rb +33 -11
  30. data/lib/yahns/proxy_http_response.rb +3 -1
  31. data/lib/yahns/proxy_pass.rb +68 -10
  32. data/lib/yahns/queue_epoll.rb +4 -0
  33. data/lib/yahns/queue_kqueue.rb +0 -6
  34. data/lib/yahns/queue_quitter_pipe.rb +4 -1
  35. data/lib/yahns/rackup_handler.rb +3 -7
  36. data/lib/yahns/server.rb +47 -27
  37. data/lib/yahns/server_mp.rb +3 -4
  38. data/lib/yahns/sigevent_efd.rb +0 -1
  39. data/lib/yahns/sigevent_pipe.rb +13 -6
  40. data/lib/yahns/socket_helper.rb +1 -1
  41. data/lib/yahns/stream_input.rb +3 -2
  42. data/lib/yahns/tee_input.rb +1 -3
  43. data/lib/yahns/version.rb +1 -1
  44. data/lib/yahns/wbuf.rb +10 -3
  45. data/lib/yahns/worker.rb +8 -0
  46. data/lib/yahns.rb +12 -7
  47. data/man/yahns-rackup.1 +17 -17
  48. data/man/yahns.1 +11 -15
  49. data/man/yahns_config.5 +31 -31
  50. data/test/helper.rb +6 -2
  51. data/test/server_helper.rb +20 -5
  52. data/test/test_bin.rb +33 -30
  53. data/test/test_config.rb +2 -2
  54. data/test/test_extras_exec_cgi.rb +24 -1
  55. data/test/test_extras_try_gzip_static.rb +1 -1
  56. data/test/test_mt_accept.rb +0 -2
  57. data/test/test_proxy_pass.rb +1 -2
  58. data/test/test_proxy_pass_no_buffering.rb +1 -1
  59. data/test/test_rack_env.rb +58 -0
  60. data/test/test_serve_static.rb +0 -1
  61. data/test/test_server.rb +1 -4
  62. data/test/test_ssl.rb +2 -0
  63. data/test/test_unix_socket.rb +1 -3
  64. data/test/test_wbuf.rb +1 -1
  65. data/yahns.gemspec +8 -5
  66. metadata +12 -9
@@ -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)
@@ -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
- buf = @ssl_blocked = buf.dup
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
- return rv # do not clear ssl_blocked
61
+ rv # do not clear ssl_blocked
47
62
  when Integer
48
- rv = len == rv ? nil : buf.byteslice(rv, len - rv)
63
+ @ssl_blocked = len == rv ? nil : buf.byteslice(rv, len - rv)
49
64
  end
50
- @ssl_blocked = nil
51
- rv
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
- unless buf = @ssl_blocked
79
- count = 0x4000 if count > 0x4000
80
- buf = Thread.current[:yahns_sfbuf] ||= ''.dup
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 then break # all done, likely
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
@@ -1,18 +1,76 @@
1
1
  # -*- encoding: binary -*-
2
- # Copyright (C) 2013-2016 all contributors <yahns-public@yhbt.net>
3
- # License: GPL-3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt)
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
- class Yahns::ProxyPass # :nodoc:
13
- attr_reader :proxy_buffering, :response_headers
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
- def initialize(dest, opts = {})
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) }
@@ -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
@@ -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
@@ -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
- if addr.include?(',')
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
- # We cannot use Process.spawn here because of redirects + close-on-exec
241
- # We must keep close_on_exec=true in the parent process and only set
242
- # close_on_exec=false in the child. There must be no opportunity
243
- # for the user app to ever get a listen socket with close_on_exec=false
244
- @reexec_pid = fork do
245
- redirects = {}
246
- @listeners.each do |sock|
247
- sock.close_on_exec = false
248
- redirects[sock.fileno] = sock
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
- ENV['YAHNS_FD'] = redirects.keys.join(',')
251
- redirects[:close_others] = true
252
- Dir.chdir(@config.value(:working_directory) || Yahns::START[:cwd])
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
- @logger.info "inherited addr=#{sock_name(io)} fd=#{io.fileno}"
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 since
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
- quitter = Yahns::QueueQuitter.new
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
- @sev.kgio_wait_readable(alive ? nil : 0.01)
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
@@ -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.kgio_wait_readable
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
- if r = IO.select(watch, nil, nil, alive ? nil : 0.1)
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
@@ -3,7 +3,6 @@
3
3
  # License: GPL-3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt)
4
4
  # frozen_string_literal: true
5
5
  class Yahns::Sigevent < SleepyPenguin::EventFD # :nodoc:
6
- include Kgio::DefaultWaiters
7
6
  def self.new
8
7
  super(0, :CLOEXEC)
9
8
  end
@@ -5,21 +5,24 @@
5
5
  class Yahns::Sigevent # :nodoc:
6
6
  attr_reader :to_io
7
7
  def initialize
8
- @to_io, @wr = Kgio::Pipe.new
9
- @to_io.close_on_exec = @wr.close_on_exec = true
8
+ @to_io, @wr = IO.pipe
10
9
  end
11
10
 
12
- def kgio_wait_readable(*args)
13
- @to_io.kgio_wait_readable(*args)
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.kgio_trywrite(".")
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.kgio_tryread(11)
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
@@ -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)
@@ -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
- to_read = length - @rbuf.size
47
- rv.replace(@rbuf.slice!(0, @rbuf.size))
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)
@@ -104,9 +104,7 @@ class Yahns::TeeInput < Yahns::StreamInput # :nodoc:
104
104
  end
105
105
 
106
106
  def tee(buffer)
107
- if buffer && buffer.size > 0
108
- @tmp.write(buffer)
109
- end
107
+ @tmp.write(buffer) if buffer
110
108
  buffer
111
109
  end
112
110
 
data/lib/yahns/version.rb CHANGED
@@ -1 +1 @@
1
- Yahns::VERSION = '1.14.1'.freeze # :nodoc:
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
- def wbuf_writev(buf)
44
- @tmpio.kgio_writev(buf)
45
- buf.inject(0) { |n, s| n += s.size }
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-2016 all contributors <yahns-public@yhbt.net>
2
- # License: GPL-3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt)
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 no user-visible API outside of the config file.
19
- # See https://yhbt.net/yahns/yahns_config.txt for the config documentation
20
- # and https://yhbt.net/yahns/ for the homepage.
21
- # Internals are subject to change.
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