lib/mcollective/shell.rb in mcollective-client-2.2.4 vs lib/mcollective/shell.rb in mcollective-client-2.4.0
- old
+ new
@@ -15,22 +15,26 @@
# stdin - a string that will be sent to stdin of the program
# stdout - a variable that will receive stdout, must support <<
# stderr - a variable that will receive stdin, must support <<
# environment - the shell environment, defaults to include LC_ALL=C
# set to nil to clear the environment even of LC_ALL
+ # timeout - a timeout in seconds after which the subprocess is killed,
+ # the special value :on_thread_exit kills the subprocess
+ # when the invoking thread (typically the agent) has ended
#
class Shell
- attr_reader :environment, :command, :status, :stdout, :stderr, :stdin, :cwd
+ attr_reader :environment, :command, :status, :stdout, :stderr, :stdin, :cwd, :timeout
def initialize(command, options={})
@environment = {"LC_ALL" => "C"}
@command = command
@status = nil
@stdout = ""
@stderr = ""
@stdin = nil
@cwd = Dir.tmpdir
+ @timeout = nil
options.each do |opt, val|
case opt.to_s
when "stdout"
raise "stdout should support <<" unless val.respond_to?("<<")
@@ -52,10 +56,13 @@
if val.nil?
@environment = {}
else
@environment.merge!(val.dup)
end
+ when "timeout"
+ raise "timeout should be a positive integer or the symbol :on_thread_exit symbol" unless val.eql?(:on_thread_exit) || ( val.is_a?(Fixnum) && val>0 )
+ @timeout = val
end
end
end
# Actually does the systemu call passing in the correct environment, stdout and stderr
@@ -65,23 +72,53 @@
"stderr" => @stderr,
"cwd" => @cwd}
opts["stdin"] = @stdin if @stdin
- # Running waitpid on the cid here will start a thread
- # with the waitpid in it, this way even if the thread
- # that started this process gets killed due to agent
- # timeout or such there will still be a waitpid waiting
- # for the child to exit and not leave zombies.
+
+ thread = Thread.current
+ # Start a double fork and exec with systemu which implies a guard thread.
+ # If a valid timeout is configured the guard thread will terminate the
+ # executing process and reap the pid.
+ # If no timeout is specified the process will run to completion with the
+ # guard thread reaping the pid on completion.
@status = systemu(@command, opts) do |cid|
begin
- sleep 1
- Process::waitpid(cid)
+ if timeout.is_a?(Fixnum)
+ # wait for the specified timeout
+ sleep timeout
+ else
+ # sleep while the agent thread is still alive
+ while(thread.alive?)
+ sleep 0.1
+ end
+ end
+
+ # if the process is still running
+ if (Process.kill(0, cid))
+ # and a timeout was specified
+ if timeout
+ if Util.windows?
+ Process.kill('KILL', cid)
+ else
+ # Kill the process
+ Process.kill('TERM', cid)
+ sleep 2
+ Process.kill('KILL', cid) if (Process.kill(0, cid))
+ end
+ end
+ # only wait if the parent thread is dead
+ Process.waitpid(cid) unless thread.alive?
+ end
rescue SystemExit
+ rescue Errno::ESRCH
rescue Errno::ECHILD
+ Log.warn("Could not reap process '#{cid}'.")
rescue Exception => e
Log.info("Unexpected exception received while waiting for child process: #{e.class}: #{e}")
end
end
+ @status.thread.kill
+ @status
end
end
end