right_popen 1.0.11 → 1.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Rakefile +71 -11
- data/lib/right_popen.rb +5 -5
- data/lib/right_popen/linux/accumulator.rb +117 -0
- data/lib/right_popen/linux/process.rb +148 -0
- data/lib/right_popen/linux/right_popen.rb +170 -0
- data/lib/right_popen/linux/utilities.rb +91 -0
- data/lib/right_popen/version.rb +28 -0
- data/right_popen.gemspec +12 -15
- data/spec/background.rb +28 -0
- data/spec/right_popen/linux/accumulator_spec.rb +272 -0
- data/spec/right_popen/linux/utilities_spec.rb +178 -0
- data/spec/right_popen_spec.rb +185 -253
- data/spec/runner.rb +112 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/stdout.rb +28 -0
- metadata +84 -12
- data/lib/linux/right_popen.rb +0 -118
data/Rakefile
CHANGED
@@ -1,14 +1,37 @@
|
|
1
1
|
require 'rubygems'
|
2
|
+
require 'bundler'
|
2
3
|
require 'fileutils'
|
3
4
|
require 'rake'
|
4
5
|
require 'rake/clean'
|
5
|
-
require 'rake/
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rake/rdoctask'
|
8
|
+
require 'spec/rake/spectask'
|
6
9
|
require 'rbconfig'
|
7
10
|
|
11
|
+
def list_spec_files
|
12
|
+
list = Dir['spec/**/*_spec.rb']
|
13
|
+
list.delete_if { |path| path.include?('/linux/') } if RUBY_PLATFORM =~ /mswin/
|
14
|
+
list
|
15
|
+
end
|
16
|
+
|
8
17
|
include Config
|
9
18
|
|
19
|
+
Bundler::GemHelper.install_tasks
|
20
|
+
|
21
|
+
# == Gem == #
|
22
|
+
|
23
|
+
gemtask = Rake::GemPackageTask.new(Gem::Specification.load("right_popen.gemspec")) do |package|
|
24
|
+
package.package_dir = ENV['PACKAGE_DIR'] || 'pkg'
|
25
|
+
package.need_zip = true
|
26
|
+
package.need_tar = true
|
27
|
+
end
|
28
|
+
|
29
|
+
directory gemtask.package_dir
|
30
|
+
|
31
|
+
CLEAN.include(gemtask.package_dir)
|
32
|
+
|
10
33
|
desc "Clean any build files for right_popen"
|
11
|
-
task :
|
34
|
+
task :win_clean do
|
12
35
|
if RUBY_PLATFORM =~ /mswin/
|
13
36
|
if File.exists?('ext/Makefile')
|
14
37
|
Dir.chdir('ext') do
|
@@ -18,6 +41,7 @@ task :clean do
|
|
18
41
|
rm 'lib/win32/right_popen.so' if File.file?('lib/win32/right_popen.so')
|
19
42
|
end
|
20
43
|
end
|
44
|
+
task :clean => :win_clean
|
21
45
|
|
22
46
|
desc "Build right_popen (but don't install it)"
|
23
47
|
task :build => [:clean] do
|
@@ -32,15 +56,14 @@ task :build => [:clean] do
|
|
32
56
|
end
|
33
57
|
|
34
58
|
desc "Build a binary gem"
|
35
|
-
task :gem => [:build]
|
36
|
-
Dir["*.gem"].each { |gem| rm gem }
|
37
|
-
ruby 'right_popen.gemspec'
|
38
|
-
end
|
59
|
+
task :gem => [:build]
|
39
60
|
|
40
61
|
desc 'Install the right_popen library as a gem'
|
41
62
|
task :install_gem => [:gem] do
|
42
|
-
|
43
|
-
|
63
|
+
Dir.chdir(File.dirname(__FILE__)) do
|
64
|
+
file = Dir["pkg/*.gem"].first
|
65
|
+
sh "gem install #{file}"
|
66
|
+
end
|
44
67
|
end
|
45
68
|
|
46
69
|
desc 'Uninstalls and reinstalls the right_popen library as a gem'
|
@@ -49,7 +72,44 @@ task :reinstall_gem do
|
|
49
72
|
sh "rake install_gem"
|
50
73
|
end
|
51
74
|
|
52
|
-
|
53
|
-
|
54
|
-
|
75
|
+
# == Unit Tests == #
|
76
|
+
|
77
|
+
task :specs => :spec
|
78
|
+
|
79
|
+
desc "Run unit tests"
|
80
|
+
Spec::Rake::SpecTask.new do |t|
|
81
|
+
t.spec_files = list_spec_files
|
82
|
+
end
|
83
|
+
|
84
|
+
desc "Run unit tests with RCov"
|
85
|
+
Spec::Rake::SpecTask.new(:rcov) do |t|
|
86
|
+
t.spec_files = list_spec_files
|
87
|
+
t.rcov = true
|
88
|
+
end
|
89
|
+
|
90
|
+
desc "Print Specdoc for unit tests"
|
91
|
+
Spec::Rake::SpecTask.new(:doc) do |t|
|
92
|
+
t.spec_opts = ["--format", "specdoc", "--dry-run"]
|
93
|
+
t.spec_files = list_spec_files
|
94
|
+
end
|
95
|
+
|
96
|
+
# == Documentation == #
|
97
|
+
|
98
|
+
desc "Generate API documentation to doc/rdocs/index.html"
|
99
|
+
Rake::RDocTask.new do |rd|
|
100
|
+
rd.rdoc_dir = 'doc/rdocs'
|
101
|
+
rd.main = 'README.rdoc'
|
102
|
+
rd.rdoc_files.include 'README.rdoc', "lib/**/*.rb"
|
103
|
+
|
104
|
+
rd.options << '--inline-source'
|
105
|
+
rd.options << '--line-numbers'
|
106
|
+
rd.options << '--all'
|
107
|
+
rd.options << '--fileboxes'
|
108
|
+
rd.options << '--diagram'
|
109
|
+
end
|
110
|
+
|
111
|
+
# == Emacs integration == #
|
112
|
+
desc "Rebuild TAGS file"
|
113
|
+
task :tags do
|
114
|
+
sh "rtags -R lib spec"
|
55
115
|
end
|
data/lib/right_popen.rb
CHANGED
@@ -26,9 +26,9 @@
|
|
26
26
|
# It relies on EventMachine for most of its internal mechanisms.
|
27
27
|
|
28
28
|
if RUBY_PLATFORM =~ /mswin/
|
29
|
-
require File.expand_path(File.join(File.dirname(__FILE__), 'win32', 'right_popen'))
|
29
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'right_popen', 'win32', 'right_popen'))
|
30
30
|
else
|
31
|
-
require File.expand_path(File.join(File.dirname(__FILE__), 'linux', 'right_popen'))
|
31
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'right_popen', 'linux', 'right_popen'))
|
32
32
|
end
|
33
33
|
|
34
34
|
module RightScale
|
@@ -55,13 +55,13 @@ module RightScale
|
|
55
55
|
# options[:exit_handler](String):: Exit handler method name, optional
|
56
56
|
#
|
57
57
|
# === Returns
|
58
|
-
# true::
|
58
|
+
# true:: always true
|
59
59
|
def self.popen3(options)
|
60
60
|
raise "EventMachine reactor must be started" unless EM.reactor_running?
|
61
61
|
raise "Missing command" unless options[:command]
|
62
62
|
raise "Missing target" unless options[:target] || !options[:stdout_handler] && !options[:stderr_handler] && !options[:exit_handler] && !options[:pid_handler]
|
63
|
-
|
64
|
-
|
63
|
+
GC.start # To garbage collect open file descriptors from passed executions
|
64
|
+
return RightScale.popen3_imp(options)
|
65
65
|
end
|
66
66
|
|
67
67
|
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
#-- -*- mode: ruby; encoding: utf-8 -*-
|
2
|
+
# Copyright: Copyright (c) 2011 RightScale, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# 'Software'), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
module RightScale
|
25
|
+
module RightPopen
|
26
|
+
class Accumulator
|
27
|
+
READ_CHUNK_SIZE = 4096
|
28
|
+
|
29
|
+
def initialize(process, inputs, read_callbacks, outputs, write_callbacks)
|
30
|
+
@process = process
|
31
|
+
@inputs = inputs
|
32
|
+
@outputs = outputs
|
33
|
+
null = Proc.new {}
|
34
|
+
@reads = {}
|
35
|
+
@writes = {}
|
36
|
+
inputs.zip(read_callbacks).each do |pair|
|
37
|
+
input, callback = pair
|
38
|
+
@reads[input] = callback
|
39
|
+
end
|
40
|
+
outputs.zip(write_callbacks).each do |pair|
|
41
|
+
output, callback = pair
|
42
|
+
@writes[output] = callback
|
43
|
+
end
|
44
|
+
@writebuffers = {}
|
45
|
+
@status = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def tick(sleep_time = 0.1)
|
49
|
+
return true unless @process.status.nil?
|
50
|
+
|
51
|
+
@process.status = status = ::Process.waitpid2(@process.pid, ::Process::WNOHANG)
|
52
|
+
|
53
|
+
inputs = @inputs.dup
|
54
|
+
outputs = @outputs.dup
|
55
|
+
ready = nil
|
56
|
+
while ready.nil?
|
57
|
+
begin
|
58
|
+
# in theory, we should note "exceptional conditions" and
|
59
|
+
# permit procs for those, too. In practice there are only
|
60
|
+
# two times when exceptional conditions occur: out of band
|
61
|
+
# data in TCP connections and "packet mode" for
|
62
|
+
# pseudoterminals. We care about neither of these,
|
63
|
+
# therefore ignore exceptional conditions.
|
64
|
+
ready = IO.select(inputs, outputs, nil, sleep_time)
|
65
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
66
|
+
end
|
67
|
+
end unless inputs.empty? && outputs.empty?
|
68
|
+
|
69
|
+
ready[0].each do |fdes|
|
70
|
+
if fdes.eof?
|
71
|
+
fdes.close
|
72
|
+
@inputs.delete(fdes)
|
73
|
+
else
|
74
|
+
chunk = fdes.readpartial(READ_CHUNK_SIZE)
|
75
|
+
@reads[fdes].call(chunk) if @reads[fdes]
|
76
|
+
end
|
77
|
+
end unless ready.nil? || ready[0].nil?
|
78
|
+
ready[1].each do |fdes|
|
79
|
+
buffered = @writebuffers[fdes]
|
80
|
+
buffered = @writes[fdes].call if @writes[fdes] if buffered.nil? || buffered.empty?
|
81
|
+
if buffered.nil?
|
82
|
+
fdes.close
|
83
|
+
@outputs.delete(fdes)
|
84
|
+
elsif !buffered.empty?
|
85
|
+
begin
|
86
|
+
amount = fdes.write_nonblock buffered
|
87
|
+
@writebuffers[fdes] = buffered[amount..-1]
|
88
|
+
rescue Errno::EPIPE
|
89
|
+
# subprocess closed the pipe; fine.
|
90
|
+
fdes.close
|
91
|
+
@outputs.delete(fdes)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end unless ready.nil? || ready[1].nil?
|
95
|
+
|
96
|
+
return !@process.status.nil?
|
97
|
+
end
|
98
|
+
|
99
|
+
def number_waiting_on
|
100
|
+
@inputs.size + @outputs.size
|
101
|
+
end
|
102
|
+
|
103
|
+
def cleanup
|
104
|
+
@inputs.each {|p| p.close unless p.closed? }
|
105
|
+
@outputs.each {|p| p.close unless p.closed? }
|
106
|
+
@process.status = ::Process.waitpid2(@process.pid) if @process.status.nil?
|
107
|
+
end
|
108
|
+
|
109
|
+
def run_to_completion(sleep_time=0.1)
|
110
|
+
until tick(sleep_time)
|
111
|
+
break if number_waiting_on == 0
|
112
|
+
end
|
113
|
+
cleanup
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
#-- -*- mode: ruby; encoding: utf-8 -*-
|
2
|
+
# Copyright: Copyright (c) 2011 RightScale, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# 'Software'), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require 'etc'
|
25
|
+
|
26
|
+
module RightScale
|
27
|
+
module RightPopen
|
28
|
+
class Process
|
29
|
+
attr_reader :pid, :stdin, :stdout, :stderr, :status_fd
|
30
|
+
attr_accessor :status
|
31
|
+
|
32
|
+
def initialize(parameters={})
|
33
|
+
parameters[:locale] = true unless parameters.has_key?(:locale)
|
34
|
+
@parameters = parameters
|
35
|
+
@status_fd = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def fork(cmd)
|
39
|
+
@cmd = cmd
|
40
|
+
stdin_r, stdin_w = IO.pipe
|
41
|
+
stdout_r, stdout_w = IO.pipe
|
42
|
+
stderr_r, stderr_w = IO.pipe
|
43
|
+
status_r, status_w = IO.pipe
|
44
|
+
|
45
|
+
[stdin_r, stdin_w, stdout_r, stdout_w,
|
46
|
+
stderr_r, stderr_w, status_r, status_w].each {|fdes| fdes.sync = true}
|
47
|
+
|
48
|
+
@pid = Kernel::fork do
|
49
|
+
begin
|
50
|
+
stdin_w.close
|
51
|
+
STDIN.reopen stdin_r
|
52
|
+
|
53
|
+
stdout_r.close
|
54
|
+
STDOUT.reopen stdout_w
|
55
|
+
|
56
|
+
stderr_r.close
|
57
|
+
STDERR.reopen stderr_w
|
58
|
+
|
59
|
+
status_r.close
|
60
|
+
status_w.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
61
|
+
|
62
|
+
if group = get_group
|
63
|
+
::Process.egid = group
|
64
|
+
::Process.gid = group
|
65
|
+
end
|
66
|
+
|
67
|
+
if user = get_user
|
68
|
+
::Process.euid = user
|
69
|
+
::Process.uid = user
|
70
|
+
end
|
71
|
+
|
72
|
+
Dir.chdir(@parameters[:directory]) if @parameters[:directory]
|
73
|
+
|
74
|
+
ENV["LC_ALL"] = "C" if @parameters[:locale]
|
75
|
+
|
76
|
+
@parameters[:environment].each do |key,value|
|
77
|
+
ENV[key.to_s] = value.to_s
|
78
|
+
end if @parameters[:environment]
|
79
|
+
|
80
|
+
File.umask(get_umask) if @parameters[:umask]
|
81
|
+
|
82
|
+
if cmd.kind_of?(Array)
|
83
|
+
exec(*cmd)
|
84
|
+
else
|
85
|
+
exec("sh", "-c", cmd)
|
86
|
+
end
|
87
|
+
raise 'forty-two'
|
88
|
+
rescue Exception => e
|
89
|
+
Marshal.dump(e, status_w)
|
90
|
+
end
|
91
|
+
status_w.close
|
92
|
+
exit!
|
93
|
+
end
|
94
|
+
|
95
|
+
stdin_r.close
|
96
|
+
stdout_w.close
|
97
|
+
stderr_w.close
|
98
|
+
status_w.close
|
99
|
+
@stdin = stdin_w
|
100
|
+
@stdout = stdout_r
|
101
|
+
@stderr = stderr_r
|
102
|
+
@status_fd = status_r
|
103
|
+
end
|
104
|
+
|
105
|
+
def wait_for_exec
|
106
|
+
begin
|
107
|
+
e = Marshal.load @status_fd
|
108
|
+
# thus meaning that the process failed to exec...
|
109
|
+
@stdin.close
|
110
|
+
@stdout.close
|
111
|
+
@stderr.close
|
112
|
+
raise(Exception === e ? e : "unknown failure!")
|
113
|
+
rescue EOFError
|
114
|
+
# thus meaning that the process did exec and we can continue.
|
115
|
+
ensure
|
116
|
+
@status_fd.close
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def get_user
|
123
|
+
user = @parameters[:user] || nil
|
124
|
+
unless user.kind_of?(Integer)
|
125
|
+
user = Etc.getpwnam(user).uid if user
|
126
|
+
end
|
127
|
+
user
|
128
|
+
end
|
129
|
+
|
130
|
+
def get_group
|
131
|
+
group = @parameters[:group] || nil
|
132
|
+
unless group.kind_of?(Integer)
|
133
|
+
group = Etc.getgrnam(group).gid if group
|
134
|
+
end
|
135
|
+
group
|
136
|
+
end
|
137
|
+
|
138
|
+
def get_umask
|
139
|
+
if @parameters[:umask].respond_to?(:oct)
|
140
|
+
value = @parameters[:umask].oct
|
141
|
+
else
|
142
|
+
value = @parameters[:umask].to_i
|
143
|
+
end
|
144
|
+
value & 007777
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
# RightScale.popen3 allows running external processes aynchronously
|
25
|
+
# while still capturing their standard and error outputs.
|
26
|
+
# It relies on EventMachine for most of its internal mechanisms.
|
27
|
+
|
28
|
+
require 'rubygems'
|
29
|
+
require 'eventmachine'
|
30
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "process"))
|
31
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "accumulator"))
|
32
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "utilities"))
|
33
|
+
|
34
|
+
module RightScale
|
35
|
+
# ensure uniqueness of handler to avoid confusion.
|
36
|
+
raise "#{StatusHandler.name} is already defined" if defined?(StatusHandler)
|
37
|
+
|
38
|
+
module StatusHandler
|
39
|
+
def initialize(file_handle)
|
40
|
+
# Voodoo to make sure that Ruby doesn't gc the file handle
|
41
|
+
# (closing the stream) before we're done with it. No, oddly
|
42
|
+
# enough EventMachine is not good about holding on to this
|
43
|
+
# itself.
|
44
|
+
@handle = file_handle
|
45
|
+
@data = ""
|
46
|
+
end
|
47
|
+
|
48
|
+
def receive_data(data)
|
49
|
+
@data << data
|
50
|
+
end
|
51
|
+
|
52
|
+
def drain_and_close
|
53
|
+
begin
|
54
|
+
while ready = IO.select([@handle], nil, nil, 0)
|
55
|
+
break if @handle.eof?
|
56
|
+
data = @handle.readpartial(4096)
|
57
|
+
receive_data(data)
|
58
|
+
end
|
59
|
+
rescue Errno::EBADF
|
60
|
+
rescue EOFError
|
61
|
+
end
|
62
|
+
close_connection
|
63
|
+
end
|
64
|
+
|
65
|
+
def unbind
|
66
|
+
if @data.size > 0
|
67
|
+
e = Marshal.load @data
|
68
|
+
raise (Exception === e ? e : "unknown failure: saw #{e} on status channel")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# ensure uniqueness of handler to avoid confusion.
|
74
|
+
raise "#{PipeHandler.name} is already defined" if defined?(PipeHandler)
|
75
|
+
|
76
|
+
module PipeHandler
|
77
|
+
def initialize(file_handle, target, handler)
|
78
|
+
# Voodoo to make sure that Ruby doesn't gc the file handle
|
79
|
+
# (closing the stream) before we're done with it. No, oddly
|
80
|
+
# enough EventMachine is not good about holding on to this
|
81
|
+
# itself.
|
82
|
+
@handle = file_handle
|
83
|
+
@target = target
|
84
|
+
@handler = handler
|
85
|
+
end
|
86
|
+
|
87
|
+
def receive_data(data)
|
88
|
+
@target.method(@handler).call(data) if @handler
|
89
|
+
end
|
90
|
+
|
91
|
+
def drain_and_close
|
92
|
+
begin
|
93
|
+
while ready = IO.select([@handle], nil, nil, 0)
|
94
|
+
break if @handle.eof?
|
95
|
+
data = @handle.readpartial(4096)
|
96
|
+
receive_data(data)
|
97
|
+
end
|
98
|
+
rescue Errno::EBADF
|
99
|
+
rescue EOFError
|
100
|
+
end
|
101
|
+
close_connection
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# ensure uniqueness of handler to avoid confusion.
|
106
|
+
raise "#{InputHandler.name} is already defined" if defined?(InputHandler)
|
107
|
+
|
108
|
+
module InputHandler
|
109
|
+
def initialize(file_handle, string)
|
110
|
+
# Voodoo to make sure that Ruby doesn't gc the file handle
|
111
|
+
# (closing the stream) before we're done with it. No, oddly
|
112
|
+
# enough EventMachine is not good about holding on to this
|
113
|
+
# itself.
|
114
|
+
@handle = file_handle
|
115
|
+
@string = string
|
116
|
+
end
|
117
|
+
|
118
|
+
def post_init
|
119
|
+
send_data(@string) if @string
|
120
|
+
close_connection_after_writing
|
121
|
+
end
|
122
|
+
|
123
|
+
def drain_and_close
|
124
|
+
close_connection
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Forks process to run given command asynchronously, hooking all three
|
129
|
+
# standard streams of the child process.
|
130
|
+
#
|
131
|
+
# === Parameters
|
132
|
+
# options[:pid_handler](Symbol):: Token for pid handler method name.
|
133
|
+
#
|
134
|
+
# See RightScale.popen3
|
135
|
+
def self.popen3_imp(options)
|
136
|
+
# note GC.start moved to common popen3 entry method for use by both windows and linux.
|
137
|
+
EM.next_tick do
|
138
|
+
process = RightPopen::Process.new(:environment => options[:environment] || {})
|
139
|
+
process.fork(options[:command])
|
140
|
+
|
141
|
+
status_handler = EM.attach(process.status_fd, StatusHandler, process.status_fd)
|
142
|
+
stderr_handler = EM.attach(process.stderr, PipeHandler, process.stderr, options[:target],
|
143
|
+
options[:stderr_handler])
|
144
|
+
stdout_handler = EM.attach(process.stdout, PipeHandler, process.stdout, options[:target],
|
145
|
+
options[:stdout_handler])
|
146
|
+
stdin_handler = EM.attach(process.stdin, InputHandler, process.stdin, options[:input])
|
147
|
+
|
148
|
+
options[:target].method(options[:pid_handler]).call(process.pid) if
|
149
|
+
options.key? :pid_handler
|
150
|
+
|
151
|
+
wait_timer = EM::PeriodicTimer.new(1) do
|
152
|
+
value = ::Process.waitpid2(process.pid, Process::WNOHANG)
|
153
|
+
unless value.nil?
|
154
|
+
begin
|
155
|
+
ignored, status = value
|
156
|
+
options[:target].method(options[:exit_handler]).call(status) if
|
157
|
+
options[:exit_handler]
|
158
|
+
ensure
|
159
|
+
stdin_handler.drain_and_close
|
160
|
+
stdout_handler.drain_and_close
|
161
|
+
stderr_handler.drain_and_close
|
162
|
+
status_handler.drain_and_close
|
163
|
+
wait_timer.cancel
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
true
|
169
|
+
end
|
170
|
+
end
|