right_popen 1.0.21-x86-mswin32-60 → 1.1.3-x86-mswin32-60
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/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
|