Class: Mediakit::Process::Runner

Inherits:
Object
  • Object
show all
Defined in:
lib/mediakit/process/runner.rb

Defined Under Namespace

Classes: CommandNotFoundError, IOWatcher, TimeoutError, TimeoutTimer

Constant Summary collapse

DEFAULT_READ_TIMEOUT_INTERVAL =
30

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(timeout: nil, nice: 0, logger: Logger.new(STDOUT)) ⇒ Runner

Returns a new instance of Runner.



20
21
22
23
24
# File 'lib/mediakit/process/runner.rb', line 20

def initialize(timeout: nil, nice: 0, logger: Logger.new(STDOUT))
  @timeout = timeout
  @nice = nice
  @logger = logger || Mediakit::Utils::NullLogger.new
end

Instance Attribute Details

#loggerObject (readonly)

Returns the value of attribute logger.



18
19
20
# File 'lib/mediakit/process/runner.rb', line 18

def logger
  @logger
end

Instance Method Details

#build_command(bin, *args) ⇒ Object



66
67
68
69
70
71
72
73
# File 'lib/mediakit/process/runner.rb', line 66

def build_command(bin, *args)
  command = build_command_without_options(bin, *args)
  if @nice == 0
    command
  else
    "nice -n #{ShellEscape.escape(@nice.to_s)} sh -c \"#{command}\""
  end
end

#build_command_without_options(bin, *args) ⇒ Object



75
76
77
78
# File 'lib/mediakit/process/runner.rb', line 75

def build_command_without_options(bin, *args)
  escaped_args = ShellEscape.escape(*args)
  "#{bin} #{escaped_args}"
end

#force_kill_process(pid) ⇒ Object



106
107
108
109
110
# File 'lib/mediakit/process/runner.rb', line 106

def force_kill_process(pid)
  ::Process.kill('SIGKILL', pid)
rescue Errno::ESRCH => e
  logger.warn("fail SIGKILL pid=#{pid} - #{e.message}, #{e.backtrace.join("\n")}")
end

#run(command, *args) ⇒ Object #run(command, args) ⇒ Object

Overloads:

  • #run(command, *args) ⇒ Object

    Parameters:

    • command (String)
    • args (Array)

      args as array for safety shellescape

  • #run(command, args) ⇒ Object

    Parameters:

    • command (String)

      command name

    • args (Array)

      args as string



34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/mediakit/process/runner.rb', line 34

def run(bin, *args)
  command = build_command(bin, *args)
  begin
    stdin, stdout, stderr, wait_thread = Open3.popen3(command)
    stdin.close
    exit_status, output, error_output = wait(stdout, stderr, wait_thread)
  rescue Errno::ENOENT => e
    raise(CommandNotFoundError, "Can't find command - #{command}, #{e.meessage}")
  end

  [exit_status, output, error_output]
end

#run_loopObject



90
91
92
93
94
95
96
# File 'lib/mediakit/process/runner.rb', line 90

def run_loop
  @loop.run
rescue => e
  # workaround for ambiguous RuntimeError
  logger.warn(e.message)
  logger.warn(e.backtrace.join("\n"))
end

#setup_watchers(stdout, stderr) ⇒ Object



80
81
82
83
84
85
86
87
88
# File 'lib/mediakit/process/runner.rb', line 80

def setup_watchers(stdout, stderr)
  @timer = @timeout ? TimeoutTimer.new(@timeout, Thread.current) : nil
  @out_watcher = IOWatcher.new(stdout) { |data| @timer.update if @timer; logger.info(data); }
  @err_watcher = IOWatcher.new(stderr) { |data| @timer.update if @timer; logger.info(data); }
  @loop = Coolio::Loop.new
  @out_watcher.attach(@loop)
  @err_watcher.attach(@loop)
  @timer.attach(@loop) if @timer
end

#teardown_watchersObject



98
99
100
101
102
103
104
# File 'lib/mediakit/process/runner.rb', line 98

def teardown_watchers
  @loop.watchers.each { |w| w.detach  if w.attached? }
  @loop.stop if @loop.has_active_watchers?
rescue RuntimeError => e
  logger.warn(e.message)
  logger.warn(e.backtrace.join("\n"))
end

#wait(stdout, stderr, wait_thread) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/mediakit/process/runner.rb', line 47

def wait(stdout, stderr, wait_thread)
  begin
    setup_watchers(stdout, stderr)
    loop_thread = Thread.new { run_loop }
    wait_thread.join
    exit_status = (wait_thread.value.exitstatus == 0)
  rescue Timeout::Error => error
    force_kill_process(wait_thread.pid)
    raise(error)
  ensure
    teardown_watchers
    loop_thread.join if loop_thread
    @out_watcher.read
    @err_watcher.read
  end

  [exit_status, @out_watcher.data, @err_watcher.data]
end