yahns 0.0.1 → 0.0.2
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 +4 -4
- data/.gitignore +1 -0
- data/Documentation/.gitignore +5 -0
- data/Documentation/GNUmakefile +50 -0
- data/Documentation/yahns-rackup.txt +152 -0
- data/Documentation/yahns.txt +68 -0
- data/Documentation/yahns_config.txt +563 -0
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +14 -7
- data/HACKING +56 -0
- data/INSTALL +8 -0
- data/README +15 -2
- data/Rakefile +2 -2
- data/bin/yahns +1 -2
- data/bin/yahns-rackup +9 -0
- data/examples/yahns_multi.conf.rb +14 -4
- data/examples/yahns_rack_basic.conf.rb +17 -1
- data/extras/README +16 -0
- data/extras/autoindex.rb +151 -0
- data/extras/exec_cgi.rb +108 -0
- data/extras/proxy_pass.rb +210 -0
- data/extras/try_gzip_static.rb +208 -0
- data/lib/yahns.rb +5 -2
- data/lib/yahns/acceptor.rb +64 -22
- data/lib/yahns/cap_input.rb +2 -2
- data/lib/yahns/{client_expire_portable.rb → client_expire_generic.rb} +12 -11
- data/lib/yahns/{client_expire.rb → client_expire_tcpi.rb} +7 -6
- data/lib/yahns/config.rb +107 -22
- data/lib/yahns/daemon.rb +2 -0
- data/lib/yahns/fdmap.rb +28 -9
- data/lib/yahns/http_client.rb +123 -37
- data/lib/yahns/http_context.rb +21 -3
- data/lib/yahns/http_response.rb +80 -19
- data/lib/yahns/log.rb +23 -4
- data/lib/yahns/queue_epoll.rb +20 -9
- data/lib/yahns/queue_quitter.rb +16 -0
- data/lib/yahns/queue_quitter_pipe.rb +24 -0
- data/lib/yahns/rack.rb +0 -1
- data/lib/yahns/rackup_handler.rb +57 -0
- data/lib/yahns/server.rb +189 -59
- data/lib/yahns/server_mp.rb +43 -35
- data/lib/yahns/sigevent_pipe.rb +1 -0
- data/lib/yahns/socket_helper.rb +37 -11
- data/lib/yahns/stream_file.rb +14 -4
- data/lib/yahns/stream_input.rb +13 -7
- data/lib/yahns/tcp_server.rb +7 -0
- data/lib/yahns/tmpio.rb +10 -3
- data/lib/yahns/unix_server.rb +7 -0
- data/lib/yahns/wbuf.rb +19 -2
- data/lib/yahns/wbuf_common.rb +10 -3
- data/lib/yahns/wbuf_str.rb +24 -0
- data/lib/yahns/worker.rb +5 -26
- data/test/helper.rb +15 -5
- data/test/server_helper.rb +37 -1
- data/test/test_bin.rb +17 -8
- data/test/test_buffer_tmpdir.rb +103 -0
- data/test/test_client_expire.rb +71 -35
- data/test/test_client_max_body_size.rb +5 -13
- data/test/test_config.rb +1 -1
- data/test/test_expect_100.rb +176 -0
- data/test/test_extras_autoindex.rb +53 -0
- data/test/test_extras_exec_cgi.rb +81 -0
- data/test/test_extras_exec_cgi.sh +35 -0
- data/test/test_extras_try_gzip_static.rb +177 -0
- data/test/test_input.rb +128 -0
- data/test/test_mt_accept.rb +48 -0
- data/test/test_output_buffering.rb +90 -63
- data/test/test_rack.rb +1 -1
- data/test/test_rack_hijack.rb +2 -6
- data/test/test_reopen_logs.rb +2 -8
- data/test/test_serve_static.rb +104 -8
- data/test/test_server.rb +448 -73
- data/test/test_stream_file.rb +1 -1
- data/test/test_unix_socket.rb +72 -0
- data/test/test_wbuf.rb +20 -17
- data/yahns.gemspec +3 -0
- metadata +57 -5
data/lib/yahns/log.rb
CHANGED
|
@@ -41,9 +41,9 @@ module Yahns::Log # :nodoc:
|
|
|
41
41
|
ObjectSpace.each_object(File) { |fp| is_log?(fp) and to_reopen << fp }
|
|
42
42
|
|
|
43
43
|
to_reopen.each do |fp|
|
|
44
|
-
|
|
45
|
-
fp.stat
|
|
46
|
-
rescue IOError, Errno::EBADF
|
|
44
|
+
begin
|
|
45
|
+
orig_st = fp.stat
|
|
46
|
+
rescue IOError, Errno::EBADF # race
|
|
47
47
|
next
|
|
48
48
|
end
|
|
49
49
|
|
|
@@ -54,8 +54,27 @@ module Yahns::Log # :nodoc:
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
begin
|
|
57
|
-
|
|
57
|
+
# stdin, stdout, stderr are special. The following dance should
|
|
58
|
+
# guarantee there is no window where `fp' is unwritable in MRI
|
|
59
|
+
# (or any correct Ruby implementation).
|
|
60
|
+
#
|
|
61
|
+
# Fwiw, GVL has zero bearing here. This is tricky because of
|
|
62
|
+
# the unavoidable existence of stdio FILE * pointers for
|
|
63
|
+
# std{in,out,err} in all programs which may use the standard C library
|
|
64
|
+
if fp.fileno <= 2
|
|
65
|
+
# We do not want to hit fclose(3)->dup(2) window for std{in,out,err}
|
|
66
|
+
# MRI will use freopen(3) here internally on std{in,out,err}
|
|
67
|
+
fp.reopen(fp.path, "a")
|
|
68
|
+
else
|
|
69
|
+
# We should not need this: http://bugs.ruby-lang.org/issues/9036
|
|
70
|
+
# MRI will not call call fclose(3) or freopen(3) here
|
|
71
|
+
# since there's no associated std{in,out,err} FILE * pointer
|
|
72
|
+
# This should atomically use dup3(2) (or dup2(2)) syscall
|
|
73
|
+
File.open(fp.path, "a") { |tmpfp| fp.reopen(tmpfp) }
|
|
74
|
+
end
|
|
75
|
+
|
|
58
76
|
fp.sync = true
|
|
77
|
+
fp.flush # IO#sync=true may not implicitly flush
|
|
59
78
|
new_st = fp.stat
|
|
60
79
|
|
|
61
80
|
# this should only happen in the master:
|
data/lib/yahns/queue_epoll.rb
CHANGED
|
@@ -6,6 +6,7 @@ class Yahns::Queue < SleepyPenguin::Epoll::IO # :nodoc:
|
|
|
6
6
|
attr_accessor :fdmap # Yahns::Fdmap
|
|
7
7
|
|
|
8
8
|
# public
|
|
9
|
+
QEV_QUIT = Epoll::OUT # Level Trigger for QueueQuitter
|
|
9
10
|
QEV_RD = Epoll::IN | Epoll::ONESHOT
|
|
10
11
|
QEV_WR = Epoll::OUT | Epoll::ONESHOT
|
|
11
12
|
QEV_RDWR = QEV_RD | QEV_WR
|
|
@@ -21,10 +22,21 @@ class Yahns::Queue < SleepyPenguin::Epoll::IO # :nodoc:
|
|
|
21
22
|
epoll_ctl(Epoll::CTL_ADD, io, flags)
|
|
22
23
|
end
|
|
23
24
|
|
|
25
|
+
def thr_init
|
|
26
|
+
Thread.current[:yahns_rbuf] = ""
|
|
27
|
+
Thread.current[:yahns_fdmap] = @fdmap
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# use only before hijacking, once hijacked, io may be unusable to us
|
|
31
|
+
def queue_del(io)
|
|
32
|
+
@fdmap.forget(io)
|
|
33
|
+
epoll_ctl(Epoll::CTL_DEL, io, 0)
|
|
34
|
+
end
|
|
35
|
+
|
|
24
36
|
# returns an array of infinitely running threads
|
|
25
37
|
def worker_thread(logger, max_events)
|
|
26
38
|
Thread.new do
|
|
27
|
-
|
|
39
|
+
thr_init
|
|
28
40
|
begin
|
|
29
41
|
epoll_wait(max_events) do |_, io| # don't care for flags for now
|
|
30
42
|
case rv = io.yahns_step
|
|
@@ -35,19 +47,18 @@ class Yahns::Queue < SleepyPenguin::Epoll::IO # :nodoc:
|
|
|
35
47
|
when :wait_readwrite
|
|
36
48
|
epoll_ctl(Epoll::CTL_MOD, io, QEV_RDWR)
|
|
37
49
|
when :ignore # only used by rack.hijack
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
# we cannot call Epoll::CTL_DEL after hijacking, the hijacker
|
|
51
|
+
# may have already closed it Likewise, io.fileno is not
|
|
52
|
+
# expected to work, so we had to erase it from fdmap before hijack
|
|
53
|
+
when nil, :close
|
|
54
|
+
# this must be the ONLY place where we call IO#close on
|
|
55
|
+
# things that got inside the queue
|
|
56
|
+
@fdmap.sync_close(io)
|
|
44
57
|
else
|
|
45
58
|
raise "BUG: #{io.inspect}#yahns_step returned: #{rv.inspect}"
|
|
46
59
|
end
|
|
47
60
|
end
|
|
48
61
|
rescue => e
|
|
49
|
-
# sleep since this check is racy (and uncommon)
|
|
50
|
-
break if closed? || (sleep(0.01) && closed?)
|
|
51
62
|
Yahns::Log.exception(logger, 'queue loop', e)
|
|
52
63
|
end while true
|
|
53
64
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
|
2
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
3
|
+
|
|
4
|
+
require 'sleepy_penguin'
|
|
5
|
+
|
|
6
|
+
# add this as a level-triggered to any thread pool stuck on epoll_wait
|
|
7
|
+
# and watch it die!
|
|
8
|
+
if SleepyPenguin.const_defined?(:EventFD)
|
|
9
|
+
class Yahns::QueueQuitter < Yahns::Sigevent # :nodoc:
|
|
10
|
+
def yahns_step
|
|
11
|
+
Thread.current.exit
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
else
|
|
15
|
+
require_relative 'queue_quitter_pipe'
|
|
16
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
|
2
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
3
|
+
#
|
|
4
|
+
# POSIX pipe version, see queue_quitter.rb for the (preferred) eventfd one
|
|
5
|
+
class Yahns::QueueQuitter # :nodoc:
|
|
6
|
+
attr_reader :to_io
|
|
7
|
+
def initialize
|
|
8
|
+
reader, @to_io = IO.pipe
|
|
9
|
+
@to_io.close_on_exec = true
|
|
10
|
+
reader.close
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def yahns_step
|
|
14
|
+
Thread.current.exit
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def fileno
|
|
18
|
+
@to_io.fileno
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def close
|
|
22
|
+
@to_io.close
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/yahns/rack.rb
CHANGED
|
@@ -18,7 +18,6 @@ class Yahns::Rack # :nodoc:
|
|
|
18
18
|
def initialize(ru, opts = {})
|
|
19
19
|
# always called after config file parsing, may be called after forking
|
|
20
20
|
@app = lambda do
|
|
21
|
-
ENV["RACK_ENV"] ||= "none"
|
|
22
21
|
if ru.respond_to?(:call)
|
|
23
22
|
inner_app = ru.respond_to?(:to_app) ? ru.to_app : ru
|
|
24
23
|
else
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
|
2
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
3
|
+
require 'yahns'
|
|
4
|
+
|
|
5
|
+
module Yahns::RackupHandler # :nodoc:
|
|
6
|
+
def self.default_host
|
|
7
|
+
environment = ENV['RACK_ENV'] || 'development'
|
|
8
|
+
environment == 'development' ? '127.0.0.1' : '0.0.0.0'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.run(app, o)
|
|
12
|
+
cfg = Yahns::Config.new
|
|
13
|
+
cfg.instance_eval do
|
|
14
|
+
# we need this because "rackup -D" sends us to "/", which might be
|
|
15
|
+
# fine for most apps, but we have SIGUSR2 restarts to support
|
|
16
|
+
working_directory(Yahns::START[:cwd])
|
|
17
|
+
|
|
18
|
+
app(:rack, app) do
|
|
19
|
+
addr = o[:listen] || "#{o[:Host]||default_host}:#{o[:Port]||8080}"
|
|
20
|
+
# allow listening to multiple addresses
|
|
21
|
+
if addr =~ /,/
|
|
22
|
+
addr.split(/,/).each { |l| listen(l) }
|
|
23
|
+
else
|
|
24
|
+
listen addr
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
val = o[:client_timeout] and client_timeout(val)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
queue do
|
|
31
|
+
wt = o[:worker_threads] and worker_threads(wt)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
%w(stderr_path stdout_path).each do |x|
|
|
35
|
+
x = x.to_sym
|
|
36
|
+
val = o[x] and __send__(x, val)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
Yahns::Server.new(cfg).start.join
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.valid_options
|
|
43
|
+
# these should be the most common options
|
|
44
|
+
{
|
|
45
|
+
"listen=ADDRESS" => "address(es) to listen on (e.g. /tmp/sock)",
|
|
46
|
+
"worker_threads=NUM" => "number of worker threads to run",
|
|
47
|
+
|
|
48
|
+
# this affects how quickly graceful shutdown goes
|
|
49
|
+
"client_timeout=SECONDS" => "timeout for idle clients",
|
|
50
|
+
|
|
51
|
+
# I don't want these here, but rackup supports daemonize and
|
|
52
|
+
# we lose useful information when that sends stdout/stderr to /dev/null
|
|
53
|
+
"stderr_path=PATH" => "stderr destination",
|
|
54
|
+
"stdout_path=PATH" => "stdout destination",
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/yahns/server.rb
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
# -*- encoding: binary -*-
|
|
2
2
|
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
|
3
3
|
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
4
|
+
require_relative 'queue_quitter'
|
|
5
|
+
require_relative 'tcp_server'
|
|
6
|
+
require_relative 'unix_server'
|
|
7
|
+
|
|
4
8
|
class Yahns::Server # :nodoc:
|
|
5
|
-
QUEUE_SIGS = [ :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU
|
|
9
|
+
QUEUE_SIGS = [ :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU,
|
|
10
|
+
:CHLD ]
|
|
6
11
|
attr_accessor :daemon_pipe
|
|
7
12
|
attr_accessor :logger
|
|
13
|
+
attr_writer :user
|
|
14
|
+
attr_writer :before_exec
|
|
8
15
|
attr_writer :worker_processes
|
|
16
|
+
attr_writer :shutdown_timeout
|
|
17
|
+
attr_writer :atfork_prepare
|
|
18
|
+
attr_writer :atfork_parent
|
|
19
|
+
attr_writer :atfork_child
|
|
9
20
|
include Yahns::SocketHelper
|
|
10
21
|
|
|
11
22
|
def initialize(config)
|
|
23
|
+
@shutdown_timeout = nil
|
|
12
24
|
@reexec_pid = 0
|
|
13
25
|
@daemon_pipe = nil # writable IO or true
|
|
14
26
|
@config = config
|
|
@@ -19,9 +31,11 @@ class Yahns::Server # :nodoc:
|
|
|
19
31
|
@listeners = []
|
|
20
32
|
@pid = nil
|
|
21
33
|
@worker_processes = nil
|
|
34
|
+
@before_exec = nil
|
|
35
|
+
@atfork_prepare = @atfork_parent = @atfork_child = nil
|
|
22
36
|
@user = nil
|
|
23
37
|
@queues = []
|
|
24
|
-
@
|
|
38
|
+
@wthr = []
|
|
25
39
|
end
|
|
26
40
|
|
|
27
41
|
def sqwakeup(sig)
|
|
@@ -34,28 +48,40 @@ class Yahns::Server # :nodoc:
|
|
|
34
48
|
inherit_listeners!
|
|
35
49
|
# we try inheriting listeners first, so we bind them later.
|
|
36
50
|
# we don't write the pid file until we've bound listeners in case
|
|
37
|
-
# yahns was started twice by mistake.
|
|
38
|
-
# checks for stale/existing pid files, race conditions are still
|
|
39
|
-
# possible (and difficult/non-portable to avoid) and can be likely
|
|
40
|
-
# to clobber the pid if the second start was in quick succession
|
|
41
|
-
# after the first, so we rely on the listener binding to fail in
|
|
42
|
-
# that case. Some tests (in and outside of this source tree) and
|
|
43
|
-
# monitoring tools may also rely on pid files existing before we
|
|
44
|
-
# attempt to connect to the listener(s)
|
|
51
|
+
# yahns was started twice by mistake.
|
|
45
52
|
|
|
46
53
|
# setup signal handlers before writing pid file in case people get
|
|
47
54
|
# trigger happy and send signals as soon as the pid file exists.
|
|
48
55
|
QUEUE_SIGS.each { |sig| trap(sig) { sqwakeup(sig) } }
|
|
49
|
-
self.pid = @config.value(:pid) # write pid file
|
|
50
56
|
bind_new_listeners!
|
|
57
|
+
self.pid = @config.value(:pid) # write pid file
|
|
51
58
|
if @worker_processes
|
|
52
59
|
require 'yahns/server_mp'
|
|
53
60
|
extend Yahns::ServerMP
|
|
54
|
-
|
|
61
|
+
else
|
|
62
|
+
switch_user(*@user) if @user
|
|
55
63
|
end
|
|
56
64
|
self
|
|
57
65
|
end
|
|
58
66
|
|
|
67
|
+
def switch_user(user, group = nil)
|
|
68
|
+
# we do not protect the caller, checking Process.euid == 0 is
|
|
69
|
+
# insufficient because modern systems have fine-grained
|
|
70
|
+
# capabilities. Let the caller handle any and all errors.
|
|
71
|
+
uid = Etc.getpwnam(user).uid
|
|
72
|
+
gid = Etc.getgrnam(group).gid if group
|
|
73
|
+
Yahns::Log.chown_all(uid, gid)
|
|
74
|
+
if gid && Process.egid != gid
|
|
75
|
+
Process.initgroups(user, gid)
|
|
76
|
+
Process::GID.change_privilege(gid)
|
|
77
|
+
end
|
|
78
|
+
Process.euid != uid and Process::UID.change_privilege(uid)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def drop_acceptors
|
|
82
|
+
@listeners.delete_if(&:ac_quit)
|
|
83
|
+
end
|
|
84
|
+
|
|
59
85
|
# replaces current listener set with +listeners+. This will
|
|
60
86
|
# close the socket if it will not exist in the new listener set
|
|
61
87
|
def listeners=(listeners)
|
|
@@ -70,19 +96,41 @@ class Yahns::Server # :nodoc:
|
|
|
70
96
|
end
|
|
71
97
|
set_names = listener_names(listeners)
|
|
72
98
|
dead_names.concat(cur_names - set_names).uniq!
|
|
73
|
-
|
|
99
|
+
dying = []
|
|
74
100
|
@listeners.delete_if do |io|
|
|
75
101
|
if dead_names.include?(sock_name(io))
|
|
76
|
-
|
|
102
|
+
if io.ac_quit
|
|
103
|
+
true
|
|
104
|
+
else
|
|
105
|
+
dying << io
|
|
106
|
+
false
|
|
107
|
+
end
|
|
77
108
|
else
|
|
78
109
|
set_server_sockopt(io, sock_opts(io))
|
|
79
110
|
false
|
|
80
111
|
end
|
|
81
112
|
end
|
|
82
113
|
|
|
114
|
+
dying.delete_if(&:ac_quit) while dying[0]
|
|
115
|
+
|
|
83
116
|
(set_names - cur_names).each { |addr| listen(addr) }
|
|
84
117
|
end
|
|
85
118
|
|
|
119
|
+
def clobber_pid(path)
|
|
120
|
+
unlink_pid_safe(@pid) if @pid
|
|
121
|
+
if path
|
|
122
|
+
fp = begin
|
|
123
|
+
tmp = "#{File.dirname(path)}/#{rand}.#$$"
|
|
124
|
+
File.open(tmp, File::RDWR|File::CREAT|File::EXCL, 0644)
|
|
125
|
+
rescue Errno::EEXIST
|
|
126
|
+
retry
|
|
127
|
+
end
|
|
128
|
+
fp.syswrite("#$$\n")
|
|
129
|
+
File.rename(fp.path, path)
|
|
130
|
+
fp.close
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
86
134
|
# sets the path for the PID file of the master process
|
|
87
135
|
def pid=(path)
|
|
88
136
|
if path
|
|
@@ -97,18 +145,18 @@ class Yahns::Server # :nodoc:
|
|
|
97
145
|
"(or pid=#{path} is stale)"
|
|
98
146
|
end
|
|
99
147
|
end
|
|
100
|
-
unlink_pid_safe(@pid) if @pid
|
|
101
148
|
|
|
102
|
-
if
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
File.
|
|
106
|
-
rescue Errno::
|
|
107
|
-
|
|
149
|
+
# rename the old pid if possible
|
|
150
|
+
if @pid && path
|
|
151
|
+
begin
|
|
152
|
+
File.rename(@pid, path)
|
|
153
|
+
rescue Errno::ENOENT, Errno::EXDEV
|
|
154
|
+
# a user may have accidentally removed the original,
|
|
155
|
+
# obviously cross-FS renames don't work, either.
|
|
156
|
+
clobber_pid(path)
|
|
108
157
|
end
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
fp.close
|
|
158
|
+
else
|
|
159
|
+
clobber_pid(path)
|
|
112
160
|
end
|
|
113
161
|
@pid = path
|
|
114
162
|
end
|
|
@@ -125,17 +173,27 @@ class Yahns::Server # :nodoc:
|
|
|
125
173
|
def listen(address)
|
|
126
174
|
address = @config.expand_addr(address)
|
|
127
175
|
return if String === address && listener_names.include?(address)
|
|
176
|
+
delay = 0.5
|
|
177
|
+
tries = 5
|
|
128
178
|
|
|
129
179
|
begin
|
|
130
180
|
io = bind_listen(address, sock_opts(address))
|
|
131
|
-
unless
|
|
181
|
+
unless Yahns::TCPServer === io || Yahns::UNIXServer === io
|
|
132
182
|
io = server_cast(io)
|
|
133
183
|
end
|
|
134
184
|
@logger.info "listening on addr=#{sock_name(io)} fd=#{io.fileno}"
|
|
135
185
|
@listeners << io
|
|
136
186
|
io
|
|
137
187
|
rescue Errno::EADDRINUSE => err
|
|
138
|
-
|
|
188
|
+
if tries == 0
|
|
189
|
+
@logger.error "adding listener failed addr=#{address} (in use)"
|
|
190
|
+
raise err
|
|
191
|
+
end
|
|
192
|
+
tries -= 1
|
|
193
|
+
@logger.warn "retrying in #{delay} seconds " \
|
|
194
|
+
"(#{tries < 0 ? 'infinite' : tries} tries left)"
|
|
195
|
+
sleep(delay)
|
|
196
|
+
retry
|
|
139
197
|
rescue => err
|
|
140
198
|
@logger.fatal "error adding listener addr=#{address}"
|
|
141
199
|
raise err
|
|
@@ -175,15 +233,25 @@ class Yahns::Server # :nodoc:
|
|
|
175
233
|
end
|
|
176
234
|
end
|
|
177
235
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
236
|
+
# We cannot use Process.spawn here because of redirects + close-on-exec
|
|
237
|
+
# We must keep close_on_exec=true in the parent process and only set
|
|
238
|
+
# close_on_exec=false in the child. There must be no opportunity
|
|
239
|
+
# for the user app to ever get a listen socket with close_on_exec=false
|
|
240
|
+
@reexec_pid = fork do
|
|
241
|
+
redirects = {}
|
|
242
|
+
@listeners.each do |sock|
|
|
243
|
+
sock.close_on_exec = false
|
|
244
|
+
redirects[sock.fileno] = sock
|
|
245
|
+
end
|
|
246
|
+
ENV['YAHNS_FD'] = redirects.keys.join(',')
|
|
247
|
+
redirects[:close_others] = true
|
|
248
|
+
Dir.chdir(@config.value(:working_directory) || Yahns::START[:cwd])
|
|
249
|
+
cmd = [ Yahns::START[0] ].concat(Yahns::START[:argv])
|
|
250
|
+
@logger.info "executing #{cmd.inspect} (in #{Dir.pwd})"
|
|
251
|
+
@before_exec.call(cmd) if @before_exec
|
|
252
|
+
cmd << redirects
|
|
253
|
+
exec(*cmd)
|
|
254
|
+
end
|
|
187
255
|
end
|
|
188
256
|
|
|
189
257
|
# unlinks a PID file at given +path+ if it contains the current PID
|
|
@@ -231,7 +299,16 @@ class Yahns::Server # :nodoc:
|
|
|
231
299
|
|
|
232
300
|
def inherit_listeners!
|
|
233
301
|
# inherit sockets from parents, they need to be plain Socket objects
|
|
234
|
-
# before they become
|
|
302
|
+
# before they become Yahns::UNIXServer or Yahns::TCPServer
|
|
303
|
+
#
|
|
304
|
+
# Note: we intentionally use a yahns-specific environment variable
|
|
305
|
+
# here because existing servers may use non-blocking listen sockets.
|
|
306
|
+
# yahns uses _blocking_ listen sockets exclusively. We cannot
|
|
307
|
+
# change an existing socket to blocking mode if two servers are
|
|
308
|
+
# running (one expecting blocking, one expecting non-blocking)
|
|
309
|
+
# because that can completely break the non-blocking one.
|
|
310
|
+
# Unfortunately, there is no one-off MSG_DONTWAIT-like flag for
|
|
311
|
+
# accept4(2).
|
|
235
312
|
inherited = ENV['YAHNS_FD'].to_s.split(/,/).map do |fd|
|
|
236
313
|
io = Socket.for_fd(fd.to_i)
|
|
237
314
|
set_server_sockopt(io, sock_opts(io))
|
|
@@ -247,7 +324,6 @@ class Yahns::Server # :nodoc:
|
|
|
247
324
|
def bind_new_listeners!
|
|
248
325
|
self.listeners = @config.config_listeners.keys
|
|
249
326
|
raise ArgumentError, "no listeners" if @listeners.empty?
|
|
250
|
-
@listeners.each { |l| l.extend(Yahns::Acceptor) }
|
|
251
327
|
end
|
|
252
328
|
|
|
253
329
|
def proc_name(tag)
|
|
@@ -255,6 +331,15 @@ class Yahns::Server # :nodoc:
|
|
|
255
331
|
$0 = ([ File.basename(s[0]), tag ]).concat(s[:argv]).join(' ')
|
|
256
332
|
end
|
|
257
333
|
|
|
334
|
+
def qegg_vivify(qegg, fdmap)
|
|
335
|
+
queue = qegg.vivify(fdmap)
|
|
336
|
+
qegg.worker_threads.times do
|
|
337
|
+
@wthr << queue.worker_thread(@logger, qegg.max_events)
|
|
338
|
+
end
|
|
339
|
+
@queues << queue
|
|
340
|
+
queue
|
|
341
|
+
end
|
|
342
|
+
|
|
258
343
|
# spins up processing threads of the server
|
|
259
344
|
def fdmap_init
|
|
260
345
|
thresh = @config.value(:client_expire_threshold)
|
|
@@ -262,27 +347,26 @@ class Yahns::Server # :nodoc:
|
|
|
262
347
|
# keeps track of all connections, like ObjectSpace, but only for IOs
|
|
263
348
|
fdmap = Yahns::Fdmap.new(@logger, thresh)
|
|
264
349
|
|
|
265
|
-
# initialize queues (epoll/kqueue) and associated worker threads
|
|
350
|
+
# once initialize queues (epoll/kqueue) and associated worker threads
|
|
266
351
|
queues = {}
|
|
267
|
-
@config.qeggs.each do |name, qe|
|
|
268
|
-
queue = qe.vivify(fdmap)
|
|
269
|
-
qe.worker_threads.times do
|
|
270
|
-
@thr << queue.worker_thread(@logger, qe.max_events)
|
|
271
|
-
end
|
|
272
|
-
@queues << queue
|
|
273
|
-
queues[qe] = queue
|
|
274
|
-
end
|
|
275
352
|
|
|
276
353
|
# spin up applications (which are preload: false)
|
|
277
|
-
@config.app_ctx.each
|
|
354
|
+
@config.app_ctx.each(&:after_fork_init)
|
|
355
|
+
|
|
356
|
+
@shutdown_timeout ||= @config.app_ctx.map(&:client_timeout).max
|
|
278
357
|
|
|
279
358
|
# spin up acceptor threads, clients flow into worker queues after this
|
|
280
359
|
@listeners.each do |l|
|
|
281
|
-
|
|
360
|
+
opts = sock_opts(l)
|
|
361
|
+
ctx = opts[:yahns_app_ctx]
|
|
362
|
+
ctx_list = opts[:yahns_app_ctx_list] ||= []
|
|
282
363
|
qegg = ctx.qegg || @config.qeggs[:default]
|
|
283
|
-
|
|
364
|
+
ctx.queue = queues[qegg] ||= qegg_vivify(qegg, fdmap)
|
|
365
|
+
ctx = ctx.dup
|
|
366
|
+
ctx.__send__(:include, l.expire_mod)
|
|
367
|
+
ctx_list << ctx
|
|
284
368
|
# acceptors feed the the queues
|
|
285
|
-
|
|
369
|
+
l.spawn_acceptor(opts[:threads] || 1, @logger, ctx)
|
|
286
370
|
end
|
|
287
371
|
fdmap
|
|
288
372
|
end
|
|
@@ -294,20 +378,55 @@ class Yahns::Server # :nodoc:
|
|
|
294
378
|
end
|
|
295
379
|
|
|
296
380
|
def quit_enter(alive)
|
|
297
|
-
|
|
298
|
-
|
|
381
|
+
if alive
|
|
382
|
+
@logger.info("gracefully exiting shutdown_timeout=#{@shutdown_timeout} s")
|
|
383
|
+
else # drop connections immediately if signaled twice
|
|
384
|
+
@logger.info("graceful exit aborted, exiting immediately")
|
|
385
|
+
# we will still call any app-defined at_exit hooks here
|
|
386
|
+
# use SIGKILL if you don't want that.
|
|
387
|
+
exit(0)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
drop_acceptors # stop acceptors, we close epolls in quit_done
|
|
299
391
|
@config.config_listeners.each_value do |opts|
|
|
300
|
-
|
|
301
|
-
|
|
392
|
+
list= opts[:yahns_app_ctx_list] or next
|
|
393
|
+
# Yahns::HttpContext#persistent_connections=
|
|
394
|
+
list.each { |ctx| ctx.persistent_connections = false }
|
|
302
395
|
end
|
|
303
396
|
false
|
|
304
397
|
end
|
|
305
398
|
|
|
306
399
|
# drops all the the IO objects we have threads waiting on before exiting
|
|
400
|
+
# This just injects the QueueQuitter object which acts like a
|
|
401
|
+
# monkey wrench thrown into a perfectly good engine :)
|
|
307
402
|
def quit_finish
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
403
|
+
quitter = Yahns::QueueQuitter.new
|
|
404
|
+
|
|
405
|
+
# throw the monkey wrench into the worker threads
|
|
406
|
+
@queues.each { |q| q.queue_add(quitter, Yahns::Queue::QEV_QUIT) }
|
|
407
|
+
|
|
408
|
+
# watch the monkey wrench destroy all the threads!
|
|
409
|
+
@wthr.delete_if { |t| t.join(0.01) } while @wthr[0]
|
|
410
|
+
|
|
411
|
+
# cleanup, our job is done
|
|
412
|
+
@queues.each(&:close).clear
|
|
413
|
+
quitter.close
|
|
414
|
+
rescue => e
|
|
415
|
+
Yahns::Log.exception(@logger, "quit finish", e)
|
|
416
|
+
ensure
|
|
417
|
+
if (@wthr.size + @listeners.size) > 0
|
|
418
|
+
@logger.error("BUG: still active wthr=#{@wthr.size} "\
|
|
419
|
+
"listeners=#{@listeners.size}")
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def reap_reexec
|
|
424
|
+
@reexec_pid > 0 or return
|
|
425
|
+
wpid, status = Process.waitpid2(@reexec_pid, Process::WNOHANG)
|
|
426
|
+
wpid or return
|
|
427
|
+
@logger.error "reaped #{status.inspect} exec()-ed"
|
|
428
|
+
@reexec_pid = 0
|
|
429
|
+
self.pid = @pid.chomp('.oldbin') if @pid
|
|
311
430
|
end
|
|
312
431
|
|
|
313
432
|
def sp_sig_handle(alive)
|
|
@@ -316,19 +435,30 @@ class Yahns::Server # :nodoc:
|
|
|
316
435
|
case sig = @sig_queue.shift
|
|
317
436
|
when :QUIT, :TERM, :INT
|
|
318
437
|
return quit_enter(alive)
|
|
438
|
+
when :CHLD
|
|
439
|
+
reap_reexec
|
|
319
440
|
when :USR1
|
|
320
441
|
usr1_reopen('')
|
|
321
442
|
when :USR2
|
|
322
443
|
reexec
|
|
323
444
|
when :HUP
|
|
324
445
|
reexec
|
|
325
|
-
return
|
|
446
|
+
return quit_enter(alive)
|
|
326
447
|
when :TTIN, :TTOU, :WINCH
|
|
327
448
|
@logger.info("SIG#{sig} ignored in single-process mode")
|
|
328
449
|
end
|
|
329
450
|
alive
|
|
330
451
|
end
|
|
331
452
|
|
|
453
|
+
def dropping(fdmap)
|
|
454
|
+
if drop_acceptors[0] || fdmap.size > 0
|
|
455
|
+
fdmap.desperate_expire_for(nil, @shutdown_timeout)
|
|
456
|
+
true
|
|
457
|
+
else
|
|
458
|
+
false
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
|
|
332
462
|
# single-threaded only, this is overriden if @worker_processes is non-nil
|
|
333
463
|
def join
|
|
334
464
|
daemon_ready
|
|
@@ -338,7 +468,7 @@ class Yahns::Server # :nodoc:
|
|
|
338
468
|
alive = sp_sig_handle(alive)
|
|
339
469
|
rescue => e
|
|
340
470
|
Yahns::Log.exception(@logger, "main loop", e)
|
|
341
|
-
end while alive || fdmap
|
|
471
|
+
end while alive || dropping(fdmap)
|
|
342
472
|
unlink_pid_safe(@pid) if @pid
|
|
343
473
|
ensure
|
|
344
474
|
quit_finish
|