Permalink
Browse files

Add a PID handler.

Prior to this the PID of the running process was
invisible to the caller of popen3.  An optional
handler notifies the caller as soon as the PID
is known.

Add pid_handler to example.
  • Loading branch information...
1 parent 425dac8 commit 34a1f7b8ae9a5281835feaa7bdd84311bc627afc Graham Hughes committed Aug 27, 2010
Showing with 44 additions and 9 deletions.
  1. +7 −0 README.rdoc
  2. +6 −2 lib/linux/right_popen.rb
  3. +2 −1 lib/right_popen.rb
  4. +4 −1 lib/win32/right_popen.rb
  5. +25 −5 spec/right_popen_spec.rb
View
@@ -26,6 +26,11 @@ to report issues.
@stdout_text = ""
@stderr_text = ""
@exit_status = nil
+ @pid = nil
+
+ def on_pid(pid)
+ @pid = pid
+ end
def on_read_stdout(data)
@stdout_text << data
@@ -45,6 +50,7 @@ to report issues.
RightScale.popen3(:command => command,
:target => self,
:environment => nil,
+ :pid_handler => :on_pid,
:stdout_handler => :on_read_stdout,
:stderr_handler => :on_read_stderr,
:exit_handler => :on_exit)
@@ -60,6 +66,7 @@ to report issues.
puts "@stdout_text = #{@stdout_text}"
puts "@stderr_text = #{@stderr_text}"
puts "@exit_status.exitstatus = #{@exit_status.exitstatus}"
+ puts "@pid = #{@pid}"
== INSTALLATION
View
@@ -110,6 +110,7 @@ def force_detach
# standard streams of the child process.
#
# === Parameters
+ # options[:pid_handler](Symbol):: Token for pid handler method name.
# options[:temp_dir]:: Path to temporary directory where executable files are
# created, default to /tmp if not specified
#
@@ -140,7 +141,10 @@ def self.popen3_imp(options)
end
# Launch child process
- EM.popen(exec_file, StdOutHandler, options, c, r, w)
+ connection = EM.popen(exec_file, StdOutHandler, options, c, r, w)
+ if options[:pid_handler]
+ options[:target].method(options[:pid_handler]).call(EM.get_subprocess_pid(connection.signature))
+ end
# Restore environment variables
unless envs.empty?
@@ -149,7 +153,7 @@ def self.popen3_imp(options)
end
# Do not close 'w', strange things happen otherwise
- # (command protocol socket gets closed during decommission)
+ # (command protocol socket gets closed during decommission)
$stderr.reopen saved_stderr
end
true
View
@@ -49,6 +49,7 @@ module RightScale
# options[:environment](Hash):: Hash of environment variables values keyed by name
# options[:input](String):: Input string that will get streamed into child's process stdin
# options[:target](Object):: object defining handler methods to be called, optional (no handlers can be defined if not specified)
+ # options[:pid_handler](String):: PID notification handler method name, optional
# options[:stdout_handler](String):: Stdout handler method name, optional
# options[:stderr_handler](String):: Stderr handler method name, optional
# options[:exit_handler](String):: Exit handler method name, optional
@@ -58,7 +59,7 @@ module RightScale
def self.popen3(options)
raise "EventMachine reactor must be started" unless EM.reactor_running?
raise "Missing command" unless options[:command]
- raise "Missing target" unless options[:target] || !options[:stdout_handler] && !options[:stderr_handler] && !options[:exit_handler]
+ raise "Missing target" unless options[:target] || !options[:stdout_handler] && !options[:stderr_handler] && !options[:exit_handler] && !options[:pid_handler]
RightScale.popen3_imp(options)
true
end
View
@@ -217,7 +217,10 @@ def self.popen3_imp(options)
# streams aren't used directly by the connectors except that they are closed
# on unbind.
stderr_eventable = EM.watch(stream_err, StdErrHandler, options, stream_err) { |c| c.notify_readable = true } if options[:stderr_handler]
- EM.watch(stream_out, StdOutHandler, options, stderr_eventable, stream_out, pid) { |c| c.notify_readable = true }
+ EM.watch(stream_out, StdOutHandler, options, stderr_eventable, stream_out, pid) do |c|
+ c.notify_readable = true
+ options[:target].method(options[:pid_handler]).call(pid) if options[:pid_handler]
+ end
EM.attach(stream_in, StdInHandler, options, stream_in) if options[:input]
# note that control returns to the caller, but the launched cmd continues
View
@@ -31,18 +31,21 @@ def initialize
end
attr_reader :output_text, :error_text, :status
+ attr_accessor :pid
def do_right_popen(command, env=nil, input=nil)
@timeout = EM::Timer.new(2) { puts "\n** Failed to run #{command.inspect}: Timeout"; EM.stop }
@output_text = ''
@error_text = ''
@status = nil
- RightScale.popen3(:command => command,
+ @pid = nil
+ RightScale.popen3(:command => command,
:input => input,
- :target => self,
+ :target => self,
:environment => env,
- :stdout_handler => :on_read_stdout,
- :stderr_handler => :on_read_stderr,
+ :stdout_handler => :on_read_stdout,
+ :stderr_handler => :on_read_stderr,
+ :pid_handler => :on_pid,
:exit_handler => :on_exit)
end
@@ -68,6 +71,11 @@ def on_read_stderr(data)
@error_text << data
end
+ def on_pid(pid)
+ raise "PID already set!" unless @pid.nil?
+ @pid = pid
+ end
+
def on_exit(status)
@last_iteration += 1
@timeout.cancel if @timeout
@@ -94,6 +102,7 @@ def on_exit(status)
runner.status.exitstatus.should == 0
runner.output_text.should == STANDARD_MESSAGE + "\n"
runner.error_text.should == ERROR_MESSAGE + "\n"
+ runner.pid.should > 0
end
it 'should return the right status' do
@@ -103,6 +112,7 @@ def on_exit(status)
runner.status.exitstatus.should == EXIT_STATUS
runner.output_text.should == ''
runner.error_text.should == ''
+ runner.pid.should > 0
end
it 'should preserve the integrity of stdout when stderr is unavailable' do
@@ -118,6 +128,7 @@ def on_exit(status)
end
runner.output_text.should == results
runner.error_text.should == ''
+ runner.pid.should > 0
end
it 'should preserve the integrity of stderr when stdout is unavailable' do
@@ -133,6 +144,7 @@ def on_exit(status)
end
runner.error_text.should == results
runner.output_text.should == ''
+ runner.pid.should > 0
end
it 'should preserve the integrity of stdout and stderr despite interleaving' do
@@ -153,17 +165,20 @@ def on_exit(status)
(results << "stderr #{i}\n") if 0 == i % 10
end
runner.error_text.should == results
+ runner.pid.should > 0
end
-
+
it 'should setup environment variables' do
command = "\"#{RUBY_CMD}\" \"#{File.expand_path(File.join(File.dirname(__FILE__), 'print_env.rb'))}\""
runner = RightPopenSpec::Runner.new
runner.run_right_popen(command)
runner.status.exitstatus.should == 0
runner.output_text.should_not include('_test_')
+ runner.pid = nil
runner.run_right_popen(command, :__test__ => '42')
runner.status.exitstatus.should == 0
runner.output_text.should match(/^__test__=42$/)
+ runner.pid.should > 0
end
it 'should restore environment variables' do
@@ -177,6 +192,7 @@ def on_exit(status)
runner.output_text.should match(/^__test__=42$/)
ENV.each { |k, v| old_envs[k].should == v }
old_envs.each { |k, v| ENV[k].should == v }
+ runner.pid.should > 0
end
if is_windows?
@@ -188,6 +204,7 @@ def on_exit(status)
runner.run_right_popen(command, 'PATH' => "c:/bogus\\bin")
runner.status.exitstatus.should == 0
runner.output_text.should include('PATH=c:\\bogus\\bin;')
+ runner.pid.should > 0
end
else
it 'should allow running bash command lines starting with a built-in command' do
@@ -196,6 +213,7 @@ def on_exit(status)
runner.run_right_popen(command)
runner.status.exitstatus.should == 0
runner.output_text.should == "1\n2\n3\n4\n5\n"
+ runner.pid.should > 0
end
end
@@ -207,6 +225,7 @@ def on_exit(status)
runner.status.exitstatus.should == 0
runner.output_text.should == STANDARD_MESSAGE + "\n"
runner.error_text.should == ERROR_MESSAGE + "\n"
+ runner.pid.should > 0
end
it 'should pass input to child process' do
@@ -216,6 +235,7 @@ def on_exit(status)
runner.status.exitstatus.should == 0
runner.output_text.should == "43\n"
runner.error_text.should be_empty
+ runner.pid.should > 0
end
end

0 comments on commit 34a1f7b

Please sign in to comment.