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