Class: LCR::Runner

Inherits:
Object
  • Object
show all
Defined in:
lib/long-command-runner/runner.rb

Overview

This class aims to manage an external process through a parallele excecution thread. This means it will not stop the main thread of your program.

Your process can output a progress indicator of the matching the format of PERCENT_INDICATOR constant.

It is possible to re-launch the same command, the out/err buffers won’t be emptied before running unless asked.

Constant Summary collapse

PERCENT_INDICATOR =

The constant used to get the percetange of completion of the command.

/(.*\s|^)((\d+)([,.]\d*)?)%.*/.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(command, opts = {}) {|nl_stdout, nl_stderr| ... } ⇒ Runner

Initializer takes the command as a plain string. You can optionaly pass a block that will be called when the command generate outputs:

Parameters:

  • command (String)

    The command to run (can be a one-line shell script)

  • opts (Hash) (defaults to: {})

    a customizable set of options

Options Hash (opts):

  • :do_progress_on (Boolean)

    Set to ‘’stderr’‘, if you want to use stderr to get the progression percentage instead of the default: `’stdout’‘. Any other value will deactivate the feature.

Yields:

  • (nl_stdout, nl_stderr)

    call when at least a line comes on stdout or stderr.

Yield Parameters:

  • nl_stdout (String | nil)

    The new line without the return line character. This may be nil if only a new line has been found on stderr.

  • nl_stderr (String | nil)

    The new line without the return line character. This may be nil if only a new line has been found on stdout.

Yield Returns:

  • (void)

    It is ignored.



39
40
41
42
43
44
45
46
47
# File 'lib/long-command-runner/runner.rb', line 39

def initialize(command, opts = {}, &on_input)
  @command = command
  @container = nil
  @launch_lock = Mutex.new
  @on_input = on_input
  @progress = 0.0
  @tms = nil
  @do_progress_on = opts[:do_progress_on] || 'stdout'
end

Instance Attribute Details

#progressObject (readonly)

Float

Get the mesured progress in percentage.

This is just the result of parsing stdout lines with PERCENT_INDICATOR. So this percentage is comming from the thread not this library.



22
23
24
# File 'lib/long-command-runner/runner.rb', line 22

def progress
  @progress
end

#tmsObject (readonly)

Benchmark::Tms

The time mesured of execution in the runner thread.



24
25
26
# File 'lib/long-command-runner/runner.rb', line 24

def tms
  @tms
end

Class Method Details

.dead?(pid) ⇒ Boolean

Returns:

  • (Boolean)


161
162
163
164
165
166
# File 'lib/long-command-runner/runner.rb', line 161

def self.dead?(pid)
  Process.kill(0, pid)
  false
rescue Errno::ESRCH
  true
end

.kill_children(signal, pid, children) ⇒ Object



168
169
170
171
172
173
174
175
176
# File 'lib/long-command-runner/runner.rb', line 168

def self.kill_children(signal, pid, children)
  n = Process.kill(signal, pid)
  sleep 0.1
  children.keys.reject { |c_pid| dead? c_pid }.each do |c_pid|
    # the not dead
    n += kill_children(signal, c_pid, children[c_pid])
  end
  n
end

Instance Method Details

#kill(signal) ⇒ Integer | nil

Send a signal to the running process.

Returns:

  • (Integer | nil)

    The number of signaled process (= 1) or nil if the Process is no more running.



129
130
131
132
133
134
135
136
# File 'lib/long-command-runner/runner.rb', line 129

def kill(signal)
  return nil if @container.nil?

  children = @container.children
  Runner.kill_children(signal, @container.pid, children)
rescue Errno::ESRCH
  nil
end

#launchObject

Actually launch the process.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/long-command-runner/runner.rb', line 62

def launch
  @bench_thread = Thread.new do
    @tms = Benchmark.measure do
      @container = Container.new(@command)
      @container.wait
    end
  end
  Thread.pass while @container.nil?
  @line_reader = LineReader.new([@container.stdout, @container.stderr]) do |*new_lines|
    on_newline(*new_lines)
  end
  @reader_thr = Thread.new { @line_reader.read }
  Thread.pass
end

#launched?Boolean

Tells if the command has been launched at least once.

Returns:

  • (Boolean)


57
58
59
# File 'lib/long-command-runner/runner.rb', line 57

def launched?
  !@container.nil?
end

#output(separator = "\n") ⇒ String

Get the output lines as separated lines

Parameters:

  • separator (String) (defaults to: "\n")

    the separator to put between lines. by default it will be ‘“n”`

Returns:

  • (String)


83
84
85
# File 'lib/long-command-runner/runner.rb', line 83

def output(separator = "\n")
  @line_reader[0].join(separator)
end

#output_error(separator = "\n") ⇒ String

Get the error output lines as separated lines

Parameters:

  • separator (String) (defaults to: "\n")

    the separator to put between lines. by default it will be ‘“n”`

Returns:

  • (String)


93
94
95
# File 'lib/long-command-runner/runner.rb', line 93

def output_error(separator = "\n")
  @line_reader[1].join(separator)
end

#pidInteger | nil

Get the pid of the process launched

Returns:

  • (Integer | nil)


100
101
102
# File 'lib/long-command-runner/runner.rb', line 100

def pid
  @container.pid
end

#running?Boolean

Is the last launched command is still running.

Returns:

  • (Boolean)


50
51
52
53
54
# File 'lib/long-command-runner/runner.rb', line 50

def running?
  return false if @container.nil?

  @container.running?
end

#statusProcess:Status | nil

Get the status of the process without blocking.

Returns:

  • (Process:Status | nil)

    The exit status of the process if it is finished. if the Process isn’t finished it return nil.



119
120
121
122
123
# File 'lib/long-command-runner/runner.rb', line 119

def status
  return nil if @container.nil?

  @container.status
end

#time_realObject

Old get the time really spend (‘real’ part of ‘time` command) DEPRECATED: use tms.real access now.



140
141
142
143
144
145
# File 'lib/long-command-runner/runner.rb', line 140

def time_real
  warn '[DEPRECATION] use :tms instead of :time_real'
  return nil if @tms.nil?

  @tms.real
end

#time_sysObject

Get the total time spent in system space (sum of tms.cstime + tms.stime)



155
156
157
158
159
# File 'lib/long-command-runner/runner.rb', line 155

def time_sys
  return nil if @tms.nil?

  @tms.cstime + @tms.stime
end

#time_userObject

Get the total time spent in user space (sum of tms.cutime + tms.utime)



148
149
150
151
152
# File 'lib/long-command-runner/runner.rb', line 148

def time_user
  return nil if @tms.nil?

  @tms.cutime + @tms.utime
end

#waitProcess::Status

Wait and return the process exit status. This method is blocking until the process if finished.

Returns:

  • (Process::Status)


108
109
110
111
112
113
# File 'lib/long-command-runner/runner.rb', line 108

def wait
  @bench_thread.join
  @reader_thr.join
  sleep 0.01 until @line_reader.streams.all?(&:eof?)
  @container.status
end