1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
|
# frozen_string_literal: true
require "pathname"
class ServerManager
ROOT = Pathname.new(File.expand_path("../../", __dir__))
class << self
def kill_all
Dir[ROOT.join("tmp/**/*.pid").to_s].each do |pid_file|
pid = begin
Integer(File.read(pid_file))
rescue ArgumentError
nil
end
if pid
begin
Process.kill(:KILL, pid)
rescue Errno::ESRCH, Errno::ECHILD
nil # It's fine
end
end
File.unlink(pid_file)
end
end
end
@worker_index = nil
singleton_class.attr_accessor :worker_index
module NullIO
extend self
def puts(_str)
nil
end
def print(_str)
nil
end
end
attr_reader :name, :host, :command
attr_accessor :out
def initialize(name, port:, command: nil, real_port: port, host: "127.0.0.1")
@name = name
@host = host
@port = port
@real_port = real_port
@command = command
@out = $stderr
end
def worker_index
ServerManager.worker_index
end
def port_offset
worker_index.to_i * 200
end
def port
@port + port_offset
end
def real_port
@real_port + port_offset
end
def spawn
shutdown
pid_file.parent.mkpath
pid = Process.spawn(*command.map(&:to_s), out: log_file.to_s, err: log_file.to_s)
pid_file.write(pid.to_s)
@out.puts "started #{name}-#{worker_index.to_i} with pid=#{pid}"
end
def wait(timeout: 5)
unless wait_until_ready(timeout: 1)
@out.puts "Waiting for #{name}-#{worker_index.to_i} (port #{real_port})..."
end
if wait_until_ready(timeout: timeout - 1)
@out.puts "#{name}-#{worker_index.to_i} ready."
true
else
@out.puts "#{name}-#{worker_index.to_i} timedout."
false
end
end
def health_check
TCPSocket.new(host, real_port)
true
rescue Errno::ECONNREFUSED
false
end
def on_ready
nil
end
def wait_until_ready(timeout: 5)
(timeout * 100).times do
if health_check
on_ready
return true
else
sleep 0.01
end
end
false
end
def shutdown
if alive?
pid = self.pid
Process.kill("INT", pid)
Process.wait(pid)
end
true
rescue Errno::ESRCH, Errno::ECHILD
true
end
def pid
Integer(pid_file.read)
rescue Errno::ENOENT, ArgumentError
nil
end
def alive?
pid = self.pid
return false unless pid
pid && Process.kill(0, pid)
true
rescue Errno::ESRCH
false
end
private
def dir
ROOT.join("tmp/#{name}-#{worker_index.to_i}").tap(&:mkpath)
end
def pid_file
dir.join("#{name}.pid")
end
def log_file
dir.join("#{name}.log")
end
end
class ServerList
def initialize(*servers)
@servers = servers
end
def silence
@servers.each { |s| s.out = ServerManager::NullIO }
yield
ensure
@servers.each { |s| s.out = $stderr }
end
def prepare
shutdown
@servers.each(&:spawn)
@servers.all?(&:wait)
end
def reset
silence { prepare }
end
def shutdown
@servers.reverse_each(&:shutdown)
end
end
|