X-Git-Url: https://review.fuel-infra.org/gitweb?a=blobdiff_plain;f=lib%2Fmcollective%2Fshell.rb;h=60f44179179c07514dc10495c4069734682c1e22;hb=7c9314f502cde8daad23b61d10b24a542e04154a;hp=257fc4b5a951f8c0a6fd7342292bc1cc21183ffc;hpb=b87d2f4e68281062df1913440ca5753ae63314a9;p=packages%2Fprecise%2Fmcollective.git diff --git a/lib/mcollective/shell.rb b/lib/mcollective/shell.rb index 257fc4b..60f4417 100644 --- a/lib/mcollective/shell.rb +++ b/lib/mcollective/shell.rb @@ -17,9 +17,12 @@ module MCollective # 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"} @@ -29,6 +32,7 @@ module MCollective @stderr = "" @stdin = nil @cwd = Dir.tmpdir + @timeout = nil options.each do |opt, val| case opt.to_s @@ -54,6 +58,9 @@ module MCollective 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 @@ -67,21 +74,51 @@ module MCollective 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