Class: By::Worker
- Inherits:
-
Object
- Object
- By::Worker
- Defined in:
- lib/by/worker.rb
Instance Attribute Summary collapse
-
#normal_exit ⇒ Object
writeonly
Whether the worker process should signal a normal exit to the client.
Instance Method Summary collapse
-
#chdir(dir) ⇒ Object
Change to the given directory.
-
#chdir_or_stop ⇒ Object
Change to the given directory, unless the client is telling the worker to stop the server.
-
#cleanup_proc ⇒ Object
A proc for communicating exit status to the client, and printing the loaded features if configured.
-
#get_args ⇒ Object
An array of arguments provided by the client.
-
#handle_args(args) ⇒ Object
Handle arguments provided by the client.
-
#initialize(socket) ⇒ Worker
constructor
Create the worker.
-
#print_loaded_features ⇒ Object
Print
$LOADED_FEATURES
to stdout. -
#reopen_stdio ⇒ Object
Replace stdin, stdout, stderr with the IO values provided by the client.
-
#replace_env ⇒ Object
Replace ENV with the environment provided by the client.
-
#run ⇒ Object
Run the worker process.
-
#stop_server ⇒ Object
Stop the server process by sending it the SIGQUIT signal, then exit.
-
#write_pid ⇒ Object
Write the current process pid to the client, to signal that the worker process is ready.
Constructor Details
#initialize(socket) ⇒ Worker
Create the worker. Arguments
- socket
-
The Unix socket to use to communicate with the client.
sigterm_handler
14 15 16 17 |
# File 'lib/by/worker.rb', line 14 def initialize(socket) @socket = socket @normal_exit = nil end |
Instance Attribute Details
#normal_exit=(value) ⇒ Object (writeonly)
Whether the worker process should signal a normal exit to the client. The default is nil, which signals a normal exit when the worker process exits normally. This can be set to false to signal an abnormal exit, such as to indicate test failures.
9 10 11 |
# File 'lib/by/worker.rb', line 9 def normal_exit=(value) @normal_exit = value end |
Instance Method Details
#chdir(dir) ⇒ Object
Change to the given directory.
78 79 80 |
# File 'lib/by/worker.rb', line 78 def chdir(dir) Dir.chdir(dir) end |
#chdir_or_stop ⇒ Object
Change to the given directory, unless the client is telling the worker to stop the server.
44 45 46 47 |
# File 'lib/by/worker.rb', line 44 def chdir_or_stop arg = @socket.readline("\0", chomp: true) arg == 'stop' ? stop_server : chdir(arg) end |
#cleanup_proc ⇒ Object
A proc for communicating exit status to the client, and printing the loaded features if configured. Used during process shutdown.
90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/by/worker.rb', line 90 def cleanup_proc proc do if @normal_exit.nil? @normal_exit = $!.nil? || ($!.is_a?(SystemExit) && $!.success?) end @socket.write(@normal_exit ? '0' : '1') @socket.shutdown(Socket::SHUT_WR) @socket.close print_loaded_features if ENV['DEBUG'] == 'log' end end |
#get_args ⇒ Object
An array of arguments provided by the client.
114 115 116 117 118 119 120 |
# File 'lib/by/worker.rb', line 114 def get_args args = [] while !@socket.eof? args << @socket.readline("\0", chomp: true) end args end |
#handle_args(args) ⇒ Object
Handle arguments provided by the client. Ensure correct client after handling the arguments.
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 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/by/worker.rb', line 124 def handle_args(args) worker = self cleanup = cleanup_proc case arg = args.first when 'm', /\.rb:\d+\z/ args.shift if arg == 'm' ARGV.replace(args) require 'm' M.define_singleton_method(:exit!) do |exit_code| worker.normal_exit = exit_code == true cleanup.call super(exit_code) end M.run(args) when 'rspec' args.shift ARGV.replace(args) require 'rspec/core' RSpec.configure do |c| # Fix start_time to be accurate if rspec was required by by-server c.instance_variable_set(:@start_time, Time.now) end # invoke exits non-zero if there are failures RSpec::Core::Runner.define_singleton_method(:exit) do |exit_code| worker.normal_exit = exit_code == 0 cleanup.call super(exit_code) end RSpec::Core::Runner.invoke at_exit(&cleanup) when 'irb' at_exit(&cleanup) args.shift ARGV.replace(args) require 'irb' IRB.start(__FILE__) when '-e' at_exit(&cleanup) unless args.length >= 2 $stderr.puts 'no code specified for -e (RuntimeError)' exit(1) end args.shift code = args.shift ARGV.replace(args) ::TOPLEVEL_BINDING.eval(code) when String args.shift ARGV.replace(args) begin require File.(arg) rescue @normal_exit = false at_exit(&cleanup) raise end if defined?(Minitest) && Minitest.class_variable_get(:@@installed_at_exit) Minitest.singleton_class.prepend(Module.new do define_method(:run) do |argv| super(argv).tap{|exit_code| worker.normal_exit = exit_code == true} end end) Minitest.after_run(&cleanup) else at_exit(&cleanup) end else # no arguments at_exit(&cleanup) ::TOPLEVEL_BINDING.eval($stdin.read) end end |
#print_loaded_features ⇒ Object
Print $LOADED_FEATURES
to stdout.
83 84 85 |
# File 'lib/by/worker.rb', line 83 def print_loaded_features puts $LOADED_FEATURES end |
#reopen_stdio ⇒ Object
Replace stdin, stdout, stderr with the IO values provided by the client.
36 37 38 39 40 |
# File 'lib/by/worker.rb', line 36 def reopen_stdio $stdin.reopen(@socket.recv_io(IO)) $stdout.reopen(@socket.recv_io(IO)) $stderr.reopen(@socket.recv_io(IO)) end |
#replace_env ⇒ Object
Replace ENV with the environment provided by the client.
103 104 105 106 107 108 109 110 111 |
# File 'lib/by/worker.rb', line 103 def replace_env env = {} while line = @socket.readline("\0", chomp: true) break if line.empty? k, v = line.split("=") env[k] = v end ENV.replace(env) end |
#run ⇒ Object
Run the worker process.
20 21 22 23 24 25 26 |
# File 'lib/by/worker.rb', line 20 def run write_pid reopen_stdio chdir_or_stop replace_env handle_args(get_args) end |
#stop_server ⇒ Object
Stop the server process by sending it the SIGQUIT signal, then exit.
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 |
# File 'lib/by/worker.rb', line 50 def stop_server i = 0 while Process.ppid != 1 && Process.kill(0, Process.ppid) if i < 4 Process.kill(:QUIT, Process.ppid) end sleep 0.1 if i == 5 # This is only reached if the QUIT signal does not # cause the process to exit. Process.kill(:KILL, Process.ppid) end if i > 8 # This is only reached if the process has still not # stopped even after the KILL signal was sent. $stderr.puts "ERROR: cannot stop by-server" @normal_exit = false break end i += 1 end cleanup_proc.call exit end |
#write_pid ⇒ Object
Write the current process pid to the client, to signal that the worker process is ready.
30 31 32 33 |
# File 'lib/by/worker.rb', line 30 def write_pid @socket.write($$.to_s) @socket.write("\0") end |