Class: Autorespawn::Slave

Inherits:
Object
  • Object
show all
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

Self

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*cmdline, name: nil, seed: ProgramID.new, env: Hash.new, **spawn_options) ⇒ Slave

Returns a new instance of Slave.

Parameters:

  • name (Object) (defaults to: nil)

    an arbitrary object that can be used for reporting / tracking reasons

  • seed (ProgramID) (defaults to: ProgramID.new)

    a seed object with some relevant files already registered, to avoid respawning the slave unnecessarily.



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, **spawn_options)
    @name = name
    @program_id = seed.dup
    @cmdline    = cmdline
    @needed = true
    @spawn_env     = env
    @spawn_options = spawn_options
    @subcommands = Array.new
    @pid        = nil
    @status     = nil
    @result_r  = nil
    @result_buffer = nil
end

Instance Attribute Details

#cmdlineObject (readonly)

The command line of the subprocess



17
18
19
# File 'lib/autorespawn/slave.rb', line 17

def cmdline
  @cmdline
end

#nameObject (readonly)

The slave’s name

It is an arbitrary object useful for reporting/tracking

Returns:

  • (Object)


13
14
15
# File 'lib/autorespawn/slave.rb', line 13

def name
  @name
end

#pidnil, 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.

Returns:

  • (nil, Integer)

    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_idObject (readonly)

The currently known program ID



15
16
17
# File 'lib/autorespawn/slave.rb', line 15

def program_id
  @program_id
end

#result_bufferString (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.

Returns:

  • (String)

    the result data as received



40
41
42
# File 'lib/autorespawn/slave.rb', line 40

def result_buffer
  @result_buffer
end

#result_rIO (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.

Returns:

  • (IO)

    the result I/O



36
37
38
# File 'lib/autorespawn/slave.rb', line 36

def result_r
  @result_r
end

#spawn_envObject (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_optionsObject (readonly)

Options that should be passed to Kernel.spawn



21
22
23
# File 'lib/autorespawn/slave.rb', line 21

def spawn_options
  @spawn_options
end

#statusProcess::Status (readonly)

Returns the exit status of the last run. Is nil while the process is running.

Returns:

  • (Process::Status)

    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

#subcommandsArray<String> (readonly)

Returns a list of commands that this slave requests.

Returns:

  • (Array<String>)

    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

Parameters:

  • the (Process::Status)

    exit status

Returns:

  • (Array<Pathname>)

    a set of files that either changed or got added since the call to #spawn. If not empty, the slave calls #needed! by itself to force a re-execution



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

Returns:

  • (Boolean)


157
158
159
# File 'lib/autorespawn/slave.rb', line 157

def finished?
    pid && status
end

#inspectObject



60
61
62
# File 'lib/autorespawn/slave.rb', line 60

def inspect
    "#<Autorespawn::Slave #{object_id.to_s(16)} #{cmdline.join(" ")}>"
end

#joinObject

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

Parameters:

  • join (Boolean) (defaults to: true)

    whether the method should wait for the child to end

See Also:



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

Returns:

  • (Boolean)


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_autoObject



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_resultObject

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

Returns:

  • (Boolean)


152
153
154
# File 'lib/autorespawn/slave.rb', line 152

def running?
    pid && !status
end

#spawnInteger

Start the slave

Returns:

  • (Integer)

    the slave’s PID



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, **spawn_options)
    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

Returns:

  • (Boolean)


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_sObject



69
# File 'lib/autorespawn/slave.rb', line 69

def to_s; inspect end