right_popen 1.1.3 → 3.0.1
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.
- 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
|
|