yahns 0.0.0TP1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/COPYING +674 -0
  4. data/GIT-VERSION-GEN +41 -0
  5. data/GNUmakefile +90 -0
  6. data/README +127 -0
  7. data/Rakefile +60 -0
  8. data/bin/yahns +32 -0
  9. data/examples/README +3 -0
  10. data/examples/init.sh +76 -0
  11. data/examples/logger_mp_safe.rb +28 -0
  12. data/examples/logrotate.conf +32 -0
  13. data/examples/yahns_multi.conf.rb +89 -0
  14. data/examples/yahns_rack_basic.conf.rb +27 -0
  15. data/lib/yahns.rb +73 -0
  16. data/lib/yahns/acceptor.rb +28 -0
  17. data/lib/yahns/client_expire.rb +40 -0
  18. data/lib/yahns/client_expire_portable.rb +39 -0
  19. data/lib/yahns/config.rb +344 -0
  20. data/lib/yahns/daemon.rb +51 -0
  21. data/lib/yahns/fdmap.rb +90 -0
  22. data/lib/yahns/http_client.rb +198 -0
  23. data/lib/yahns/http_context.rb +65 -0
  24. data/lib/yahns/http_response.rb +184 -0
  25. data/lib/yahns/log.rb +73 -0
  26. data/lib/yahns/queue.rb +7 -0
  27. data/lib/yahns/queue_egg.rb +23 -0
  28. data/lib/yahns/queue_epoll.rb +57 -0
  29. data/lib/yahns/rack.rb +80 -0
  30. data/lib/yahns/server.rb +336 -0
  31. data/lib/yahns/server_mp.rb +181 -0
  32. data/lib/yahns/sigevent.rb +7 -0
  33. data/lib/yahns/sigevent_efd.rb +18 -0
  34. data/lib/yahns/sigevent_pipe.rb +29 -0
  35. data/lib/yahns/socket_helper.rb +117 -0
  36. data/lib/yahns/stream_file.rb +34 -0
  37. data/lib/yahns/stream_input.rb +150 -0
  38. data/lib/yahns/tee_input.rb +114 -0
  39. data/lib/yahns/tmpio.rb +27 -0
  40. data/lib/yahns/wbuf.rb +36 -0
  41. data/lib/yahns/wbuf_common.rb +32 -0
  42. data/lib/yahns/worker.rb +58 -0
  43. data/test/covshow.rb +29 -0
  44. data/test/helper.rb +115 -0
  45. data/test/server_helper.rb +65 -0
  46. data/test/test_bin.rb +97 -0
  47. data/test/test_client_expire.rb +132 -0
  48. data/test/test_config.rb +56 -0
  49. data/test/test_fdmap.rb +19 -0
  50. data/test/test_output_buffering.rb +291 -0
  51. data/test/test_queue.rb +59 -0
  52. data/test/test_rack.rb +28 -0
  53. data/test/test_serve_static.rb +42 -0
  54. data/test/test_server.rb +415 -0
  55. data/test/test_stream_file.rb +30 -0
  56. data/test/test_wbuf.rb +136 -0
  57. data/yahns.gemspec +19 -0
  58. metadata +165 -0
