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
    @needs_spawn = 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



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/autorespawn/slave.rb', line 164

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?
        @needs_spawn = true
    end
    result_r.close
    modified
end

#finished?Boolean

Whether the slave has already ran, and is finished

Returns:

  • (Boolean)


125
126
127
# File 'lib/autorespawn/slave.rb', line 125

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



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

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:



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

def kill(signal = 'TERM', join: true)
    Process.kill signal, pid
    if join
        self.join
    end
end

#needs_spawn?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 needs_spawn?
    @needs_spawn || !status || program_id.changed?
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



187
188
189
190
191
192
# File 'lib/autorespawn/slave.rb', line 187

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)


120
121
122
# File 'lib/autorespawn/slave.rb', line 120

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
    @needs_spawn = 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)


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

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