[Groonga-commit] ranguba/chupa-text at 3e58262 [master] Add support external command timeout

Back to archive index
Kouhei Sutou null+****@clear*****
Sun Mar 3 07:26:57 JST 2019


Kouhei Sutou	2019-03-03 07:26:57 +0900 (Sun, 03 Mar 2019)

  Revision: 3e58262819f4d66b8bf5c6c8963941f23eaa53fd
  https://github.com/ranguba/chupa-text/commit/3e58262819f4d66b8bf5c6c8963941f23eaa53fd

  Message:
    Add support external command timeout

  Modified files:
    lib/chupa-text/external-command.rb

  Modified: lib/chupa-text/external-command.rb (+129 -82)
===================================================================
--- lib/chupa-text/external-command.rb    2019-03-03 06:05:08 +0900 (88ffd91)
+++ lib/chupa-text/external-command.rb    2019-03-03 07:26:57 +0900 (05fc10f)
@@ -1,4 +1,4 @@
-# Copyright (C) 2014  Kouhei Sutou <kou****@clear*****>
+# Copyright (C) 2014-2019  Kouhei Sutou <kou****@clear*****>
 # Copyright (C) 2010  Yuto HAYAMIZU <y.hay****@gmail*****>
 #
 # This library is free software; you can redistribute it and/or
@@ -20,6 +20,8 @@ require "pathname"
 
 module ChupaText
   class ExternalCommand
+    include Loggable
+
     attr_reader :path
     def initialize(path)
       @path = Pathname.new(path)
@@ -31,12 +33,11 @@ module ChupaText
       else
         options = {}
       end
-      spawn_options = options[:spawn_options] || {}
       pid = spawn(options[:env] || {},
                   @path.to_s,
                   *arguments,
-                  default_spawn_options.merge(spawn_options))
-      pid, status = Process.waitpid2(pid)
+                  spawn_options(options[:spawn_options]))
+      status = wait_process(pid, options[:timeout])
       status.success?
     end
 
@@ -51,101 +52,147 @@ module ChupaText
     end
 
     private
-    def default_spawn_options
-      SpawnLimitOptions.new.options
+    def spawn_options(user_options)
+      options = (user_options || {}).dup
+      apply_default_spawn_limit(options, :cpu, :int)
+      apply_default_spawn_limit(options, :as, :size)
+      options
     end
 
-    class SpawnLimitOptions
-      include Loggable
-
-      attr_reader :options
-      def initialize
-        @options = {}
-        set_default_options
+    def apply_default_spawn_limit(options, key, type)
+      # TODO: Workaround for Ruby 2.3.3p222
+      case key
+      when :cpu
+        option_key = :rlimit_cpu
+      when :as
+        option_key = :rlimit_as
+      else
+        option_key = :"rlimit_#{key}"
       end
+      return if options[option_key]
 
-      private
-      def set_default_options
-        set_option(:cpu, :int)
-        set_option(:as, :size)
+      tag = "[limit][#{key}]"
+      value =
+        ENV["CHUPA_TEXT_EXTERNAL_COMMAND_LIMIT_#{key.to_s.upcase}"] ||
+        # For backward compatibility
+        ENV["CHUPA_EXTERNAL_COMMAND_LIMIT_#{key.to_s.upcase}"]
+      value = send("parse_#{type}", tag, value)
+      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}>(#{limit_info})")
 
-      def set_option(key, type)
-        value =
-          ENV["CHUPA_TEXT_EXTERNAL_COMMAND_LIMIT_#{key.to_s.upcase}"] ||
-          # For backward compatibility
-          ENV["CHUPA_EXTERNAL_COMMAND_LIMIT_#{key.to_s.upcase}"]
-        return if value.nil?
-        return if value.empty?
-        value = send("parse_#{type}", key, value)
-        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(key, value, hard_limit)
-          return nil
-        end
-        limit_info = "soft-limit:#{soft_limit}, hard-limit:#{hard_limit}"
-        info("#{log_tag}[#{key}][set] <#{value}>(#{limit_info})")
-
-        # TODO: Workaround for Ruby 2.3.3p222
-        case key
-        when :cpu
-          @options[:rlimit_cpu] = value
-        when :as
-          @options[:rlimit_as] = value
-        else
-          @options[:"rlimit_#{key}"] = value
-        end
+      options[option_key] = value
+    end
+
+    def log_hard_limit_over_value(tag, value, hard_limit)
+      warn("#{log_tag}#{tag}[large] " +
+           "<#{value}>(hard-limit:#{hard_limit})")
+    end
+
+    def parse_int(tag, value)
+      return nil if value.nil?
+      return nil if value.empty?
+      begin
+        Integer(value)
+      rescue ArgumentError
+        log_invalid_value(tag, value, type, "int")
+        nil
       end
+    end
 
-      def parse_int(key, value)
-        begin
-          Integer(value)
-        rescue ArgumentError
-          log_invalid_value(key, value, type, "int")
-          nil
-        end
+    def parse_size(tag, value)
+      return nil if value.nil?
+      return nil if value.empty?
+      scale = 1
+      case value
+      when /GB?\z/i
+        scale = 1024 ** 3
+        number = $PREMATCH
+      when /MB?\z/i
+        scale = 1024 ** 2
+        number = $PREMATCH
+      when /KB?\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
 
-      def parse_size(key, value)
-        return nil if value.nil?
-        scale = 1
-        case value
-        when /GB?\z/i
-          scale = 1024 ** 3
-          number = $PREMATCH
-        when /MB?\z/i
-          scale = 1024 ** 2
-          number = $PREMATCH
-        when /KB?\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(key, value, "size")
-          return nil
-        end
-        (number * scale).to_i
+    def parse_time(tag, value)
+      return nil if value.nil?
+      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
+
+    def log_invalid_value(tag, value, type)
+      warn("#{log_tag}#{tag}[invalid] <#{value}>(#{type})")
+    end
 
-      def log_hard_limit_over_value(key, value, hard_limit)
-        warn("#{log_tag}[#{key}][large] <#{value}>(hard-limit:#{hard_limit})")
+    def wait_process(pid, timeout)
+      if timeout.nil?
+        timeout_env = ENV["CHUPA_TEXT_EXTERNAL_COMMAND_TIMEOUT"]
+        timeout = parse_time("[timeout]", timeout_env) if timeout_env
       end
 
-      def log_invalid_value(key, value, type)
-        warn("#{log_tag}[#{key}][invalid] <#{value}>(#{type})")
+      if timeout
+        status = wait_process_timeout(pid, timeout)
+        return status if status
+        Process.kill(:TERM, pid)
+        status = wait_process_timeout(pid, 5)
+        return status if status
+        Process.kill(:KILL, pid)
       end
+      _, status = Process.waitpid2(pid)
+      status
+    end
 
-      def log_tag
-        "[external-command][limit]"
+    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
+
+    def log_tag
+      "[external-command]"
     end
   end
 end
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.osdn.me/mailman/archives/groonga-commit/attachments/20190303/d9389f8c/attachment-0001.html>


More information about the Groonga-commit mailing list
Back to archive index