right_popen 1.1.3 → 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.rdoc +2 -0
- data/lib/right_popen.rb +35 -31
- data/lib/right_popen/linux/popen3_async.rb +26 -10
- data/lib/right_popen/linux/process.rb +21 -25
- data/lib/right_popen/{linux/popen3_sync.rb → popen3_sync.rb} +2 -1
- data/lib/right_popen/process_base.rb +20 -16
- data/lib/right_popen/process_status.rb +3 -0
- data/lib/right_popen/safe_output_buffer.rb +3 -0
- data/lib/right_popen/target_proxy.rb +12 -8
- data/right_popen.gemspec +57 -31
- metadata +39 -128
- data/Rakefile +0 -127
- data/lib/right_popen/linux/accumulator.rb +0 -127
- data/lib/right_popen/linux/utilities.rb +0 -94
- data/lib/right_popen/version.rb +0 -28
- data/spec/background.rb +0 -28
- data/spec/increment.rb +0 -2
- data/spec/print_env.rb +0 -2
- data/spec/produce_mixed_output.rb +0 -12
- data/spec/produce_output.rb +0 -2
- data/spec/produce_status.rb +0 -1
- data/spec/produce_stderr_only.rb +0 -5
- data/spec/produce_stdout_only.rb +0 -5
- data/spec/right_popen/linux/accumulator_spec.rb +0 -267
- data/spec/right_popen/linux/utilities_spec.rb +0 -178
- data/spec/right_popen/safe_output_buffer_spec.rb +0 -26
- data/spec/right_popen_spec.rb +0 -331
- data/spec/runner.rb +0 -217
- data/spec/sleeper.rb +0 -35
- data/spec/spec_helper.rb +0 -26
- data/spec/stdout.rb +0 -28
- data/spec/writer.rb +0 -34
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0c6e583ac6c97d2d520601667148416247a8cbca
|
4
|
+
data.tar.gz: ac4c240351ecb997a4b911e1fa6e8a71ca95275a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fceac1055ea55fb2b423fd65b51b380c4a948b7736084f3e2288df6bfa3715276650737fd8505b82cf9eabcb7e79d729b21802b893d95cfcd0b34113fdea8a41
|
7
|
+
data.tar.gz: 3f0b2cd13282f1456c1115df88a7a2c8d6a4fdc742cb73faece900fa504c2937758d555603929ad4485ad7afdc36288e816313e9035079c81141a2df5d08554a
|
data/README.rdoc
CHANGED
data/lib/right_popen.rb
CHANGED
@@ -21,39 +21,20 @@
|
|
21
21
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
22
|
#++
|
23
23
|
|
24
|
-
require File.expand_path(File.join(File.dirname(__FILE__), 'right_popen', 'target_proxy'))
|
25
|
-
|
26
|
-
# TEAL FIX: this seems like test harness code smell, not production code. it
|
27
|
-
# should be removed in next major revision. unfortunately we cannot remove these
|
28
|
-
# require statements without breaking any code that depends on them.
|
29
|
-
unless RUBY_PLATFORM =~ /mswin|mingw/
|
30
|
-
require File.expand_path(File.join(File.dirname(__FILE__), 'right_popen', 'linux', 'accumulator'))
|
31
|
-
require File.expand_path(File.join(File.dirname(__FILE__), 'right_popen', 'linux', 'utilities'))
|
32
|
-
end
|
33
|
-
|
34
24
|
module RightScale
|
25
|
+
module RightPopen
|
35
26
|
|
36
|
-
|
37
|
-
|
38
|
-
# @deprecated in favor of sync vs. async methods in RightPopen namespace.
|
39
|
-
#
|
40
|
-
# === Parameters
|
41
|
-
# @param [Hash] options for execution
|
42
|
-
#
|
43
|
-
# === Returns
|
44
|
-
# @return [TrueClass] always true
|
45
|
-
def self.popen3(options)
|
46
|
-
warn 'WARNING: RightScale.popen3 is deprecated in favor of RightScale::RightPopen.popen3_async'
|
47
|
-
options = options.dup
|
48
|
-
cmd = options.dup.delete(:command)
|
49
|
-
raise ::ArgumentError.new("Missing command") unless cmd
|
50
|
-
::RightScale::RightPopen.popen3_async(options[:command], options)
|
51
|
-
end
|
27
|
+
# exceptions
|
28
|
+
class ProcessError < Exception; end
|
52
29
|
|
53
|
-
|
30
|
+
# autoloads
|
31
|
+
autoload :ProcessStatus, 'right_popen/process_status'
|
32
|
+
autoload :SafeOutputBuffer, 'right_popen/safe_output_buffer'
|
33
|
+
autoload :TargetProxy, 'right_popen/target_proxy'
|
54
34
|
|
55
35
|
# see popen3_async for details.
|
56
36
|
DEFAULT_POPEN3_OPTIONS = {
|
37
|
+
:directory => nil,
|
57
38
|
:environment => nil,
|
58
39
|
:exit_handler => nil,
|
59
40
|
:group => nil,
|
@@ -80,15 +61,36 @@ module RightScale
|
|
80
61
|
# === Return
|
81
62
|
# @return [TrueClass] always true
|
82
63
|
def self.require_popen3_impl(synchronicity)
|
64
|
+
# implementation of Process is specific to platform.
|
83
65
|
case RUBY_PLATFORM
|
84
66
|
when /mswin/
|
85
|
-
|
67
|
+
platform_subdir = 'windows'
|
68
|
+
impl_subdir = ::File.join(platform_subdir, 'mswin')
|
86
69
|
when /mingw/
|
87
|
-
|
70
|
+
platform_subdir = 'windows'
|
71
|
+
impl_subdir = ::File.join(platform_subdir, 'mingw')
|
72
|
+
when /win32|dos|cygwin/
|
73
|
+
raise NotImplementedError
|
88
74
|
else
|
89
|
-
|
75
|
+
platform_subdir = 'linux'
|
76
|
+
impl_subdir = platform_subdir
|
90
77
|
end
|
91
|
-
|
78
|
+
impl_module = ::File.join(impl_subdir, 'process')
|
79
|
+
|
80
|
+
# only require EM when async is requested.
|
81
|
+
case synchronicity
|
82
|
+
when :popen3_sync
|
83
|
+
sync_module = 'popen3_sync'
|
84
|
+
when :popen3_async
|
85
|
+
sync_module = ::File.join(platform_subdir, 'popen3_async')
|
86
|
+
else
|
87
|
+
fail 'unexpected synchronicity'
|
88
|
+
end
|
89
|
+
|
90
|
+
# platform-specific requires.
|
91
|
+
base_dir = ::File.join(::File.dirname(__FILE__), 'right_popen').gsub("\\", '/')
|
92
|
+
require ::File.expand_path(impl_module, base_dir)
|
93
|
+
require ::File.expand_path(sync_module, base_dir)
|
92
94
|
end
|
93
95
|
|
94
96
|
# Spawns a process to run given command synchronously. This is similar to
|
@@ -129,6 +131,7 @@ module RightScale
|
|
129
131
|
#
|
130
132
|
# === Parameters
|
131
133
|
# @param [Hash] options for execution
|
134
|
+
# @option options [String] :directory as initial working directory for child process or nil to inherit current working directory
|
132
135
|
# @option options [Hash] :environment variables values keyed by name
|
133
136
|
# @option options [Symbol] :exit_handler target method called on exit
|
134
137
|
# @option options [Integer|String] :group or gid for forked process (linux only)
|
@@ -145,6 +148,7 @@ module RightScale
|
|
145
148
|
# @option options [Integer|String] :user or uid for forked process (linux only)
|
146
149
|
# @option options [Symbol] :watch_handler called periodically with process during watch; return true to continue, false to abandon (sync only)
|
147
150
|
# @option options [String] :watch_directory to monitor for child process writing files
|
151
|
+
# @option options [Symbol] :async_exception_handler target method called if an exception is handled (on another thread)
|
148
152
|
#
|
149
153
|
# === Returns
|
150
154
|
# @return [TrueClass] always true
|
@@ -22,9 +22,9 @@
|
|
22
22
|
#++
|
23
23
|
|
24
24
|
require 'rubygems'
|
25
|
+
require 'right_popen'
|
25
26
|
require 'eventmachine'
|
26
|
-
|
27
|
-
require File.expand_path(File.join(File.dirname(__FILE__), "process"))
|
27
|
+
require 'yaml'
|
28
28
|
|
29
29
|
module RightScale::RightPopen
|
30
30
|
|
@@ -32,12 +32,13 @@ module RightScale::RightPopen
|
|
32
32
|
raise "#{StatusHandler.name} is already defined" if defined?(StatusHandler)
|
33
33
|
|
34
34
|
module StatusHandler
|
35
|
-
def initialize(file_handle)
|
35
|
+
def initialize(file_handle, target)
|
36
36
|
# Voodoo to make sure that Ruby doesn't gc the file handle
|
37
37
|
# (closing the stream) before we're done with it. No, oddly
|
38
38
|
# enough EventMachine is not good about holding on to this
|
39
39
|
# itself.
|
40
40
|
@handle = file_handle
|
41
|
+
@target = target
|
41
42
|
@data = ""
|
42
43
|
end
|
43
44
|
|
@@ -59,8 +60,15 @@ module RightScale::RightPopen
|
|
59
60
|
|
60
61
|
def unbind
|
61
62
|
if @data.size > 0
|
62
|
-
|
63
|
-
|
63
|
+
error_data = ::YAML.load(@data)
|
64
|
+
status_fd_error = ::RightScale::RightPopen::ProcessError.new(
|
65
|
+
"#{error_data['class']}: #{error_data['message']}")
|
66
|
+
if error_data['backtrace']
|
67
|
+
status_fd_error.set_backtrace(error_data['backtrace'])
|
68
|
+
end
|
69
|
+
if @target
|
70
|
+
@target.async_exception_handler(status_fd_error) rescue nil
|
71
|
+
end
|
64
72
|
end
|
65
73
|
end
|
66
74
|
end
|
@@ -132,7 +140,7 @@ module RightScale::RightPopen
|
|
132
140
|
|
133
141
|
# connect EM eventables to open streams.
|
134
142
|
handlers = []
|
135
|
-
handlers << ::EM.attach(process.status_fd, ::RightScale::RightPopen::StatusHandler, process.status_fd)
|
143
|
+
handlers << ::EM.attach(process.status_fd, ::RightScale::RightPopen::StatusHandler, process.status_fd, target)
|
136
144
|
handlers << ::EM.attach(process.stderr, ::RightScale::RightPopen::PipeHandler, process.stderr, target, :stderr_handler)
|
137
145
|
handlers << ::EM.attach(process.stdout, ::RightScale::RightPopen::PipeHandler, process.stdout, target, :stdout_handler)
|
138
146
|
handlers << ::EM.attach(process.stdin, ::RightScale::RightPopen::InputHandler, process.stdin, options[:input])
|
@@ -147,13 +155,16 @@ module RightScale::RightPopen
|
|
147
155
|
|
148
156
|
# periodic watcher.
|
149
157
|
watch_process(process, 0.1, target, handlers)
|
150
|
-
rescue
|
158
|
+
rescue Exception => e
|
151
159
|
# we can't raise from the main EM thread or it will stop EM.
|
152
160
|
# the spawn method will signal the exit handler but not the
|
153
161
|
# pid handler in this case since there is no pid. any action
|
154
162
|
# (logging, etc.) associated with the failure will have to be
|
155
163
|
# driven by the exit handler.
|
156
|
-
|
164
|
+
if target
|
165
|
+
target.async_exception_handler(e) rescue nil
|
166
|
+
target.exit_handler(process.status) rescue nil if process
|
167
|
+
end
|
157
168
|
end
|
158
169
|
end
|
159
170
|
true
|
@@ -188,13 +199,18 @@ module RightScale::RightPopen
|
|
188
199
|
target.size_limit_handler rescue nil if process.size_limit_exceeded?
|
189
200
|
target.exit_handler(process.status) rescue nil
|
190
201
|
end
|
191
|
-
rescue
|
202
|
+
rescue Exception => e
|
192
203
|
# we can't raise from the main EM thread or it will stop EM.
|
193
204
|
# the spawn method will signal the exit handler but not the
|
194
205
|
# pid handler in this case since there is no pid. any action
|
195
206
|
# (logging, etc.) associated with the failure will have to be
|
196
207
|
# driven by the exit handler.
|
197
|
-
|
208
|
+
if target
|
209
|
+
target.async_exception_handler(e) rescue nil
|
210
|
+
status = process && process.status
|
211
|
+
status ||= ::RightScale::RightPopen::ProcessStatus.new(nil, 1)
|
212
|
+
target.exit_handler(status)
|
213
|
+
end
|
198
214
|
end
|
199
215
|
end
|
200
216
|
true
|
@@ -21,14 +21,16 @@
|
|
21
21
|
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
22
|
#++
|
23
23
|
|
24
|
+
require 'rubygems'
|
24
25
|
require 'etc'
|
25
|
-
|
26
|
-
require
|
27
|
-
require
|
26
|
+
require 'fcntl'
|
27
|
+
require 'yaml'
|
28
|
+
require 'right_popen'
|
29
|
+
require 'right_popen/process_base'
|
28
30
|
|
29
31
|
module RightScale
|
30
32
|
module RightPopen
|
31
|
-
class Process < ProcessBase
|
33
|
+
class Process < ::RightScale::RightPopen::ProcessBase
|
32
34
|
|
33
35
|
def initialize(options={})
|
34
36
|
super(options)
|
@@ -39,7 +41,9 @@ module RightScale
|
|
39
41
|
# === Return
|
40
42
|
# @return [TrueClass|FalseClass] true if running
|
41
43
|
def alive?
|
42
|
-
|
44
|
+
unless @pid
|
45
|
+
raise ::RightScale::RightPopen::ProcessError, 'Process not started'
|
46
|
+
end
|
43
47
|
unless @status
|
44
48
|
begin
|
45
49
|
ignored, status = ::Process.waitpid2(@pid, ::Process::WNOHANG)
|
@@ -72,7 +76,9 @@ module RightScale
|
|
72
76
|
# === Return
|
73
77
|
# @return [ProcessStatus] exit status
|
74
78
|
def wait_for_exit_status
|
75
|
-
|
79
|
+
unless @pid
|
80
|
+
raise ::RightScale::RightPopen::ProcessError, 'Process not started'
|
81
|
+
end
|
76
82
|
unless @status
|
77
83
|
begin
|
78
84
|
ignored, status = ::Process.waitpid2(@pid)
|
@@ -162,7 +168,7 @@ module RightScale
|
|
162
168
|
environment_hash['LC_ALL'] = 'C' if @options[:locale]
|
163
169
|
environment_hash.merge!(@options[:environment]) if @options[:environment]
|
164
170
|
environment_hash.each do |key, value|
|
165
|
-
::ENV[key.to_s] = value.
|
171
|
+
::ENV[key.to_s] = value.nil? ? nil: value.to_s
|
166
172
|
end
|
167
173
|
|
168
174
|
if cmd.kind_of?(Array)
|
@@ -173,7 +179,14 @@ module RightScale
|
|
173
179
|
end
|
174
180
|
raise 'Unreachable code'
|
175
181
|
rescue ::Exception => e
|
176
|
-
|
182
|
+
# note that Marshal.dump/load isn't reliable for all kinds of
|
183
|
+
# exceptions or else can be truncated by I/O buffering.
|
184
|
+
error_data = {
|
185
|
+
'class' => e.class.name,
|
186
|
+
'message' => e.message,
|
187
|
+
'backtrace' => e.backtrace
|
188
|
+
}
|
189
|
+
status_w.puts(::YAML.dump(error_data))
|
177
190
|
end
|
178
191
|
status_w.close
|
179
192
|
exit!
|
@@ -197,23 +210,6 @@ module RightScale
|
|
197
210
|
raise
|
198
211
|
end
|
199
212
|
|
200
|
-
# @deprecated this seems like test harness code smell, not production code.
|
201
|
-
def wait_for_exec
|
202
|
-
warn 'WARNING: RightScale::RightPopen::Process#wait_for_exec is deprecated in lib and will be moved to spec'
|
203
|
-
begin
|
204
|
-
e = ::Marshal.load(@status_fd)
|
205
|
-
# thus meaning that the process failed to exec...
|
206
|
-
@stdin.close
|
207
|
-
@stdout.close
|
208
|
-
@stderr.close
|
209
|
-
raise(Exception === e ? e : "unknown failure!")
|
210
|
-
rescue EOFError
|
211
|
-
# thus meaning that the process did exec and we can continue.
|
212
|
-
ensure
|
213
|
-
@status_fd.close
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
213
|
private
|
218
214
|
|
219
215
|
def get_user
|
@@ -21,14 +21,15 @@
|
|
21
21
|
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
22
|
#++
|
23
23
|
|
24
|
+
require 'rubygems'
|
25
|
+
require 'right_popen'
|
24
26
|
require 'thread'
|
27
|
+
require 'yaml'
|
25
28
|
|
26
29
|
module RightScale
|
27
30
|
module RightPopen
|
28
31
|
class ProcessBase
|
29
32
|
|
30
|
-
class ProcessError < Exception; end
|
31
|
-
|
32
33
|
attr_reader :pid, :stdin, :stdout, :stderr, :status_fd, :status
|
33
34
|
attr_reader :start_time, :stop_time, :channels_to_finish
|
34
35
|
|
@@ -146,6 +147,7 @@ module RightScale
|
|
146
147
|
@status = nil
|
147
148
|
@last_interrupt = nil
|
148
149
|
@channels_to_finish = nil
|
150
|
+
@wait_thread = nil
|
149
151
|
|
150
152
|
if @size_limit_bytes = @options[:size_limit_bytes]
|
151
153
|
@watch_directory = @options[:watch_directory] || @options[:directory] || ::Dir.pwd
|
@@ -205,7 +207,7 @@ module RightScale
|
|
205
207
|
# @return [TrueClass] always true
|
206
208
|
def sync_exit_with_target
|
207
209
|
abandon = false
|
208
|
-
|
210
|
+
status_fd_data = []
|
209
211
|
begin
|
210
212
|
while true
|
211
213
|
channels_to_watch = @channels_to_finish.map { |ctf| ctf.last }
|
@@ -223,7 +225,7 @@ module RightScale
|
|
223
225
|
data = dead ? channel.gets(nil) : channel.gets
|
224
226
|
if data
|
225
227
|
if key == :status_fd
|
226
|
-
|
228
|
+
status_fd_data << data
|
227
229
|
else
|
228
230
|
@target.method(key).call(data)
|
229
231
|
end
|
@@ -242,19 +244,19 @@ module RightScale
|
|
242
244
|
end
|
243
245
|
end
|
244
246
|
wait_for_exit_status
|
247
|
+
unless status_fd_data.empty?
|
248
|
+
data = status_fd_data.join
|
249
|
+
error_data = ::YAML.load(data)
|
250
|
+
status_fd_error = ::RightScale::RightPopen::ProcessError.new(
|
251
|
+
"#{error_data['class']}: #{error_data['message']}")
|
252
|
+
if error_data['backtrace']
|
253
|
+
status_fd_error.set_backtrace(error_data['backtrace'])
|
254
|
+
end
|
255
|
+
raise status_fd_error
|
256
|
+
end
|
245
257
|
@target.timeout_handler if timer_expired?
|
246
258
|
@target.size_limit_handler if size_limit_exceeded?
|
247
259
|
@target.exit_handler(@status)
|
248
|
-
|
249
|
-
# re-raise exception from fork, if any.
|
250
|
-
case last_exception
|
251
|
-
when nil
|
252
|
-
# all good
|
253
|
-
when ::Exception
|
254
|
-
raise last_exception
|
255
|
-
else
|
256
|
-
raise "Unknown failure: saw #{last_exception.inspect} on status channel."
|
257
|
-
end
|
258
260
|
ensure
|
259
261
|
# abandon will not close I/O objects; caller takes responsibility via
|
260
262
|
# process object passed to watch_handler. if anyone calls interrupt
|
@@ -295,7 +297,7 @@ module RightScale
|
|
295
297
|
next_interrupt = sigs.first
|
296
298
|
end
|
297
299
|
unless next_interrupt
|
298
|
-
raise ::RightScale::RightPopen::
|
300
|
+
raise ::RightScale::RightPopen::ProcessError,
|
299
301
|
'Unable to kill child process'
|
300
302
|
end
|
301
303
|
@last_interrupt = next_interrupt
|
@@ -328,7 +330,9 @@ module RightScale
|
|
328
330
|
def start_timer
|
329
331
|
# start timer when process comes alive (ruby processes are slow to
|
330
332
|
# start in Windows, etc.).
|
331
|
-
|
333
|
+
unless @pid
|
334
|
+
raise ::RightScale::RightPopen::ProcessError, 'Process not started'
|
335
|
+
end
|
332
336
|
@start_time = ::Time.now
|
333
337
|
@stop_time = @options[:timeout_seconds] ?
|
334
338
|
(@start_time + @options[:timeout_seconds]) :
|
@@ -21,6 +21,9 @@
|
|
21
21
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
22
|
#++
|
23
23
|
|
24
|
+
require 'rubygems'
|
25
|
+
require 'right_popen'
|
26
|
+
|
24
27
|
module RightScale
|
25
28
|
module RightPopen
|
26
29
|
|
@@ -29,19 +32,20 @@ module RightScale
|
|
29
32
|
class TargetProxy
|
30
33
|
|
31
34
|
HANDLER_NAME_TO_PARAMETER_COUNT = {
|
32
|
-
:exit_handler
|
33
|
-
:pid_handler
|
34
|
-
:size_limit_handler
|
35
|
-
:stderr_handler
|
36
|
-
:stdout_handler
|
37
|
-
:timeout_handler
|
38
|
-
:watch_handler
|
35
|
+
:exit_handler => 1,
|
36
|
+
:pid_handler => 1,
|
37
|
+
:size_limit_handler => 0,
|
38
|
+
:stderr_handler => 1,
|
39
|
+
:stdout_handler => 1,
|
40
|
+
:timeout_handler => 0,
|
41
|
+
:watch_handler => 1,
|
42
|
+
:async_exception_handler => 1,
|
39
43
|
}
|
40
44
|
|
41
45
|
def initialize(options = {})
|
42
46
|
if options[:target].nil? &&
|
43
47
|
!(options.keys & HANDLER_NAME_TO_PARAMETER_COUNT.keys).empty?
|
44
|
-
raise ArgumentError,
|
48
|
+
raise ::ArgumentError, 'Missing target'
|
45
49
|
end
|
46
50
|
@target = options[:target] # hold target reference (if any) against GC
|
47
51
|
|