Class: Frontkick::Command

Inherits:
Object
  • Object
show all
Defined in:
lib/frontkick/command.rb

Class Method Summary collapse

Class Method Details

.build_command(env, cmd_array) ⇒ Object

private



137
138
139
140
141
142
# File 'lib/frontkick/command.rb', line 137

def self.build_command(env, cmd_array)
  command = env.map {|k,v| "#{k}=#{v} " }.join('')
  command << cmd_array.first
  command << " #{cmd_array[1..-1].shelljoin}" if cmd_array.size > 1
  command
end

.exec(*env_cmd, **opts, &block) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/frontkick/command.rb', line 7

def self.exec(*env_cmd, **opts, &block)
  env, cmd = env_cmd.size >= 2 ? env_cmd : [{}, env_cmd.first]
  # With this conversion,
  #
  #   popen3(env, *cmd_array, opts)
  #
  # works for both interface of:
  #
  #   popen3(env, command, opts)
  #   popen3(env, program, *args, opts)
  cmd_array = cmd.is_a?(Array) ? cmd : [cmd]

  opts[:timeout_kill] = true unless opts.has_key?(:timeout_kill) # default: true
  opts[:timeout_kill_signal] = 'SIGINT' unless opts.has_key?(:timeout_kill_signal) # default: 'SIGINT'

  exit_code, duration = nil
  stdin, stdout, stderr, wait_thr, pid = nil

  if opts[:out]
    if opts[:out].is_a?(String)
      out = File.open(opts[:out], 'w')
      out.sync = true
    else
      out = opts[:out] # IO
    end
  else
    out = StringIO.new
  end

  if opts[:err]
    if opts[:err].is_a?(String)
      err = File.open(opts[:err], 'w')
      err.sync = true
    else
      err = opts[:err] # IO
    end
  else
    err = StringIO.new
  end

  if opts[:dry_run]
    command = build_command(env, cmd_array)
    return Result.new(:stdout => command, :stderr => '', :exit_code => 0, :duration => 0)
  end

  popen3_opts = self.popen3_opts(opts)

  lock_fd = file_lock(opts[:exclusive], opts[:exclusive_blocking]) if opts[:exclusive]
  begin
    ::Timeout.timeout(opts[:timeout], Frontkick::TimeoutLocal) do # nil is for no timeout
      duration = Benchmark.realtime do
        if opts[:popen2e]
          stdin, stdout, wait_thr = Open3.popen2e(env, *cmd_array, popen3_opts)
          out_thr = Thread.new {
            begin
              while true
                out.write stdout.readpartial(4096)
              end
            rescue EOFError
            end
          }
          stdin.close
          pid = wait_thr.pid

          yield(wait_thr) if block_given?

          out_thr.join
          exit_code = wait_thr.value.exitstatus
          process_wait(pid)
        else
          stdin, stdout, stderr, wait_thr = Open3.popen3(env, *cmd_array, popen3_opts)
          out_thr = Thread.new {
            begin
              while true
                out.write stdout.readpartial(4096)
              end
            rescue EOFError
            end
          }
          err_thr = Thread.new {
            begin
              while true
                err.write stderr.readpartial(4096)
              end
            rescue EOFError
            end
          }
          stdin.close
          pid = wait_thr.pid

          yield(wait_thr) if block_given?

          out_thr.join
          err_thr.join
          exit_code = wait_thr.value.exitstatus
          process_wait(pid)
        end
      end
    end
  rescue Frontkick::TimeoutLocal => e
    if opts[:timeout_kill]
      Process.kill(opts[:timeout_kill_signal], pid)
      exit_code = wait_thr.value.exitstatus
      process_wait(pid)
    end
    command = build_command(env, cmd_array)
    raise Frontkick::Timeout.new(pid, command, opts[:timeout_kill])
  ensure
    stdin.close if stdin and !stdin.closed?
    stdout.close if stdout and !stdout.closed?
    stderr.close if stderr and !stderr.closed?
    wait_thr.kill if wait_thr and !wait_thr.stop?
    lock_fd.flock(File::LOCK_UN) if lock_fd
    if opts[:out] and opts[:out].is_a?(String)
      out.close rescue nil
    end
    if opts[:err] and opts[:err].is_a?(String)
      err.close rescue nil
    end
  end

  Result.new(
    :stdout => opts[:out] ? opts[:out] : out.string,
    :stderr => opts[:err] ? opts[:err] : err.string,
    :exit_code => exit_code, :duration => duration
  )
end

.file_lock(lock_file, blocking = nil) ⇒ Object

Use file lock to perfome exclusive operation

Parameters:

  • lock_file

    file path used to lock

  • blocking (defaults to: nil)

    blocking or non-blocking. default is nil (false)

Returns:

  • file descriptor

Raises:

  • Fontkick::Locked if locked



171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/frontkick/command.rb', line 171

def self.file_lock(lock_file, blocking = nil)
  lock_fd = File.open(lock_file, File::RDWR|File::CREAT, 0644)
  if blocking
    lock_fd.flock(File::LOCK_EX)
  else
    success = lock_fd.flock(File::LOCK_EX|File::LOCK_NB)
    unless success
      lock_fd.flock(File::LOCK_UN)
      raise Frontkick::Locked
    end
  end
  lock_fd
end

.popen3_opts(opts) ⇒ Object



144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/frontkick/command.rb', line 144

def self.popen3_opts(opts)
  opts.dup.tap {|o|
    o.delete(:timeout_kill)
    o.delete(:timeout_kill_signal)
    o.delete(:exclusive)
    o.delete(:exclusive_blocking)
    o.delete(:timeout)
    o.delete(:out)
    o.delete(:err)
    o.delete(:popen2e)
  }
end

.process_wait(pid) ⇒ Object



157
158
159
160
161
162
163
# File 'lib/frontkick/command.rb', line 157

def self.process_wait(pid)
  begin
    pid, status = Process.waitpid2(pid) # wait child processes finish
  rescue Errno::ECHILD => e
    # no child process
  end
end