class ChupaText::ExternalCommand

Attributes

path[R]

Public Class Methods

default_limit_as() click to toggle source
# File lib/chupa-text/external-command.rb, line 45
def default_limit_as
  @default_limit_as || limit_env("AS")
end
default_limit_as=(as) click to toggle source
# File lib/chupa-text/external-command.rb, line 49
def default_limit_as=(as)
  @default_limit_as = as
end
default_limit_cpu() click to toggle source
# File lib/chupa-text/external-command.rb, line 37
def default_limit_cpu
  @default_limit_cpu || limit_env("CPU")
end
default_limit_cpu=(cpu) click to toggle source
# File lib/chupa-text/external-command.rb, line 41
def default_limit_cpu=(cpu)
  @default_limit_cpu = cpu
end
default_timeout() click to toggle source
# File lib/chupa-text/external-command.rb, line 29
def default_timeout
  @default_timeout || ENV["CHUPA_TEXT_EXTERNAL_COMMAND_TIMEOUT"]
end
default_timeout=(timeout) click to toggle source
# File lib/chupa-text/external-command.rb, line 33
def default_timeout=(timeout)
  @default_timeout = timeout
end
new(path) click to toggle source
# File lib/chupa-text/external-command.rb, line 62
def initialize(path)
  @path = Pathname.new(path)
end

Private Class Methods

limit_env(name) click to toggle source
# File lib/chupa-text/external-command.rb, line 54
def limit_env(name)
  ENV["CHUPA_TEXT_EXTERNAL_COMMAND_LIMIT_#{name}"] ||
    # For backward compatibility
    ENV["CHUPA_EXTERNAL_COMMAND_LIMIT_#{name}"]
end

Public Instance Methods

exist?() click to toggle source
# File lib/chupa-text/external-command.rb, line 97
def exist?
  if @path.absolute?
    @path.file? and @path.executable?
  else
    (ENV['PATH'] || "").split(File::PATH_SEPARATOR).any? do |path|
      (Pathname.new(path) + @path).expand_path.exist?
    end
  end
end
run(*arguments) click to toggle source
# File lib/chupa-text/external-command.rb, line 66
def run(*arguments)
  if arguments.last.is_a?(Hash)
    options = arguments.pop
  else
    options = {}
  end
  data = options[:data]
  pid = spawn(options[:env] || {},
              @path.to_s,
              *arguments,
              spawn_options(options[:spawn_options], data))
  if data
    soft_timeout = data.timeout
  else
    soft_timeout = nil
  end
  status = nil
  begin
    status = wait_process(pid, options[:timeout], soft_timeout)
  ensure
    unless status
      begin
        Process.kill(:KILL, pid)
        Process.waitpid(pid)
      rescue SystemCallError
      end
    end
  end
  status.success?
end

Private Instance Methods

apply_default_spawn_limit(options, soft_value, key, type) click to toggle source
# File lib/chupa-text/external-command.rb, line 122
def apply_default_spawn_limit(options, soft_value, key, type)
  # TODO: Workaround for Ruby 2.3.3p222
  case key
  when :cpu
    option_key = :rlimit_cpu
    unit = "s"
  when :as
    option_key = :rlimit_as
    unit = ""
  else
    option_key = :"rlimit_#{key}"
    unit = ""
  end
  return if options[option_key]

  tag = "[limit][#{key}]"
  value = self.class.__send__("default_limit_#{key}")
  value = __send__("parse_#{type}", tag, value)
  soft_value = __send__("parse_#{type}", tag, soft_value)
  if value
    value = soft_value if soft_value and soft_value < value
  else
    value = soft_value
  end
  return if value.nil?
  rlimit_number = Process.const_get("RLIMIT_#{key.to_s.upcase}")
  soft_limit, hard_limit = Process.getrlimit(rlimit_number)
  if hard_limit < value
    log_hard_limit_over_value(tag, value, hard_limit)
    return nil
  end
  limit_info = "soft-limit:#{soft_limit}, hard-limit:#{hard_limit}"
  info("#{log_tag}#{tag}[set] <#{value}#{unit}>(#{limit_info})")

  options[option_key] = value
end
log_hard_limit_over_value(tag, value, hard_limit) click to toggle source
# File lib/chupa-text/external-command.rb, line 159
def log_hard_limit_over_value(tag, value, hard_limit)
  warn("#{log_tag}#{tag}[large] " +
       "<#{value}>(hard-limit:#{hard_limit})")
