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
-
#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.
-
#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
#each_tracked_file(with_status: false, &block) ⇒ Object
Enumerate the files that are tracked for #needed?
65 66 67 |
# File 'lib/autorespawn/slave.rb', line 65 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
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
# File 'lib/autorespawn/slave.rb', line 199 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 result_r.close modified end |
#finished? ⇒ Boolean
Whether the slave has already ran, and is finished
157 158 159 |
# File 'lib/autorespawn/slave.rb', line 157 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
174 175 176 177 |
# File 'lib/autorespawn/slave.rb', line 174 def join _, status = Process.waitpid2(pid) finished(status) end |
#kill(signal = 'TERM', join: true) ⇒ Object
Kill the slave
166 167 168 169 170 171 |
# File 'lib/autorespawn/slave.rb', line 166 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
135 136 137 |
# File 'lib/autorespawn/slave.rb', line 135 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
120 121 122 123 124 125 126 |
# File 'lib/autorespawn/slave.rb', line 120 def needed? if running? then false elsif !@needed.nil? @needed else program_id.changed? end end |
#needed_auto ⇒ Object
147 148 149 |
# File 'lib/autorespawn/slave.rb', line 147 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
143 144 145 |
# File 'lib/autorespawn/slave.rb', line 143 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
222 223 224 225 226 227 |
# File 'lib/autorespawn/slave.rb', line 222 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)
74 75 76 |
# File 'lib/autorespawn/slave.rb', line 74 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
152 153 154 |
# File 'lib/autorespawn/slave.rb', line 152 def running? pid && !status end |
#spawn ⇒ Integer
Start the slave
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 |
# File 'lib/autorespawn/slave.rb', line 81 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 = nil 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
184 185 186 187 188 189 |
# File 'lib/autorespawn/slave.rb', line 184 def success? if !status raise NotFinished, "called {#success?} on a #{pid ? 'running' : 'non-started'} child" end @success end |
#to_s ⇒ Object
69 |
# File 'lib/autorespawn/slave.rb', line 69 def to_s; inspect end |