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.
-
#initial_dump ⇒ String
readonly
private
What is remaining of the initial dump that should be passed to the slave.
-
#initial_w ⇒ IO
readonly
private
The IO used to transmit initial information to the slave.
-
#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
-
#each_tracked_file(with_status: false, &block) ⇒ Object
Enumerate the files that are tracked for #needed?.
-
#finished(status) ⇒ Array<Pathname>
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.
- #needed_auto ⇒ Object
-
#not_needed! ⇒ Object
Forces #needed? to return false.
-
#poll ⇒ Object
Must be called regularly to ensure a good communication with the slave.
-
#read_queued_result ⇒ Object
private
Queue any pending result data sent by the slave.
-
#register_files(files) ⇒ Object
Register files on the program ID.
-
#running? ⇒ Boolean
Whether the slave is running.
-
#spawn(send_initial_dump: true) ⇒ Integer
Start the slave.
-
#success? ⇒ Boolean
Whether the slave behaved properly.
- #to_s ⇒ Object
-
#write_initial_dump ⇒ Object
Write as much of the initial dump to the slave.
Constructor Details
#initialize(*cmdline, name: nil, seed: ProgramID.new, env: Hash.new, **spawn_options) ⇒ Slave
Returns a new instance of Slave.
55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/autorespawn/slave.rb', line 55 def initialize(*cmdline, name: nil, seed: ProgramID.new, env: Hash.new, **) @name = name @program_id = seed.dup @cmdline = cmdline @needed = true @spawn_env = env @spawn_options = @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 |
#initial_dump ⇒ 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 what is remaining of the initial dump that should be passed to the slave.
49 50 51 |
# File 'lib/autorespawn/slave.rb', line 49 def initial_dump @initial_dump end |
#initial_w ⇒ 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 IO used to transmit initial information to the slave.
44 45 46 |
# File 'lib/autorespawn/slave.rb', line 44 def initial_w @initial_w 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 @spawn_options 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
#each_tracked_file(with_status: false, &block) ⇒ Object
Enumerate the files that are tracked for #needed?
74 75 76 |
# File 'lib/autorespawn/slave.rb', line 74 def each_tracked_file(with_status: false, &block) @program_id.each_tracked_file(with_status: with_status, &block) end |
#finished(status) ⇒ Array<Pathname>
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
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/autorespawn/slave.rb', line 241 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 @program_id = program_id.slice(file_list) modified = program_id.register_files(file_list) if !modified.empty? needed! end @success = @success && status.success? result_r.close initial_w.close modified end |
#finished? ⇒ Boolean
Whether the slave has already ran, and is finished
199 200 201 |
# File 'lib/autorespawn/slave.rb', line 199 def finished? pid && status end |
#inspect ⇒ Object
69 70 71 |
# File 'lib/autorespawn/slave.rb', line 69 def inspect "#<Autorespawn::Slave #{object_id.to_s(16)} #{cmdline.join(" ")}>" end |
#join ⇒ Object
Wait for the slave to terminate and call #finished
216 217 218 219 |
# File 'lib/autorespawn/slave.rb', line 216 def join _, status = Process.waitpid2(pid) finished(status) end |
#kill(signal = 'TERM', join: true) ⇒ Object
Kill the slave
208 209 210 211 212 213 |
# File 'lib/autorespawn/slave.rb', line 208 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
177 178 179 |
# File 'lib/autorespawn/slave.rb', line 177 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
162 163 164 165 166 167 168 |
# File 'lib/autorespawn/slave.rb', line 162 def needed? if running? then false elsif !@needed.nil? @needed else program_id.changed? end end |
#needed_auto ⇒ Object
189 190 191 |
# File 'lib/autorespawn/slave.rb', line 189 def needed_auto @needed = nil end |
#not_needed! ⇒ Object
Forces #needed? to return false
Call #needed_auto to revert back to determining if the slave is needed or not using the tracked files
185 186 187 |
# File 'lib/autorespawn/slave.rb', line 185 def not_needed! @needed = false end |
#poll ⇒ Object
Must be called regularly to ensure a good communication with the slave
140 141 142 143 144 145 |
# File 'lib/autorespawn/slave.rb', line 140 def poll return unless running? write_initial_dump read_queued_result 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
266 267 268 269 270 271 |
# File 'lib/autorespawn/slave.rb', line 266 def read_queued_result while true result_buffer << result_r.read_nonblock(1024) end rescue IO::WaitReadable, EOFError end |
#register_files(files) ⇒ Object
Register files on the program ID
(see ProgramID#register_files)
83 84 85 |
# File 'lib/autorespawn/slave.rb', line 83 def register_files(files) program_id.register_files(files) end |
#running? ⇒ Boolean
Whether the slave is running
194 195 196 |
# File 'lib/autorespawn/slave.rb', line 194 def running? pid && !status end |
#spawn(send_initial_dump: true) ⇒ Integer
Start the slave
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 |
# File 'lib/autorespawn/slave.rb', line 97 def spawn(send_initial_dump: true) 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 = nil pid = Kernel.spawn(env, *cmdline, initial_r => initial_r, result_w => result_w, **) initial_r.close result_w.close @initial_w = initial_w @initial_dump = Marshal.dump([name, program_id]) initial_w.write([initial_dump.size].pack("L<")) if send_initial_dump while !write_initial_dump select([], [initial_w]) end end @pid = pid @status = nil @result_buffer = '' @result_r = result_r pid rescue Exception if pid Process.kill 'TERM', pid end initial_w.close if initial_w && !initial_w.closed? initial_r.close if initial_r && !initial_r.closed? result_w.close if result_w && !result_w.closed? result_r.close if result_r && !result_r.closed? raise 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
226 227 228 229 230 231 |
# File 'lib/autorespawn/slave.rb', line 226 def success? if !status raise NotFinished, "called {#success?} on a #{pid ? 'running' : 'non-started'} child" end @success end |
#to_s ⇒ Object
78 |
# File 'lib/autorespawn/slave.rb', line 78 def to_s; inspect end |
#write_initial_dump ⇒ Object
Write as much of the initial dump to the slave
To avoid blocking in #spawn, the initial dump
150 151 152 153 154 155 156 157 158 |
# File 'lib/autorespawn/slave.rb', line 150 def write_initial_dump return if initial_dump.empty? written_bytes = initial_w.write_nonblock(initial_dump) @initial_dump = @initial_dump[written_bytes, initial_dump.size - written_bytes] initial_dump.empty? rescue IO::WaitWritable true end |