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

#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

Parameters:

  • the (Process::Status)

    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

Returns:

  • (Boolean)


141
142
143
# File 'lib/autorespawn/slave.rb', line 141

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



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

Parameters:

  • join (Boolean) (defaults to: true)

    whether the method should wait for the child to end

See Also:



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

Returns:

  • (Boolean)


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_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



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

Returns:

  • (Boolean)


136
137
138
# File 'lib/autorespawn/slave.rb', line 136

def running?
    pid && !status
end

#spawnInteger

Start the slave

Returns:

  • (Integer)

    the slave’s PID



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


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_sObject



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

def to_s; inspect end