Class: Autorespawn::Slave
- Inherits:
-
Object
- Object
- Autorespawn::Slave
- Defined in:
- lib/autorespawn/slave.rb
Overview
Representation of an autorespawn-aware subprocess that is started by a Manager
Slaves have two roles: the one of discovery (what are the commands that need to be started) and the one of
Direct Known Subclasses
Instance Attribute Summary collapse
-
#cmdline ⇒ Object
readonly
The command line of the subprocess.
-
#name ⇒ Object
readonly
The slave’s name.
-
#pid ⇒ nil, Integer
readonly
Pid the PID of the current process or of the last process if it is finished.
-
#program_id ⇒ Object
readonly
The currently known program ID.
-
#result_buffer ⇒ String
readonly
private
The result data as received.
-
#result_r ⇒ IO
readonly
private
The result I/O.
-
#spawn_env ⇒ Object
readonly
Environment that should be set in the subprocess.
-
#spawn_options ⇒ Object
readonly
Options that should be passed to Kernel.spawn.
-
#status ⇒ Process::Status
readonly
The exit status of the last run.
-
#subcommands ⇒ Array<String>
readonly
A list of commands that this slave requests.
Instance Method Summary collapse
-
#finished(status) ⇒ Object
private
Announce that the slave already finished, with the given exit status.
-
#finished? ⇒ Boolean
Whether the slave has already ran, and is finished.
-
#initialize(*cmdline, name: nil, seed: ProgramID.new, env: Hash.new, **spawn_options) ⇒ Slave
constructor
A new instance of Slave.
- #inspect ⇒ Object
-
#join ⇒ Object
Wait for the slave to terminate and call #finished.
-
#kill(signal = 'TERM', join: true) ⇒ Object
Kill the slave.
-
#needed! ⇒ Object
Marks this slave for execution.
-
#needed? ⇒ Boolean
Whether this slave would need to be spawned, either because it has never be, or because the program ID changed.
-
#not_needed! ⇒ Object
Resets #needed? to false.
-
#read_queued_result ⇒ Object
private
Queue any pending result data sent by the slave.
-
#register_files(files, search_path = program_id.ruby_load_path, ignore_not_found: true) ⇒ Object
Register files on the program ID.
-
#running? ⇒ Boolean
Whether the slave is running.
-
#spawn ⇒ Integer
Start the slave.
-
#success? ⇒ Boolean
Whether the slave behaved properly.
- #to_s ⇒ Object
Constructor Details
#initialize(*cmdline, name: nil, seed: ProgramID.new, env: Hash.new, **spawn_options) ⇒ Slave
Returns a new instance of Slave.
46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/autorespawn/slave.rb', line 46 def initialize(*cmdline, name: nil, seed: ProgramID.new, env: Hash.new, **) @name = name @program_id = seed.dup @cmdline = cmdline @needed = true @spawn_env = env = @subcommands = Array.new @pid = nil @status = nil @result_r = nil @result_buffer = nil end |
Instance Attribute Details
#cmdline ⇒ Object (readonly)
The command line of the subprocess
17 18 19 |
# File 'lib/autorespawn/slave.rb', line 17 def cmdline @cmdline end |
#name ⇒ Object (readonly)
The slave’s name
It is an arbitrary object useful for reporting/tracking
13 14 15 |
# File 'lib/autorespawn/slave.rb', line 13 def name @name end |
#pid ⇒ nil, Integer (readonly)
Returns pid the PID of the current process or of the last process if it is finished. It is non-nil only after #spawn has been called.
25 26 27 |
# File 'lib/autorespawn/slave.rb', line 25 def pid @pid end |
#program_id ⇒ Object (readonly)
The currently known program ID
15 16 17 |
# File 'lib/autorespawn/slave.rb', line 15 def program_id @program_id end |
#result_buffer ⇒ String (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the result data as received.
40 41 42 |
# File 'lib/autorespawn/slave.rb', line 40 def result_buffer @result_buffer end |
#result_r ⇒ IO (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the result I/O.
36 37 38 |
# File 'lib/autorespawn/slave.rb', line 36 def result_r @result_r end |
#spawn_env ⇒ Object (readonly)
Environment that should be set in the subprocess
19 20 21 |
# File 'lib/autorespawn/slave.rb', line 19 def spawn_env @spawn_env end |
#spawn_options ⇒ Object (readonly)
Options that should be passed to Kernel.spawn
21 22 23 |
# File 'lib/autorespawn/slave.rb', line 21 def end |
#status ⇒ Process::Status (readonly)
Returns the exit status of the last run. Is nil while the process is running.
28 29 30 |
# File 'lib/autorespawn/slave.rb', line 28 def status @status end |
#subcommands ⇒ Array<String> (readonly)
Returns a list of commands that this slave requests.
31 32 33 |
# File 'lib/autorespawn/slave.rb', line 31 def subcommands @subcommands end |
Instance Method Details
#finished(status) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Announce that the slave already finished, with the given exit status
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/autorespawn/slave.rb', line 180 def finished(status) @status = status read_queued_result begin @subcommands, file_list = Marshal.load(result_buffer) @success = true rescue ArgumentError # "Marshal data too short" @subcommands = Array.new file_list = Array.new @success = false end modified = program_id.register_files(file_list) @program_id = program_id.slice(file_list) if !modified.empty? needed! end result_r.close modified end |
#finished? ⇒ Boolean
Whether the slave has already ran, and is finished
141 142 143 |
# File 'lib/autorespawn/slave.rb', line 141 def finished? pid && status end |
#inspect ⇒ Object
60 61 62 |
# File 'lib/autorespawn/slave.rb', line 60 def inspect "#<Autorespawn::Slave #{object_id.to_s(16)} #{cmdline.join(" ")}>" end |
#join ⇒ Object
Wait for the slave to terminate and call #finished
158 159 160 161 |
# File 'lib/autorespawn/slave.rb', line 158 def join _, status = Process.waitpid2(pid) finished(status) end |
#kill(signal = 'TERM', join: true) ⇒ Object
Kill the slave
150 151 152 153 154 155 |
# File 'lib/autorespawn/slave.rb', line 150 def kill(signal = 'TERM', join: true) Process.kill signal, pid if join self.join end end |
#needed! ⇒ Object
Marks this slave for execution
Note that it will only be executed by the underlying Manager when (1) a slot is available and (2) it has stopped running
This is usually not called directly, but through Manager#queue which also ensures that the slave gets in front of the execution queue
126 127 128 |
# File 'lib/autorespawn/slave.rb', line 126 def needed! @needed = true end |
#needed? ⇒ Boolean
Whether this slave would need to be spawned, either because it has never be, or because the program ID changed
115 116 117 |
# File 'lib/autorespawn/slave.rb', line 115 def needed? !running? && (@needed || program_id.changed?) end |
#not_needed! ⇒ Object
Resets #needed? to false
131 132 133 |
# File 'lib/autorespawn/slave.rb', line 131 def not_needed! @needed = false end |
#read_queued_result ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Queue any pending result data sent by the slave
203 204 205 206 207 208 |
# File 'lib/autorespawn/slave.rb', line 203 def read_queued_result while true result_buffer << result_r.read_nonblock(1024) end rescue IO::WaitReadable, EOFError end |
#register_files(files, search_path = program_id.ruby_load_path, ignore_not_found: true) ⇒ Object
Register files on the program ID
(see ProgramID#register_files)
69 70 71 |
# File 'lib/autorespawn/slave.rb', line 69 def register_files(files, search_path = program_id.ruby_load_path, ignore_not_found: true) program_id.register_files(files, search_path, ignore_not_found: ignore_not_found) end |
#running? ⇒ Boolean
Whether the slave is running
136 137 138 |
# File 'lib/autorespawn/slave.rb', line 136 def running? pid && !status end |
#spawn ⇒ Integer
Start the slave
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 |
# File 'lib/autorespawn/slave.rb', line 76 def spawn if running? raise AlreadyRunning, "cannot call #spawn on #{self}: already running" end initial_r, initial_w = IO.pipe result_r, result_w = IO.pipe env = self.spawn_env.merge( SLAVE_INITIAL_STATE_ENV => initial_r.fileno.to_s, SLAVE_RESULT_ENV => result_w.fileno.to_s) program_id.refresh @needed = false pid = Kernel.spawn(env, *cmdline, initial_r => initial_r, result_w => result_w, **) initial_r.close result_w.close Marshal.dump([name, program_id], initial_w) @pid = pid @status = nil @result_buffer = '' @result_r = result_r pid rescue Exception => e if pid Process.kill 'TERM', pid end result_r.close if result_r && !result_r.closed? raise ensure initial_r.close if initial_r && !initial_r.closed? initial_w.close if initial_w && !initial_r.closed? result_w.close if result_w && !result_w.closed? end |
#success? ⇒ Boolean
Whether the slave behaved properly
This does not indicate whether the slave’s intended work has been done, only that it produced the data expected by Autorespawn. To check the child’s success w.r.t. its execution, check #status
168 169 170 171 172 173 |
# File 'lib/autorespawn/slave.rb', line 168 def success? if !status raise NotFinished, "called {#success?} on a #{pid ? 'running' : 'non-started'} child" end @success end |
#to_s ⇒ Object
64 |
# File 'lib/autorespawn/slave.rb', line 64 def to_s; inspect end |