Module: Yahns::ServerMP
- Defined in:
- lib/yahns/server_mp.rb
Overview
Copyright © 2013-2016 all contributors <[email protected]> License: GPL-3.0+ (www.gnu.org/licenses/gpl-3.0.txt) frozen_string_literal: true
Constant Summary collapse
- EXIT_SIGS =
:nodoc:
[ :QUIT, :TERM, :INT ]
Instance Method Summary collapse
- #__call_hooks(ary, worker_nr) ⇒ Object
- #fdmap_init_mp ⇒ Object
-
#join ⇒ Object
monitors children and receives signals forever (or until a termination signal is sent).
- #maintain_worker_count ⇒ Object
- #mp_sig_handle(watch, alive) ⇒ Object
-
#reap_all ⇒ Object
reaps all unreaped workers/reexec processes.
- #run_mp_worker(worker) ⇒ Object
-
#soft_kill_each_worker(sig) ⇒ Object
fakes delivery of a signal to each worker.
- #spawn_missing_workers ⇒ Object
-
#worker_atfork_internal(worker) ⇒ Object
this is the first thing that runs after forking in a child gets rid of stuff the worker has no business keeping track of to free some resources and drops all sig handlers.
Instance Method Details
#__call_hooks(ary, worker_nr) ⇒ Object
58 59 60 |
# File 'lib/yahns/server_mp.rb', line 58 def __call_hooks(ary, worker_nr) ary.each { |x| x.call(worker_nr) } if ary end |
#fdmap_init_mp ⇒ Object
138 139 140 141 142 143 |
# File 'lib/yahns/server_mp.rb', line 138 def fdmap_init_mp fdmap = fdmap_init # builds apps (if not preloading) [:USR1, *EXIT_SIGS].each { |sig| trap(sig) { sqwakeup(sig) } } @config.postfork_cleanup # reduce live objects fdmap end |
#join ⇒ Object
monitors children and receives signals forever (or until a termination signal is sent). This handles signals one-at-a-time time and we’ll happily drop signals in case somebody is signalling us too often.
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/yahns/server_mp.rb', line 87 def join spawn_missing_workers state = :respawn # :QUIT, :WINCH proc_name 'master' @logger.info "master process ready" daemon_ready begin @sev.kgio_wait_readable @sev.yahns_step reap_all case @sig_queue.shift when *EXIT_SIGS # graceful shutdown (twice for non graceful) @listeners.each(&:close).clear soft_kill_each_worker("QUIT") state = :QUIT when :USR1 # rotate logs usr1_reopen("master ") soft_kill_each_worker("USR1") when :USR2 # exec binary, stay alive in case something went wrong reexec when :WINCH if $stdin.tty? @logger.info "SIGWINCH ignored because we're not daemonized" else state = :WINCH @logger.info "gracefully stopping all workers" soft_kill_each_worker("QUIT") @worker_processes = 0 end when :TTIN state = :respawn unless state == :QUIT @worker_processes += 1 when :TTOU @worker_processes -= 1 if @worker_processes > 0 when :HUP state = :respawn unless state == :QUIT if @config.config_file load_config! else # exec binary and exit if there's no config file @logger.info "config_file not present, reexecuting binary" reexec end end while @sig_queue[0] maintain_worker_count if state == :respawn rescue => e Yahns::Log.exception(@logger, "master loop error", e) end while state != :QUIT || @workers.size > 0 @logger.info "master complete" unlink_pid_safe(@pid) if @pid end |
#maintain_worker_count ⇒ Object
9 10 11 12 13 14 15 |
# File 'lib/yahns/server_mp.rb', line 9 def maintain_worker_count (off = @workers.size - @worker_processes) == 0 and return off < 0 and return spawn_missing_workers @workers.each_value do |worker| worker.nr >= @worker_processes and worker.soft_kill(Signal.list["QUIT"]) end end |
#mp_sig_handle(watch, alive) ⇒ Object
159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/yahns/server_mp.rb', line 159 def mp_sig_handle(watch, alive) # not performance critical watch.delete_if { |io| io.to_io.closed? } if r = IO.select(watch, nil, nil, alive ? nil : 0.1) r[0].each(&:yahns_step) end case @sig_queue.shift when *EXIT_SIGS return quit_enter(alive) when :USR1 usr1_reopen("worker ") end alive end |
#reap_all ⇒ Object
reaps all unreaped workers/reexec processes
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/yahns/server_mp.rb', line 175 def reap_all begin wpid, status = Process.waitpid2(-1, Process::WNOHANG) wpid or return if @reexec_pid == wpid @logger.error "reaped #{status.inspect} exec()-ed" @reexec_pid = 0 self.pid = @pid.chomp('.oldbin') if @pid proc_name('master') else worker = @workers.delete(wpid) desc = worker ? "worker=#{worker.nr}" : "(unknown)" m = "reaped #{status.inspect} #{desc}" status.success? ? @logger.info(m) : @logger.error(m) end rescue Errno::ECHILD return end while true end |
#run_mp_worker(worker) ⇒ Object
145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/yahns/server_mp.rb', line 145 def run_mp_worker(worker) fdmap = fdmap_init_mp alive = true watch = [ worker, @sev ] begin alive = mp_sig_handle(watch, alive) rescue => e Yahns::Log.exception(@logger, "main worker loop", e) end while alive || dropping(fdmap) exit ensure quit_finish end |
#soft_kill_each_worker(sig) ⇒ Object
fakes delivery of a signal to each worker
18 19 20 21 |
# File 'lib/yahns/server_mp.rb', line 18 def soft_kill_each_worker(sig) sig = Signal.list[sig] @workers.each_value { |worker| worker.soft_kill(sig) } end |
#spawn_missing_workers ⇒ Object
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/yahns/server_mp.rb', line 62 def spawn_missing_workers worker_nr = -1 until (worker_nr += 1) == @worker_processes @workers.value?(worker_nr) and next worker = Yahns::Worker.new(worker_nr) @logger.info("worker=#{worker_nr} spawning...") __call_hooks(@atfork_prepare, worker_nr) if pid = fork @workers[pid] = worker.atfork_parent # XXX is this useful? __call_hooks(@atfork_parent, worker_nr) else worker_atfork_internal(worker) run_mp_worker(worker) end end rescue => e Yahns::Log.exception(@logger, "spawning worker", e) exit! end |
#worker_atfork_internal(worker) ⇒ Object
this is the first thing that runs after forking in a child gets rid of stuff the worker has no business keeping track of to free some resources and drops all sig handlers. traps for USR1, USR2, and HUP may be set in the after_fork Proc by the user.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/yahns/server_mp.rb', line 28 def worker_atfork_internal(worker) worker.atfork_child # daemon_pipe may be true for non-initial workers @daemon_pipe = @daemon_pipe.close if @daemon_pipe.respond_to?(:close) srand # in case this pops up again: https://bugs.ruby-lang.org/issues/4338 # The OpenSSL PRNG is seeded with only the pid, and apps with frequently # dying workers can recycle pids OpenSSL::Random.seed(rand.to_s) if defined?(OpenSSL::Random) # we'll re-trap EXIT_SIGS later for graceful shutdown iff we accept clients EXIT_SIGS.each { |sig| trap(sig) { exit!(0) } } exit!(0) if (@sig_queue & EXIT_SIGS)[0] # did we inherit sigs from parent? @sig_queue = [] # ignore WINCH, TTIN, TTOU, HUP in the workers (Yahns::Server::QUEUE_SIGS - EXIT_SIGS).each { |sig| trap(sig, nil) } trap(:CHLD, 'DEFAULT') @logger.info("worker=#{worker.nr} spawned pid=#$$") proc_name "worker[#{worker.nr}]" Yahns::START.clear @sev.close @sev = Yahns::Sigevent.new switch_user(*@user) if @user @user = @workers = nil __call_hooks(@atfork_child, worker.nr) @atfork_child = @atfork_parent = @atfork_prepare = nil end |