From f9d110fabfcd1063801fe30566a4cd543820978d Mon Sep 17 00:00:00 2001 From: Brandon Dunne Date: Tue, 7 May 2013 15:42:43 -0400 Subject: [PATCH] Fix issue related to command output filling up the IO pipe. If command being run had output of more than 64k,the pipe would fill and the process would hang waiting for room to write more data into the pipe. Reading the pipe synchrously eliminates the problem. See the following link for more detail on the problem and another solution. http://stackoverflow.com/questions/13829830/ruby-process-spawn-stdout-pipe-buffer-size-limit/13846146#13846146 --- lib/linux_admin/common.rb | 53 +++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/lib/linux_admin/common.rb b/lib/linux_admin/common.rb index 847bf19..f45464a 100644 --- a/lib/linux_admin/common.rb +++ b/lib/linux_admin/common.rb @@ -2,20 +2,57 @@ module LinuxAdmin module Common def self.run(cmd, options = {}) begin - r, w = IO.pipe - pid, status = Process.wait2(Kernel.spawn(cmd, :err => [:child, :out], :out => w)) - w.close - if options[:return_output] && status.exitstatus == 0 - r.read - elsif options[:return_exitstatus] || status.exitstatus == 0 - status.exitstatus + out = launch(cmd) + if options[:return_output] && exitstatus == 0 + out + elsif options[:return_exitstatus] || exitstatus == 0 + exitstatus else - raise "Error: Exit Code #{status.exitstatus}" + raise "Error: Exit Code #{exitstatus}" end rescue return nil if options[:return_exitstatus] raise + ensure + self.exitstatus = nil end end + + private + + # IO pipes have a maximum size of 64k before blocking, + # so we need to read and write synchronously. + # http://stackoverflow.com/questions/13829830/ruby-process-spawn-stdout-pipe-buffer-size-limit/13846146#13846146 + THREAD_SYNC_KEY = "LinuxAdmin-exitstatus" + + def self.launch(cmd) + pipe_r, pipe_w = IO.pipe + pid = Kernel.spawn(cmd, :err => [:child, :out], :out => pipe_w) + wait_for_process(pid, pipe_w) + wait_for_output(pipe_r) + end + + def self.wait_for_process(pid, pipe_w) + self.exitstatus = :not_done + Thread.new(Thread.current) do |parent_thread| + _, status = Process.wait2(pid) + pipe_w.close + parent_thread[THREAD_SYNC_KEY] = status.exitstatus + end + end + + def self.wait_for_output(pipe_r) + out = pipe_r.read + sleep(0.1) while exitstatus == :not_done + return out + end + + def self.exitstatus + Thread.current[THREAD_SYNC_KEY] + end + + def self.exitstatus=(value) + Thread.current[THREAD_SYNC_KEY] = value + end end end \ No newline at end of file