end
log_invalid_value(tag, value, type) click to toggle source
Calls superclass method ChupaText::Loggable#log_invalid_value
# File lib/chupa-text/external-command.rb, line 257
def log_invalid_value(tag, value, type)
  super("#{log_tag}#{tag}", value, type)
end
log_tag() click to toggle source
# File lib/chupa-text/external-command.rb, line 296
def log_tag
  "[external-command]"
end
parse_int(tag, value) click to toggle source
# File lib/chupa-text/external-command.rb, line 164
def parse_int(tag, value)
  case value
  when nil
    nil
  when Integer
    value
  when Float
    value.round
  else
    return nil if value.empty?
    begin
      Integer(value)
    rescue ArgumentError
      log_invalid_value(tag, value, type, "int")
      nil
    end
  end
end
parse_size(tag, value) click to toggle source
# File lib/chupa-text/external-command.rb, line 183
def parse_size(tag, value)
  case value
  when nil
    nil
  when Numeric
    value
  else
    return nil if value.empty?
    scale = 1
    case value
    when /GB?\z/i
      scale = 1000 ** 3
      number = $PREMATCH
    when /GiB?\z/i
      scale = 1024 ** 3
      number = $PREMATCH
    when /MB?\z/i
      scale = 1000 ** 2
      number = $PREMATCH
    when /MiB?\z/i
      scale = 1024 ** 2
      number = $PREMATCH
    when /[kK]B?\z/i
      scale = 1000 ** 1
      number = $PREMATCH
    when /KiB?\z/i
      scale = 1024 ** 1
      number = $PREMATCH
    when /B?\z/i
      number = $PREMATCH
    else
      number = value
    end
    begin
      number = Float(number)
    rescue ArgumentError
      log_invalid_value(tag, value, "size")
      return nil
    end
    (number * scale).to_i
  end
end
parse_time(tag, value) click to toggle source
# File lib/chupa-text/external-command.rb, line 226
def parse_time(tag, value)
  case value
  when nil
    nil
  when Numeric
    value
  else
    return nil if value.empty?
    scale = 1
    case value
    when /h\z/i
      scale = 60 * 60
      number = $PREMATCH
    when /m\z/i
      scale = 60
      number = $PREMATCH
    when /s\z/i
      number = $PREMATCH
    else
      number = value
    end
    begin
      number = Float(number)
    rescue ArgumentError
      log_invalid_value(tag, value, "time")
      return nil
    end
    (number * scale).to_f
  end
end
spawn_options(user_options, data) click to toggle source
# File lib/chupa-text/external-command.rb, line 108
def spawn_options(user_options, data)
  options = (user_options || {}).dup
  if data
    soft_limit_cpu = data.limit_cpu
    soft_limit_as = data.limit_as
  else
    soft_limit_cpu = nil
    soft_limit_as = nil
  end
  apply_default_spawn_limit(options, soft_limit_cpu, :cpu, :time)
  apply_default_spawn_limit(options, soft_limit_as, :as, :size)
  options
end
wait_process(pid, timeout, soft_timeout) click to toggle source
# File lib/chupa-text/external-command.rb, line 261
def wait_process(pid, timeout, soft_timeout)
  tag = "[timeout]"
  timeout = TimeoutValue.new(tag, timeout || self.class.default_timeout).raw
  soft_timeout = TimeoutValue.new(tag, soft_timeout).raw
  if timeout
    timeout = soft_timeout if soft_timeout and soft_timeout < timeout
  else
    timeout = soft_timeout
  end
  if timeout
    info("#{log_tag}#{tag}[use] " +
         "<#{TimeoutValue.new(tag, timeout)}>: <#{pid}>")
    status = wait_process_timeout(pid, timeout)
    return status if status
    info("#{log_tag}#{tag}[terminate] <#{pid}>")
    Process.kill(:TERM, pid)
    status = wait_process_timeout(pid, 5)
    return status if status
    info("#{log_tag}#{tag}[kill] <#{pid}>")
    Process.kill(:KILL, pid)
  end
  _, status = Process.waitpid2(pid)
  status
end
wait_process_timeout(pid, timeout) click to toggle source
# File lib/chupa-text/external-command.rb, line 286
def wait_process_timeout(pid, timeout)
  limit = Time.now + timeout
  while Time.now < limit
    _, status = Process.waitpid2(pid, Process::WNOHANG)
    return status if status
    sleep(1)
  end
  nil
end