X-Git-Url: https://review.fuel-infra.org/gitweb?a=blobdiff_plain;f=lib%2Fmcollective%2Fshell.rb;h=60f44179179c07514dc10495c4069734682c1e22;hb=7c9314f502cde8daad23b61d10b24a542e04154a;hp=0f113d51976ba97c71ca7dd0ce3ba8f01d336725;hpb=d1f1649ba43c5cbc43c4beb2380096ba051d646a;p=packages%2Fprecise%2Fmcollective.git diff --git a/lib/mcollective/shell.rb b/lib/mcollective/shell.rb index 0f113d5..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,25 +74,51 @@ module MCollective opts["stdin"] = @stdin if @stdin - # Check if the parent thread is alive. If it should die, - # and the process spawned by systemu is still alive, - # fire off a blocking waitpid and wait for the process to - # finish so that we can avoid 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 - while(thread.alive?) - sleep 0.1 + 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 - Process.waitpid(cid) if Process.getpgid(cid) + # 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