yahns 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|