right_popen 1.0.21-x86-mswin32-60 → 1.1.3-x86-mswin32-60
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +10 -8
- data/lib/right_popen.rb +125 -30
- data/lib/right_popen/process_base.rb +339 -0
- data/lib/right_popen/process_status.rb +64 -0
- data/lib/right_popen/safe_output_buffer.rb +79 -0
- data/lib/right_popen/target_proxy.rb +67 -0
- data/lib/right_popen/version.rb +2 -2
- data/lib/right_popen/win32/popen3_async.rb +258 -0
- data/lib/right_popen/win32/popen3_sync.rb +35 -0
- data/lib/right_popen/win32/process.rb +306 -0
- data/lib/right_popen/win32/{right_popen.rb → right_popen_ex.rb} +8 -231
- data/lib/win32/right_popen.so +0 -0
- data/right_popen.gemspec +2 -2
- data/spec/produce_mixed_output.rb +3 -0
- data/spec/right_popen/safe_output_buffer_spec.rb +26 -0
- data/spec/right_popen_spec.rb +272 -227
- data/spec/runner.rb +171 -79
- data/spec/sleeper.rb +35 -0
- data/spec/stdout.rb +1 -1
- data/spec/writer.rb +34 -0
- metadata +23 -15
@@ -0,0 +1,64 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 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
|
+
module RightScale
|
25
|
+
|
26
|
+
module RightPopen
|
27
|
+
|
28
|
+
# Quacks like Process::Status, which we cannot instantiate ourselves because
|
29
|
+
# has no public new method for cases where we need to create our own.
|
30
|
+
class ProcessStatus
|
31
|
+
|
32
|
+
attr_reader :pid, :exitstatus, :termsig
|
33
|
+
|
34
|
+
# === Parameters
|
35
|
+
# @param [Integer] pid as process identifier
|
36
|
+
# @param [Integer] exitstatus as process exit code or nil
|
37
|
+
# @param [Integer] termination signal or nil
|
38
|
+
def initialize(pid, exitstatus, termsig=nil)
|
39
|
+
@pid = pid
|
40
|
+
@exitstatus = exitstatus
|
41
|
+
@termsig = termsig
|
42
|
+
end
|
43
|
+
|
44
|
+
# Simulates Process::Status.exited? which seems like a weird method since
|
45
|
+
# this object cannot logically be queried until the process exits.
|
46
|
+
#
|
47
|
+
# === Returns
|
48
|
+
# @return [TrueClass] always true
|
49
|
+
def exited?
|
50
|
+
return true
|
51
|
+
end
|
52
|
+
|
53
|
+
# Simulates Process::Status.success?
|
54
|
+
#
|
55
|
+
# === Returns
|
56
|
+
# true if the process returned zero as its exit code or nil if terminate was signalled
|
57
|
+
def success?
|
58
|
+
# note that Linux ruby returns nil when exitstatus is nil and a termsig
|
59
|
+
# value is set instead.
|
60
|
+
return @exitstatus ? (0 == @exitstatus) : nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 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
|
+
module RightScale
|
25
|
+
|
26
|
+
module RightPopen
|
27
|
+
|
28
|
+
# Provides an output handler implementation that buffers output (from a
|
29
|
+
# child process) while ensuring that the output does not exhaust memory
|
30
|
+
# in the current process. it does this by preserving only the most
|
31
|
+
# interesting bits of data (start of lines, last in output).
|
32
|
+
class SafeOutputBuffer
|
33
|
+
|
34
|
+
# note utf-8 encodings for the Unicode elipsis character are inconsistent
|
35
|
+
# between ruby platforms (Windows vs Linux) and versions (1.8 vs 1.9).
|
36
|
+
ELLIPSIS = '...'
|
37
|
+
|
38
|
+
DEFAULT_MAX_LINE_COUNT = 64
|
39
|
+
DEFAULT_MAX_LINE_LENGTH = 256
|
40
|
+
|
41
|
+
attr_reader :buffer, :max_line_count, :max_line_length
|
42
|
+
|
43
|
+
# === Parameters
|
44
|
+
# @param [Array] buffer for lines
|
45
|
+
# @param [Integer] max_line_count to limit number of lines in buffer
|
46
|
+
# @param [Integer] max_line_length to truncate charcters from start of line
|
47
|
+
def initialize(buffer = [],
|
48
|
+
max_line_count = DEFAULT_MAX_LINE_COUNT,
|
49
|
+
max_line_length = DEFAULT_MAX_LINE_LENGTH)
|
50
|
+
raise ArgumentError.new('buffer is required') unless @buffer = buffer
|
51
|
+
raise ArgumentError.new('max_line_count is invalid') unless (@max_line_count = max_line_count) > 1
|
52
|
+
raise ArgumentError.new('max_line_length is invalid') unless (@max_line_length = max_line_length) > ELLIPSIS.length
|
53
|
+
end
|
54
|
+
|
55
|
+
def display_text; @buffer.join("\n"); end
|
56
|
+
|
57
|
+
# Buffers data with specified truncation.
|
58
|
+
#
|
59
|
+
# === Parameters
|
60
|
+
# @param [Object] data of any kind
|
61
|
+
def safe_buffer_data(data)
|
62
|
+
# note that the chomping ensures that the exact output cannot be
|
63
|
+
# preserved but the truncation would tend to eliminate trailing newlines
|
64
|
+
# in any case. if you want exact output then don't use safe buffering.
|
65
|
+
data = data.to_s.chomp
|
66
|
+
if @buffer.size >= @max_line_count
|
67
|
+
@buffer.shift
|
68
|
+
@buffer[0] = ELLIPSIS
|
69
|
+
end
|
70
|
+
if data.length > @max_line_length
|
71
|
+
truncation = [data.length - (@max_line_length - ELLIPSIS.length), 0].max
|
72
|
+
data = "#{data[0..(@max_line_length - ELLIPSIS.length - 1)]}#{ELLIPSIS}"
|
73
|
+
end
|
74
|
+
@buffer << data
|
75
|
+
true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 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
|
+
module RightScale
|
25
|
+
module RightPopen
|
26
|
+
|
27
|
+
# proxies calls to target to simplify popen3 implementation code and to make
|
28
|
+
# the proxied callbacks slightly more efficient.
|
29
|
+
class TargetProxy
|
30
|
+
|
31
|
+
HANDLER_NAME_TO_PARAMETER_COUNT = {
|
32
|
+
:exit_handler => 1,
|
33
|
+
:pid_handler => 1,
|
34
|
+
:size_limit_handler => 0,
|
35
|
+
:stderr_handler => 1,
|
36
|
+
:stdout_handler => 1,
|
37
|
+
:timeout_handler => 0,
|
38
|
+
:watch_handler => 1,
|
39
|
+
}
|
40
|
+
|
41
|
+
def initialize(options = {})
|
42
|
+
if options[:target].nil? &&
|
43
|
+
!(options.keys & HANDLER_NAME_TO_PARAMETER_COUNT.keys).empty?
|
44
|
+
raise ArgumentError, "Missing target"
|
45
|
+
end
|
46
|
+
@target = options[:target] # hold target reference (if any) against GC
|
47
|
+
|
48
|
+
# define an instance method for each handler that either proxies
|
49
|
+
# directly to the target method (with parameters) or else does nothing.
|
50
|
+
HANDLER_NAME_TO_PARAMETER_COUNT.each do |handler_name, parameter_count|
|
51
|
+
parameter_list = (1..parameter_count).map { |i| "p#{i}" }.join(', ')
|
52
|
+
instance_eval <<EOF
|
53
|
+
if @target && options[#{handler_name.inspect}]
|
54
|
+
@#{handler_name.to_s}_method = @target.method(options[#{handler_name.inspect}])
|
55
|
+
def #{handler_name.to_s}(#{parameter_list})
|
56
|
+
@#{handler_name.to_s}_method.call(#{parameter_list})
|
57
|
+
end
|
58
|
+
else
|
59
|
+
def #{handler_name.to_s}(#{parameter_list}); true; end
|
60
|
+
end
|
61
|
+
EOF
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end # RightPopen
|
67
|
+
end # RightScale
|
data/lib/right_popen/version.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#-- -*- mode: ruby; encoding: utf-8 -*-
|
2
|
-
# Copyright: Copyright (c) 2011 RightScale, Inc.
|
2
|
+
# Copyright: Copyright (c) 2011-2013 RightScale, Inc.
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining
|
5
5
|
# a copy of this software and associated documentation files (the
|
@@ -23,6 +23,6 @@
|
|
23
23
|
|
24
24
|
module RightScale
|
25
25
|
module RightPopen
|
26
|
-
VERSION = "1.
|
26
|
+
VERSION = "1.1.3"
|
27
27
|
end
|
28
28
|
end
|
@@ -0,0 +1,258 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009-2013 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
|
+
require 'rubygems'
|
25
|
+
require 'eventmachine'
|
26
|
+
|
27
|
+
require ::File.expand_path(::File.join(::File.dirname(__FILE__), 'process'))
|
28
|
+
|
29
|
+
module RightScale::RightPopen
|
30
|
+
|
31
|
+
# ensure uniqueness of handler to avoid confusion.
|
32
|
+
raise "#{StdInHandler.name} is already defined" if defined?(StdInHandler)
|
33
|
+
|
34
|
+
# Eventmachine callback handler for stdin stream
|
35
|
+
module StdInHandler
|
36
|
+
|
37
|
+
# === Parameters
|
38
|
+
# options[:input](String):: Input to be streamed into child process stdin
|
39
|
+
# stream_in(IO):: Standard input stream.
|
40
|
+
def initialize(options, stream_in)
|
41
|
+
@stream_in = stream_in
|
42
|
+
@input = options[:input]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Eventmachine callback asking for more to write
|
46
|
+
# Send input and close stream in
|
47
|
+
def post_init
|
48
|
+
if @input
|
49
|
+
send_data(@input)
|
50
|
+
close_connection_after_writing
|
51
|
+
@input = nil
|
52
|
+
else
|
53
|
+
close_connection
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
# ensure uniqueness of handler to avoid confusion.
|
60
|
+
raise "#{StdOutHandler.name} is already defined" if defined?(StdOutHandler)
|
61
|
+
|
62
|
+
# Provides an eventmachine callback handler for the stdout stream.
|
63
|
+
module StdOutHandler
|
64
|
+
|
65
|
+
# === Parameters
|
66
|
+
# @param [Process] process that was executed
|
67
|
+
# @param [Object] target defining handler methods to be called
|
68
|
+
# @param [Connector] stderr_eventable EM object representing stderr handler.
|
69
|
+
# @param [IO] stream_out as standard output stream
|
70
|
+
def initialize(process, target, stderr_eventable, stream_out)
|
71
|
+
@process = process
|
72
|
+
@target = target
|
73
|
+
@stderr_eventable = stderr_eventable
|
74
|
+
@stream_out = stream_out
|
75
|
+
@status = nil
|
76
|
+
end
|
77
|
+
|
78
|
+
# Callback from EM to asynchronously read the stdout stream. Note that this
|
79
|
+
# callback mechanism is deprecated after EM v0.12.8 but the win32 EM code
|
80
|
+
# has never advanced beyond that point.
|
81
|
+
def notify_readable
|
82
|
+
data = ::RightScale::RightPopen.async_read(@stream_out)
|
83
|
+
receive_data(data) if (data && data.length > 0)
|
84
|
+
detach unless data
|
85
|
+
end
|
86
|
+
|
87
|
+
# Callback from EM to receive data, which we also use to handle the
|
88
|
+
# asynchronous data we read ourselves.
|
89
|
+
def receive_data(data)
|
90
|
+
@target.stdout_handler(data)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Override of Connection.get_status() for Windows implementation.
|
94
|
+
def get_status
|
95
|
+
unless @status
|
96
|
+
@status = @process.wait_for_exit_status
|
97
|
+
end
|
98
|
+
return @status
|
99
|
+
end
|
100
|
+
|
101
|
+
# Callback from EM to unbind.
|
102
|
+
def unbind
|
103
|
+
# We force the stderr watched handler to go away so that
|
104
|
+
# we don't end up with a broken pipe
|
105
|
+
@stderr_eventable.force_detach if @stderr_eventable
|
106
|
+
@target.timeout_handler if @process.timer_expired?
|
107
|
+
@target.size_limit_handler if @process.size_limit_exceeded?
|
108
|
+
@target.exit_handler(get_status)
|
109
|
+
@stream_out.close
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# ensure uniqueness of handler to avoid confusion.
|
114
|
+
raise "#{StdErrHandler.name} is already defined" if defined?(StdErrHandler)
|
115
|
+
|
116
|
+
# Provides an eventmachine callback handler for the stderr stream.
|
117
|
+
module StdErrHandler
|
118
|
+
|
119
|
+
# === Parameters
|
120
|
+
# @param [Object] target defining handler methods to be called
|
121
|
+
# @param [IO] stream_err as standard error stream
|
122
|
+
def initialize(target, stream_err)
|
123
|
+
@target = target
|
124
|
+
@stderr_handler = target.method(:stderr_handler)
|
125
|
+
@stream_err = stream_err
|
126
|
+
@unbound = false
|
127
|
+
end
|
128
|
+
|
129
|
+
# Callback from EM to asynchronously read the stderr stream. Note that this
|
130
|
+
# callback mechanism is deprecated after EM v0.12.8
|
131
|
+
def notify_readable
|
132
|
+
# call native win32 implementation for async_read
|
133
|
+
data = ::RightScale::RightPopen.async_read(@stream_err)
|
134
|
+
receive_data(data) if (data && data.length > 0)
|
135
|
+
detach unless data
|
136
|
+
end
|
137
|
+
|
138
|
+
# Callback from EM to receive data, which we also use to handle the
|
139
|
+
# asynchronous data we read ourselves.
|
140
|
+
def receive_data(data)
|
141
|
+
@stderr_handler.call(data)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Callback from EM to unbind.
|
145
|
+
def unbind
|
146
|
+
@unbound = true
|
147
|
+
@stream_err.close
|
148
|
+
end
|
149
|
+
|
150
|
+
# Forces detachment of the stderr handler on EM's next tick.
|
151
|
+
def force_detach
|
152
|
+
# Use next tick to prevent issue in EM where descriptors list
|
153
|
+
# gets out-of-sync when calling detach in an unbind callback
|
154
|
+
::EM.next_tick { detach unless @unbound }
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# See RightScale.popen3_async for details
|
159
|
+
def self.popen3_async_impl(cmd, target, options)
|
160
|
+
# always create eventables on the main EM thread by using next_tick. this
|
161
|
+
# prevents synchronization problems between EM threads.
|
162
|
+
::EM.next_tick do
|
163
|
+
process = nil
|
164
|
+
begin
|
165
|
+
# create process.
|
166
|
+
process = ::RightScale::RightPopen::Process.new(options)
|
167
|
+
process.spawn(cmd, target)
|
168
|
+
|
169
|
+
# close input immediately unless streaming. the EM implementation is
|
170
|
+
# flawed so it is important to not create an eventable for the input
|
171
|
+
# stream unless required. the issue stems from EM thinking that file
|
172
|
+
# handles and socket handles come from the same pool in the stdio
|
173
|
+
# libraries; in Linux they come from the same pool, in Windows they don't.
|
174
|
+
process.stdin.close unless options[:input]
|
175
|
+
|
176
|
+
# attach handlers to event machine and let it monitor incoming data. the
|
177
|
+
# streams aren't used directly by the connectors except that they are
|
178
|
+
# closed on unbind.
|
179
|
+
stderr_eventable = ::EM.watch(process.stderr, ::RightScale::RightPopen::StdErrHandler, target, process.stderr) do |c|
|
180
|
+
c.notify_readable = true
|
181
|
+
end
|
182
|
+
::EM.watch(process.stdout, ::RightScale::RightPopen::StdOutHandler, process, target, stderr_eventable, process.stdout) do |c|
|
183
|
+
c.notify_readable = true
|
184
|
+
target.pid_handler(process.pid)
|
185
|
+
|
186
|
+
# initial watch callback.
|
187
|
+
#
|
188
|
+
# note that we cannot abandon async watch; callback needs to interrupt
|
189
|
+
# in this case
|
190
|
+
target.watch_handler(process)
|
191
|
+
end
|
192
|
+
if options[:input]
|
193
|
+
::EM.attach(process.stdin, ::RightScale::RightPopen::StdInHandler, options, process.stdin)
|
194
|
+
end
|
195
|
+
|
196
|
+
# create a periodic watcher only if needed in the win32 async case because
|
197
|
+
# the exit handler is tied to EM eventable detachment.
|
198
|
+
#
|
199
|
+
# TEAL FIX: does this logic need to differ from Linux or can they share
|
200
|
+
# the same code? they probably differ because the mswin32 implementation
|
201
|
+
# of EM stopped many versions back.
|
202
|
+
if process.needs_watching?
|
203
|
+
::EM.next_tick do
|
204
|
+
watch_process(process, 0.1, target)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
rescue
|
208
|
+
# we can't raise from the main EM thread or it will stop EM.
|
209
|
+
# the spawn method will signal the exit handler but not the
|
210
|
+
# pid handler in this case since there is no pid. any action
|
211
|
+
# (logging, etc.) associated with the failure will have to be
|
212
|
+
# driven by the exit handler.
|
213
|
+
target.exit_handler(process.status) rescue nil if target && process
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# note that control returns to the caller, but the launched cmd continues
|
218
|
+
# running and sends output to the handlers. the caller is not responsible
|
219
|
+
# for waiting for the process to terminate or closing streams as the
|
220
|
+
# watched eventables will handle this automagically. notification will be
|
221
|
+
# sent to the exit_handler on process termination.
|
222
|
+
true
|
223
|
+
end
|
224
|
+
|
225
|
+
# watches process for interrupt criteria. doubles the wait time up to a
|
226
|
+
# maximum of 1 second for next wait.
|
227
|
+
#
|
228
|
+
# === Parameters
|
229
|
+
# @param [Process] process that was run
|
230
|
+
# @param [Numeric] wait_time as seconds to wait before checking status
|
231
|
+
# @param [Object] target for handler calls
|
232
|
+
#
|
233
|
+
# === Return
|
234
|
+
# true:: Always return true
|
235
|
+
def self.watch_process(process, wait_time, target)
|
236
|
+
::EM::Timer.new(wait_time) do
|
237
|
+
begin
|
238
|
+
if process.alive?
|
239
|
+
if process.timer_expired? || process.size_limit_exceeded?
|
240
|
+
process.interrupt
|
241
|
+
else
|
242
|
+
# cannot abandon async watch; callback needs to interrupt in this case
|
243
|
+
target.watch_handler(process)
|
244
|
+
end
|
245
|
+
watch_process(process, [wait_time * 2, 1].min, target)
|
246
|
+
end
|
247
|
+
rescue
|
248
|
+
# we can't raise from the main EM thread or it will stop EM.
|
249
|
+
# the spawn method will signal the exit handler but not the
|
250
|
+
# pid handler in this case since there is no pid. any action
|
251
|
+
# (logging, etc.) associated with the failure will have to be
|
252
|
+
# driven by the exit handler.
|
253
|
+
end
|
254
|
+
end
|
255
|
+
true
|
256
|
+
end
|
257
|
+
|
258
|
+
end
|