Module: Runners

Included in:
CMake, Lcov, PotentialBuild
Defined in:
lib/runners.rb

Overview

captures functions to run commands on the system

Instance Method Summary collapse

Instance Method Details

#monitor_thread_state(timeout, thread, tick, stdout, stderr) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/runners.rb', line 66

def monitor_thread_state(timeout, thread, tick, stdout, stderr)
  pid = thread[:pid]
  start = Time.now
  out = String.new # rubocop:disable Performance/UnfreezeString:
  err = String.new # rubocop:disable Performance/UnfreezeString:
  while (Time.now - start) < timeout && thread.alive?
    out, err, this_break = read_state_singular(stdout, stderr, tick, out, err)
    break if this_break
  end

  # Give Ruby time to clean up the other thread
  sleep 1

  if thread.alive?    # We need to kill the process, because killing the thread leaves
    # the process alive but detached, annoyingly enough.
    # :nocov: I cannot figure out how to reproduce this right now

    Process.kill('TERM', pid)    # :nocov:

  end
  [out, err]
end

#read_state_singular(stdout, stderr, tick, out, err) ⇒ Object



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/runners.rb', line 89

def read_state_singular(stdout, stderr, tick, out, err)
  this_break = false  # Wait up to `tick` seconds for output/error data

  rs, = Kernel.select([stdout, stderr], nil, nil, tick)  # Try to read the data

  begin
    rs&.each do |r|
      if r == stdout
        out << stdout.read_nonblock(4096)
      elsif r == stderr
        err << stderr.read_nonblock(4096)
      end
    end
  rescue IO::WaitReadable # rubocop:disable Lint/SuppressedException
    # A read would block, so loop around for another select
  rescue EOFError    # Command has completed, not really an error...

    this_break = true
  end
  [out, err, this_break]
end

#run_scripts(this_config, commands, env = {}) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/runners.rb', line 7

def run_scripts(this_config, commands, env = {})
  all_out = String.new # rubocop:disable Performance/UnfreezeString: Too much burden to unfreeze everywhere
  all_err = String.new # rubocop:disable Performance/UnfreezeString:
  all_result = 0

  commands.each do |cmd|
    out_this_cmd, err_this_cmd, result_this_command = run_single_script(this_config, cmd, env)

    $logger.error("Error running script command: #{cmd}") unless result_this_command.exitstatus.zero?

    all_out += out_this_cmd
    all_err += err_this_cmd
    all_result += result_this_command.exitstatus
  end

  [all_out, all_err, all_result]
end

#run_single_script(this_config, cmd, env) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/runners.rb', line 25

def run_single_script(this_config, cmd, env)
  if this_config.os == 'Windows'    # :nocov: Not testing on Windows

    $logger.debug 'Unable to set timeout for process execution on windows'
    stdout, stderr, result = Open3.capture3(env, cmd)    # :nocov:

  else
    # allow up to 6 hours
    stdout, stderr, result = run_with_timeout(env, cmd, 60 * 60 * 6)
  end

  stderr.encode('UTF-8', :invalid => :replace).split("\n").each do |l|
    $logger.debug("cmd: #{cmd}: stderr: #{l}")
  end

  [stdout, stderr, result]
end

#run_with_timeout(env, command, timeout = 60 * 60 * 4, tick = 2) ⇒ Object

originally from gist.github.com/lpar/1032297 runs a specified shell command in a separate thread. If it exceeds the given timeout in seconds, kills it. Returns any output produced by the command (stdout or stderr) as a String. Uses Kernel.select to wait up to the tick length (in seconds) between checks on the command's status

If you've got a cleaner way of doing this, I'd be interested to see it. If you think you can do it with Ruby's Timeout module, think again.



52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/runners.rb', line 52

def run_with_timeout(env, command, timeout = 60 * 60 * 4, tick = 2)
  begin
    # Start task in another thread, which spawns a process
    stdin, stdout, stderr, thread = Open3.popen3(env, command)    # Start watching the original running thread and watching output

    out, err = monitor_thread_state(timeout, thread, tick, stdout, stderr)
  ensure
    stdin&.close
    stdout&.close
    stderr&.close
  end
  [out.force_encoding('UTF-8'), err.force_encoding('UTF-8'), thread.value]
end