Module: TimeoutCommand
- Defined in:
- lib/misc/timeoutcom.rb
Class Method Summary collapse
- .kill_processgroup(pgid, msgout) ⇒ Object
- .last_output_time ⇒ Object
- .parse_timespan(arg) ⇒ Object
- .process_alive?(pid) ⇒ Boolean
- .processgroup_alive?(pgid) ⇒ Boolean
- .timeout_command(command_timeout, msgout = STDERR, opts = {}) ⇒ Object
Class Method Details
.kill_processgroup(pgid, msgout) ⇒ Object
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/misc/timeoutcom.rb', line 52 def kill_processgroup(pgid, msgout) begin Process.kill('INT', -pgid) msgout.puts "timeout: INT signal sent." if msgout signals = ['INT', 'TERM', 'TERM', 'KILL'] signals.each {|sig| Process.kill(0, -pgid); sleep 0.1 Process.kill(0, -pgid); sleep 0.2 Process.kill(0, -pgid); sleep 0.3 Process.kill(0, -pgid); sleep 0.4 Process.kill(0, -pgid) 4.times { sleep 1 Process.kill(0, -pgid) } Process.kill(sig, -pgid) msgout.puts "timeout: #{sig} signal sent." if msgout } rescue Errno::ESRCH # no process i.e. success to kill end end |
.last_output_time ⇒ Object
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/misc/timeoutcom.rb', line 87 def last_output_time last_output_time = [STDOUT, STDERR].map {|f| s = f.stat if s.file? s.mtime else nil end }.compact if last_output_time.empty? nil else last_output_time.max end end |
.parse_timespan(arg) ⇒ Object
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/misc/timeoutcom.rb', line 32 def parse_timespan(arg) case arg when Integer, Float timeout = arg when Time timeout = arg - Time.now when /\A\d+(\.\d+)?(?:s|sec)?\z/ timeout = $&.to_f when /\A\d+(\.\d+)?(?:m|min)\z/ timeout = $&.to_f * 60 when /\A\d+(\.\d+)?(?:h|hour)\z/ timeout = $&.to_f * 60 * 60 when /\A\d+(\.\d+)?(?:d|day)\z/ timeout = $&.to_f * 60 * 60 * 24 else raise ArgumentError, "invalid time span: #{arg.inspect}" end timeout end |
.process_alive?(pid) ⇒ Boolean
74 75 76 77 78 79 80 81 |
# File 'lib/misc/timeoutcom.rb', line 74 def process_alive?(pid) begin Process.kill(0, pid) rescue Errno::ESRCH # no process return false end return true end |
.processgroup_alive?(pgid) ⇒ Boolean
83 84 85 |
# File 'lib/misc/timeoutcom.rb', line 83 def processgroup_alive?(pgid) process_alive?(-pgid) end |
.timeout_command(command_timeout, msgout = STDERR, opts = {}) ⇒ Object
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 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/misc/timeoutcom.rb', line 103 def timeout_command(command_timeout, msgout=STDERR, opts={}) command_timeout = parse_timespan(command_timeout) output_interval_timeout = nil if opts[:output_interval_timeout] output_interval_timeout = parse_timespan(opts[:output_interval_timeout]) end if command_timeout < 0 raise CommandTimeout, 'no time to run a command' end pid = fork { Process.setpgid($$, $$) yield } begin Process.setpgid(pid, pid) rescue Errno::EACCES # already execed. rescue Errno::ESRCH # already exited. (setpgid for a zombie fails on OpenBSD) end wait_thread = Thread.new { Process.wait2(pid)[1] } begin start_time = Time.now limit_time = start_time + command_timeout command_status = nil while true join_timeout = limit_time - Time.now if join_timeout < 0 timeout_reason = "command execution time exceeds #{command_timeout} seconds." break end if output_interval_timeout and t = last_output_time and (tmp_join_timeout = t + output_interval_timeout - Time.now) < join_timeout join_timeout = tmp_join_timeout if join_timeout < 0 timeout_reason = "output interval exceeds #{output_interval_timeout} seconds." break end end if wait_thread.join(join_timeout) command_status = wait_thread.value break end end if command_status return command_status else msgout.puts "timeout: #{timeout_reason}" if msgout begin Process.kill(0, -pid) msgout.puts "timeout: the process group #{pid} is alive." if msgout kill_processgroup(pid, msgout) rescue Errno::ESRCH # no process end raise CommandTimeout, timeout_reason end rescue Interrupt Process.kill("INT", -pid) raise rescue SignalException Process.kill($!., -pid) raise ensure if processgroup_alive?(pid) msgout.puts "some descendant process in process group #{pid} remain." if msgout kill_processgroup(pid, msgout) end end end |