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.
@@ -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
@@ -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.0.21"
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