@@ -0,0 +1,73 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
3
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
4
+
5
+ # logging-related utility functions for all of yahns
6
+ module Yahns::Log # :nodoc:
7
+ def self.exception(logger, prefix, exc)
8
+ message = exc.message
9
+ message = message.dump if /[[:cntrl:]]/ =~ message # prevent code injection
10
+ logger.error "#{prefix}: #{message} (#{exc.class})"
11
+ exc.backtrace.each { |line| logger.error(line) }
12
+ end
13
+
14
+ def self.is_log?(fp)
15
+ append_flags = IO::WRONLY | IO::APPEND
16
+
17
+ ! fp.closed? &&
18
+ fp.stat.file? &&
19
+ fp.sync &&
20
+ (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
21
+ rescue IOError, Errno::EBADF
22
+ false
23
+ end
24
+
25
+ def self.chown_all(uid, gid)
26
+ ObjectSpace.each_object(File) do |fp|
27
+ fp.chown(uid, gid) if is_log?(fp)
28
+ end
29
+ end
30
+
31
+ # This reopens ALL logfiles in the process that have been rotated
32
+ # using logrotate(8) (without copytruncate) or similar tools.
33
+ # A +File+ object is considered for reopening if it is:
34
+ # 1) opened with the O_APPEND and O_WRONLY flags
35
+ # 2) the current open file handle does not match its original open path
36
+ # 3) unbuffered (as far as userspace buffering goes, not O_SYNC)
37
+ # Returns the number of files reopened
38
+ def self.reopen_all
39
+ to_reopen = []
40
+ nr = 0
41
+ ObjectSpace.each_object(File) { |fp| is_log?(fp) and to_reopen << fp }
42
+
43
+ to_reopen.each do |fp|
44
+ orig_st = begin
45
+ fp.stat
46
+ rescue IOError, Errno::EBADF
47
+ next
48
+ end
49
+
50
+ begin
51
+ b = File.stat(fp.path)
52
+ next if orig_st.ino == b.ino && orig_st.dev == b.dev
53
+ rescue Errno::ENOENT
54
+ end
55
+
56
+ begin
57
+ File.open(fp.path, 'a') { |tmpfp| fp.reopen(tmpfp) }
58
+ fp.sync = true
59
+ new_st = fp.stat
60
+
61
+ # this should only happen in the master:
62
+ if orig_st.uid != new_st.uid || orig_st.gid != new_st.gid
63
+ fp.chown(orig_st.uid, orig_st.gid)
64
+ end
65
+
66
+ nr += 1
67
+ rescue IOError, Errno::EBADF
68
+ # not much we can do...
69
+ end
70
+ end
71
+ nr
72
+ end
73
+ end
@@ -0,0 +1,7 @@
1
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
2
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
3
+ if SleepyPenguin.const_defined?(:Epoll)
4
+ require_relative 'queue_epoll'
5
+ else
6
+ require_relative 'queue_kqueue' # TODO
7
+ end
@@ -0,0 +1,23 @@
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
+ # this represents a Yahns::Queue before its vivified. This only
5
+ # lives in the parent process and should be clobbered after qc_vivify
6
+ class Yahns::QueueEgg # :nodoc:
7
+ attr_writer :max_events, :worker_threads
8
+ attr_accessor :logger
9
+
10
+ def initialize
11
+ @max_events = 1 # 1 is good if worker_threads > 1
12
+ @worker_threads = 7 # any default is wrong for most apps...
13
+ @logger = nil
14
+ end
15
+
16
+ # only call after forking
17
+ def qc_vivify(fdmap)
18
+ queue = Yahns::Queue.new
19
+ queue.fdmap = fdmap
20
+ queue.spawn_worker_threads(@logger, @worker_threads, @max_events)
21
+ queue
22
+ end
23
+ end
@@ -0,0 +1,57 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
3
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
4
+ class Yahns::Queue < SleepyPenguin::Epoll::IO # :nodoc:
5
+ include SleepyPenguin
6
+ attr_accessor :fdmap # Yahns::Fdmap
7
+
8
+ # public
9
+ QEV_RD = Epoll::IN | Epoll::ONESHOT
10
+ QEV_WR = Epoll::OUT | Epoll::ONESHOT
11
+ QEV_RDWR = QEV_RD | QEV_WR
12
+
13
+ def self.new
14
+ super(SleepyPenguin::Epoll::CLOEXEC)
15
+ end
16
+
17
+ # for HTTP and HTTPS servers, we rely on the io writing to us, first
18
+ # flags: QEV_RD/QEV_WR (usually QEV_RD)
19
+ def queue_add(io, flags)
20
+ @fdmap.add(io)
21
+ epoll_ctl(Epoll::CTL_ADD, io, flags)
22
+ end
23
+
24
+ # returns an array of infinitely running threads
25
+ def spawn_worker_threads(logger, worker_threads, max_events)
26
+ worker_threads.times do
27
+ Thread.new do
28
+ Thread.current[:yahns_rbuf] = ""
29
+ begin
30
+ epoll_wait(max_events) do |_, io| # don't care for flags for now
31
+ case rv = io.yahns_step
32
+ when :wait_readable
33
+ epoll_ctl(Epoll::CTL_MOD, io, QEV_RD)
34
+ when :wait_writable
35
+ epoll_ctl(Epoll::CTL_MOD, io, QEV_WR)
36
+ when :wait_readwrite
37
+ epoll_ctl(Epoll::CTL_MOD, io, QEV_RDWR)
38
+ when :delete # only used by rack.hijack
39
+ epoll_ctl(Epoll::CTL_DEL, io, 0)
40
+ @fdmap.delete(io)
41
+ when nil
42
+ # this is be the ONLY place where we call IO#close on
43
+ # things inside the queue
44
+ io.close
45
+ @fdmap.decr
46
+ else
47
+ raise "BUG: #{io.inspect}#yahns_step returned: #{rv.inspect}"
48
+ end
49
+ end
50
+ rescue => e
51
+ break if (IOError === e || Errno::EBADF === e) && closed?
52
+ Yahns::Log.exception(logger, 'queue loop', e)
53
+ end while true
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,80 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
3
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
4
+ require 'rack'
5
+ class Yahns::Rack # :nodoc:
6
+ attr_reader :preload
7
+
8
+ # enforce a single instance for the identical config.ru
9
+ def self.instance_key(*args)
10
+ ru = args[0]
11
+
12
+ # it's safe to expand_path now since we enforce working_directory in the
13
+ # top-level config is called before any apps are created
14
+ # ru may also be a Rack::Builder object or any already-built Rack app
15
+ ru.respond_to?(:call) ? ru.object_id : File.expand_path(ru)
16
+ end
17
+
18
+ def initialize(ru, opts = {})
19
+ # always called after config file parsing, may be called after forking
20
+ @app = lambda do
21
+ ENV["RACK_ENV"] ||= "none"
22
+ if ru.respond_to?(:call)
23
+ inner_app = ru.respond_to?(:to_app) ? ru.to_app : ru
24
+ else
25
+ inner_app = case ru
26
+ when /\.ru$/
27
+ raw = File.read(ru)
28
+ raw.sub!(/^__END__\n.*/, '')
29
+ eval("Rack::Builder.new {(\n#{raw}\n)}.to_app", TOPLEVEL_BINDING, ru)
30
+ else
31
+ require ru
32
+ Object.const_get(File.basename(ru, '.rb').capitalize)
33
+ end
34
+ end
35
+ inner_app
36
+ end
37
+ @ru = ru
38
+ @preload = opts[:preload]
39
+ build_app! if @preload
40
+ end
41
+
42
+ def config_context
43
+ ctx_class = Class.new(Yahns::HttpClient)
44
+ ctx_class.extend(Yahns::HttpContext)
45
+ ctx_class.http_ctx_init(self)
46
+ ctx_class
47
+ end
48
+
49
+ def build_app!
50
+ if @app.respond_to?(:arity) && @app.arity == 0
51
+ Gem.refresh if defined?(Gem) && Gem.respond_to?(:refresh)
52
+ @app = @app.call
53
+ end
54
+ end
55
+
56
+ # allow different HttpContext instances to have different Rack defaults
57
+ def app_defaults
58
+ {
59
+ # logger is set in http_context
60
+ "rack.errors" => $stderr,
61
+ "rack.multiprocess" => true,
62
+ "rack.multithread" => true,
63
+ "rack.run_once" => false,
64
+ "rack.hijack?" => true,
65
+ "rack.version" => [ 1, 2 ],
66
+ "SCRIPT_NAME" => "",
67
+
68
+ # this is not in the Rack spec, but some apps may rely on it
69
+ "SERVER_SOFTWARE" => "yahns"
70
+ }
71
+ end
72
+
73
+ def app_after_fork
74
+ build_app! unless @preload
75
+ @app
76
+ end
77
+ end
78
+
79
+ # register ourselves
80
+ Yahns::Config::APP_CLASS[:rack] = Yahns::Rack
@@ -0,0 +1,336 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
3
+ # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
4
+ class Yahns::Server # :nodoc:
5
+ QUEUE_SIGS = [ :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ]
6
+ attr_accessor :daemon_pipe
7
+ attr_accessor :logger
8
+ attr_writer :worker_processes
9
+ include Yahns::SocketHelper
10
+
11
+ def initialize(config)
12
+ @reexec_pid = 0
13
+ @daemon_pipe = nil # writable IO or true
14
+ @config = config
15
+ @workers = {} # pid -> workers
16
+ @sig_queue = [] # nil in forked workers
17
+ @logger = Logger.new($stderr)
18
+ @sev = Yahns::Sigevent.new
19
+ @listeners = []
20
+ @pid = nil
21
+ @worker_processes = nil
22
+ @user = nil
23
+ end
24
+
25
+ def sqwakeup(sig)
26
+ @sig_queue << sig
27
+ @sev.sev_signal
28
+ end
29
+
30
+ def start
31
+ @config.commit!(self)
32
+ inherit_listeners!
33
+ # we try inheriting listeners first, so we bind them later.
34
+ # we don't write the pid file until we've bound listeners in case
35
+ # yahns was started twice by mistake. Even though our #pid= method
36
+ # checks for stale/existing pid files, race conditions are still
37
+ # possible (and difficult/non-portable to avoid) and can be likely
38
+ # to clobber the pid if the second start was in quick succession
39
+ # after the first, so we rely on the listener binding to fail in
40
+ # that case. Some tests (in and outside of this source tree) and
41
+ # monitoring tools may also rely on pid files existing before we
42
+ # attempt to connect to the listener(s)
43
+
44
+ # setup signal handlers before writing pid file in case people get
45
+ # trigger happy and send signals as soon as the pid file exists.
46
+ QUEUE_SIGS.each { |sig| trap(sig) { sqwakeup(sig) } }
47
+ self.pid = @config.value(:pid) # write pid file
48
+ bind_new_listeners!
49
+ if @worker_processes
50
+ require 'yahns/server_mp'
51
+ extend Yahns::ServerMP
52
+ mp_init
53
+ end
54
+ self
55
+ end
56
+
57
+ # replaces current listener set with +listeners+. This will
58
+ # close the socket if it will not exist in the new listener set
59
+ def listeners=(listeners)
60
+ cur_names, dead_names = [], []
61
+ listener_names.each do |name|
62
+ if ?/ == name[0]
63
+ # mark unlinked sockets as dead so we can rebind them
64
+ (File.socket?(name) ? cur_names : dead_names) << name
65
+ else
66
+ cur_names << name
67
+ end
68
+ end
69
+ set_names = listener_names(listeners)
70
+ dead_names.concat(cur_names - set_names).uniq!
71
+
72
+ @listeners.delete_if do |io|
73
+ if dead_names.include?(sock_name(io))
74
+ (io.close rescue nil).nil? # true
75
+ else
76
+ set_server_sockopt(io, sock_opts(io))
77
+ false
78
+ end
79
+ end
80
+
81
+ (set_names - cur_names).each { |addr| listen(addr) }
82
+ end
83
+
84
+ # sets the path for the PID file of the master process
85
+ def pid=(path)
86
+ if path
87
+ if x = valid_pid?(path)
88
+ return path if @pid && path == @pid && x == $$
89
+ if x == @reexec_pid && @pid =~ /\.oldbin\z/
90
+ @logger.warn("will not set pid=#{path} while reexec-ed "\
91
+ "child is running PID:#{x}")
92
+ return
93
+ end
94
+ raise ArgumentError, "Already running on PID:#{x} " \
95
+ "(or pid=#{path} is stale)"
96
+ end
97
+ end
98
+ unlink_pid_safe(@pid) if @pid
99
+
100
+ if path
101
+ fp = begin
102
+ tmp = "#{File.dirname(path)}/#{rand}.#$$"
103
+ File.open(tmp, File::RDWR|File::CREAT|File::EXCL, 0644)
104
+ rescue Errno::EEXIST
105
+ retry
106
+ end
107
+ fp.syswrite("#$$\n")
108
+ File.rename(fp.path, path)
109
+ fp.close
110
+ end
111
+ @pid = path
112
+ end
113
+
114
+ # add a given address to the +listeners+ set, idempotently
115
+ # Allows workers to add a private, per-process listener via the
116
+ # after_fork hook. Very useful for debugging and testing.
117
+ # +:tries+ may be specified as an option for the number of times
118
+ # to retry, and +:delay+ may be specified as the time in seconds
119
+ # to delay between retries.
120
+ # A negative value for +:tries+ indicates the listen will be
121
+ # retried indefinitely, this is useful when workers belonging to
122
+ # different masters are spawned during a transparent upgrade.
123
+ def listen(address)
124
+ address = @config.expand_addr(address)
125
+ return if String === address && listener_names.include?(address)
126
+
127
+ begin
128
+ io = bind_listen(address, sock_opts(address))
129
+ unless Kgio::TCPServer === io || Kgio::UNIXServer === io
130
+ io = server_cast(io)
131
+ end
132
+ @logger.info "listening on addr=#{sock_name(io)} fd=#{io.fileno}"
133
+ @listeners << io
134
+ io
135
+ rescue Errno::EADDRINUSE => err
136
+ @logger.error "adding listener failed addr=#{address} (in use)"
137
+ rescue => err
138
+ @logger.fatal "error adding listener addr=#{address}"
139
+ raise err
140
+ end
141
+ end
142
+
143
+ def daemon_ready
144
+ @daemon_pipe or return
145
+ @daemon_pipe.syswrite("#$$")
146
+ @daemon_pipe.close
147
+ @daemon_pipe = true # for SIGWINCH
148
+ end
149
+
150
+ # reexecutes the Yahns::START with a new binary
151
+ def reexec
152
+ if @reexec_pid > 0
153
+ begin
154
+ Process.kill(0, @reexec_pid)
155
+ @logger.error "reexec-ed child already running PID:#@reexec_pid"
156
+ return
157
+ rescue Errno::ESRCH
158
+ @reexec_pid = 0
159
+ end
160
+ end
161
+
162
+ if @pid
163
+ old_pid = "#@pid.oldbin"
164
+ begin
165
+ self.pid = old_pid # clear the path for a new pid file
166
+ rescue ArgumentError
167
+ @logger.error "old PID:#{valid_pid?(old_pid)} running with " \
168
+ "existing pid=#{old_pid}, refusing rexec"
169
+ return
170
+ rescue => e
171
+ @logger.error "error writing pid=#{old_pid} #{e.class} #{e.message}"
172
+ return
173
+ end
174
+ end
175
+
176
+ @reexec_pid = fork do
177
+ redirects = {}
178
+ listeners.each do |sock|
179
+ sock.close_on_exec = false
180
+ redirects[sock.fileno] = sock
181
+ end
182
+ ENV['YAHNS_FD'] = redirects.keys.map(&:to_s).join(',')
183
+ Dir.chdir(@config.value(:working_directory) || Yahns::START[:cwd])
184
+ cmd = [ Yahns::START[0] ].concat(Yahns::START[:argv])
185
+ @logger.info "executing #{cmd.inspect} (in #{Dir.pwd})"
186
+ cmd << redirects
187
+ exec(*cmd)
188
+ end
189
+ proc_name 'master (old)'
190
+ end
191
+
192
+ # unlinks a PID file at given +path+ if it contains the current PID
193
+ # still potentially racy without locking the directory (which is
194
+ # non-portable and may interact badly with other programs), but the
195
+ # window for hitting the race condition is small
196
+ def unlink_pid_safe(path)
197
+ (File.read(path).to_i == $$ and File.unlink(path)) rescue nil
198
+ end
199
+
200
+ # returns a PID if a given path contains a non-stale PID file,
201
+ # nil otherwise.
202
+ def valid_pid?(path)
203
+ wpid = File.read(path).to_i
204
+ wpid <= 0 and return
205
+ Process.kill(0, wpid)
206
+ wpid
207
+ rescue Errno::EPERM
208
+ @logger.info "pid=#{path} possibly stale, got EPERM signalling PID:#{wpid}"
209
+ nil
210
+ rescue Errno::ESRCH, Errno::ENOENT
211
+ # don't unlink stale pid files, racy without non-portable locking...
212
+ end
213
+
214
+ def load_config!
215
+ @logger.info "reloading config_file=#{@config.config_file}"
216
+ @config.config_reload!
217
+ @config.commit!(self)
218
+ kill_each_worker(:QUIT)
219
+ Yahns::Log.reopen_all
220
+ @logger.info "done reloading config_file=#{@config.config_file}"
221
+ rescue StandardError, LoadError, SyntaxError => e
222
+ Yahns::Log.exception(@logger,
223
+ "error reloading config_file=#{@config.config_file}", e)
224
+ end
225
+
226
+ # returns an array of string names for the given listener array
227
+ def listener_names(listeners = @listeners)
228
+ listeners.map { |io| sock_name(io) }
229
+ end
230
+
231
+ def sock_opts(io)
232
+ @config.config_listeners[sock_name(io)]
233
+ end
234
+
235
+ def inherit_listeners!
236
+ # inherit sockets from parents, they need to be plain Socket objects
237
+ # before they become Kgio::UNIXServer or Kgio::TCPServer
238
+ inherited = ENV['YAHNS_FD'].to_s.split(/,/).map do |fd|
239
+ io = Socket.for_fd(fd.to_i)
240
+ set_server_sockopt(io, sock_opts(io))
241
+ @logger.info "inherited addr=#{sock_name(io)} fd=#{fd}"
242
+ server_cast(io)
243
+ end
244
+
245
+ @listeners.replace(inherited)
246
+ end
247
+
248
+ # call only after calling inherit_listeners!
249
+ # This binds any listeners we did NOT inherit from the parent
250
+ def bind_new_listeners!
251
+ self.listeners = @config.config_listeners.keys
252
+ raise ArgumentError, "no listeners" if @listeners.empty?
253
+ @listeners.each { |l| l.extend(Yahns::Acceptor) }
254
+ end
255
+
256
+ def proc_name(tag)
257
+ s = Yahns::START
258
+ $0 = ([ File.basename(s[0]), tag ]).concat(s[:argv]).join(' ')
259
+ end
260
+
261
+ # spins up processing threads of the server
262
+ def fdmap_init
263
+ thresh = @config.value(:client_expire_threshold)
264
+
265
+ # keeps track of all connections, like ObjectSpace, but only for IOs
266
+ fdmap = Yahns::Fdmap.new(@logger, thresh)
267
+
268
+ # initialize queues (epoll/kqueue) and associated worker threads
269
+ queues = {}
270
+ @config.qeggs.each do |name, qegg|
271
+ queue = qegg.qc_vivify(fdmap) # worker threads run after this
272
+ queues[qegg] = queue
273
+ end
274
+
275
+ # spin up applications (which are preload: false)
276
+ @config.app_ctx.each { |ctx| ctx.after_fork_init }
277
+
278
+ # spin up acceptors, clients flow into worker queues after this
279
+ @listeners.each do |l|
280
+ ctx = sock_opts(l)[:yahns_app_ctx]
281
+ qegg = ctx.qegg || @config.qeggs[:default]
282
+
283
+ # acceptors feed the the queues
284
+ l.spawn_acceptor(@logger, ctx, queues[qegg])
285
+ end
286
+ fdmap
287
+ end
288
+
289
+ def usr1_reopen(prefix)
290
+ @logger.info "#{prefix}reopening logs..."
291
+ Yahns::Log.reopen_all
292
+ @logger.info "#{prefix}done reopening logs"
293
+ end
294
+
295
+ def quit_enter(alive)
296
+ self.listeners = []
297
+ exit(0) unless alive # drop connections immediately if signaled twice
298
+ @config.config_listeners.each_value do |opts|
299
+ ctx = opts[:yahns_app_ctx] or next
300
+ ctx.persistent_connections = false # Yahns::HttpContext
301
+ end
302
+ false
303
+ end
304
+
305
+ def sp_sig_handle(alive)
306
+ @sev.kgio_wait_readable(alive ? nil : 0.01)
307
+ @sev.yahns_step
308
+ case sig = @sig_queue.shift
309
+ when :QUIT, :TERM, :INT
310
+ return quit_enter(alive)
311
+ when :USR1
312
+ usr1_reopen('')
313
+ when :USR2
314
+ reexec
315
+ when :HUP
316
+ reexec
317
+ return false
318
+ when :TTIN, :TTOU, :WINCH
319
+ @logger.info("SIG#{sig} ignored in single-process mode")
320
+ end
321
+ alive
322
+ end
323
+
324
+ # single-threaded only, this is overriden if @worker_processes is non-nil
325
+ def join
326
+ daemon_ready
327
+ fdmap = fdmap_init
328
+ alive = true
329
+ begin
330
+ alive = sp_sig_handle(alive)
331
+ rescue => e
332
+ Yahns::Log.exception(@logger, "main loop", e)
333
+ end while alive || fdmap.size > 0
334
+ unlink_pid_safe(@pid) if @pid
335
+ end
336
+ end