mssh 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/mssh +1 -1
- metadata +4 -6
- data/bin/mcmd-ev +0 -97
- data/lib/mcmd-ev.rb +0 -390
data/bin/mssh
CHANGED
@@ -56,7 +56,7 @@ if (!options[:range].nil?)
|
|
56
56
|
end
|
57
57
|
if (!options[:file].nil?)
|
58
58
|
targets_fd = File.open(options[:file])
|
59
|
-
targets_fd.read.
|
59
|
+
targets_fd.read.each_line { |x| targets << x.chomp }
|
60
60
|
end
|
61
61
|
if (!options[:hostlist].nil?)
|
62
62
|
targets.push *options[:hostlist]
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mssh
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 19
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 6
|
10
|
+
version: 0.0.6
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Evan Miller
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-
|
18
|
+
date: 2012-10-02 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: json
|
@@ -42,10 +42,8 @@ extensions: []
|
|
42
42
|
extra_rdoc_files:
|
43
43
|
- LICENSE.md
|
44
44
|
files:
|
45
|
-
- lib/mcmd-ev.rb
|
46
45
|
- lib/mcmd.rb
|
47
46
|
- bin/mcmd
|
48
|
-
- bin/mcmd-ev
|
49
47
|
- bin/mssh
|
50
48
|
- README.md
|
51
49
|
- LICENSE.md
|
data/bin/mcmd-ev
DELETED
@@ -1,97 +0,0 @@
|
|
1
|
-
#!/usr/bin/ruby
|
2
|
-
|
3
|
-
require 'pp'
|
4
|
-
|
5
|
-
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
|
6
|
-
require 'mcmd-ev'
|
7
|
-
|
8
|
-
require 'optparse'
|
9
|
-
options = {
|
10
|
-
:maxflight => 200,
|
11
|
-
:timeout => 60,
|
12
|
-
:global_timeout => 0,
|
13
|
-
}
|
14
|
-
|
15
|
-
optparse = OptionParser.new do |opts|
|
16
|
-
opts.on('-r', '--range RANGE', 'currently takes a CSV list') do |arg|
|
17
|
-
options[:range] = arg
|
18
|
-
end
|
19
|
-
opts.on('-m', '--maxflight 50', 'How many subprocesses? 50 by default') do |arg|
|
20
|
-
options[:maxflight] = arg
|
21
|
-
end
|
22
|
-
opts.on('-t', '--timeout 60', 'How many seconds may each individual process take? 0 for no timeout') do |arg|
|
23
|
-
options[:timeout] = arg
|
24
|
-
end
|
25
|
-
opts.on('-g', '--global_timeout 600', 'How many seconds for the whole shebang 0 for no timeout') do |arg|
|
26
|
-
options[:global_timeout] = arg
|
27
|
-
end
|
28
|
-
opts.on('--noshell', "Don't invoke a shell. Args will be passed to exec verbatim ") do |arg|
|
29
|
-
options[:noshell] = arg
|
30
|
-
end
|
31
|
-
opts.on('-c', '--collapse', "Collapse similar output ") do |arg|
|
32
|
-
options[:collapse] = arg
|
33
|
-
end
|
34
|
-
opts.on('-v', '--verbose', "Verbose output") do |arg|
|
35
|
-
options[:verbose] = arg
|
36
|
-
end
|
37
|
-
opts.on('-d', '--debug', "Debug output") do |arg|
|
38
|
-
options[:debug] = arg
|
39
|
-
end
|
40
|
-
# option to merge stdin/stdout into one buf? how should this work?
|
41
|
-
# option to ignore as-we-go yield output - this is off by default now except for success/fail
|
42
|
-
end
|
43
|
-
optparse.parse!
|
44
|
-
|
45
|
-
raise "Error, need -r argument" if options[:range].nil? or options[:range].empty?
|
46
|
-
raise "Error, need command to run" if ARGV.size.zero?
|
47
|
-
|
48
|
-
m = MultipleCmd.new
|
49
|
-
|
50
|
-
targets = options[:range].split ","
|
51
|
-
|
52
|
-
m.commands = targets.map { |t| ["/bin/sh", "-c"].push ARGV.map { |arg| arg.gsub('HOSTNAME', t)}.join " " }
|
53
|
-
command_to_target = Hash.new
|
54
|
-
targets.size.times do |i|
|
55
|
-
command_to_target[m.commands[i].object_id] = targets[i]
|
56
|
-
end
|
57
|
-
m.yield_startcmd = lambda { |p| puts "#{command_to_target[p.command.object_id]}: starting" } if options[:verbose]
|
58
|
-
m.yield_wait = lambda { |p| puts "#{p.success? ? 'SUCCESS' : 'FAILURE'} #{command_to_target[p.command.object_id]}: '#{p.stdout_buf}'" }
|
59
|
-
# m.yield_proc_timeout = lambda { |p| puts "am killing #{p.inspect}"}
|
60
|
-
|
61
|
-
m.perchild_timeout = options[:timeout].to_i
|
62
|
-
m.global_timeout = options[:global_timeout].to_i
|
63
|
-
m.maxflight = options[:maxflight].to_i
|
64
|
-
m.verbose = options[:verbose]
|
65
|
-
m.debug = options[:debug]
|
66
|
-
|
67
|
-
result = m.run
|
68
|
-
|
69
|
-
if options[:collapse]
|
70
|
-
# print a collapsed summary
|
71
|
-
stdout_matches_success = Hash.new
|
72
|
-
stdout_matches_failure = Hash.new
|
73
|
-
result.each do |r|
|
74
|
-
if r[:retval].success?
|
75
|
-
stdout_matches_success[r[:stdout_buf]] = [] if stdout_matches_success[r[:stdout_buf]].nil?
|
76
|
-
stdout_matches_success[r[:stdout_buf]] << command_to_target[r[:command].object_id]
|
77
|
-
else
|
78
|
-
stdout_matches_failure[r[:stdout_buf]] = [] if stdout_matches_failure[r[:stdout_buf]].nil?
|
79
|
-
stdout_matches_failure[r[:stdout_buf]] << command_to_target[r[:command].object_id]
|
80
|
-
end
|
81
|
-
end
|
82
|
-
# output => [targets ...]
|
83
|
-
stdout_matches_success.each_pair do |k,v|
|
84
|
-
puts "SUCCESS: #{v.join ','}: #{k}"
|
85
|
-
end
|
86
|
-
stdout_matches_failure.each_pair do |k,v|
|
87
|
-
puts "FAILURE: #{v.join ','}: #{k}"
|
88
|
-
end
|
89
|
-
else
|
90
|
-
# we already printed while in-flight; do nothing
|
91
|
-
# not collapse, print one per host
|
92
|
-
# result.each do |r|
|
93
|
-
# target = command_to_target[r[:command].object_id]
|
94
|
-
# puts "#{target}: '#{r[:stdout_buf].chomp}'\n"
|
95
|
-
# end
|
96
|
-
end
|
97
|
-
|
data/lib/mcmd-ev.rb
DELETED
@@ -1,390 +0,0 @@
|
|
1
|
-
#!/usr/bin/ruby
|
2
|
-
|
3
|
-
require 'pp'
|
4
|
-
require 'rubygems'
|
5
|
-
require 'eventmachine'
|
6
|
-
|
7
|
-
|
8
|
-
# TODO - figure out why IO.sysread isn't raising on EOF
|
9
|
-
module FdWatcher
|
10
|
-
def notify_readable
|
11
|
-
puts "readable: '#{@io.inspect}'"
|
12
|
-
puts "mcmd obj: '#{@mcmd}'"
|
13
|
-
# read available bytes, add to the subproc's read buf
|
14
|
-
if not @mcmd.subproc_by_fd.has_key?(@io)
|
15
|
-
raise "Select returned a fd which I have not seen! fd: #{@io.inspect}"
|
16
|
-
end
|
17
|
-
subproc = @mcmd.subproc_by_fd[@io]
|
18
|
-
buf = ""
|
19
|
-
begin
|
20
|
-
buf = @io.sysread(4096)
|
21
|
-
puts "saw #{buf}"
|
22
|
-
if buf.nil?
|
23
|
-
raise " Impossible result from sysread()"
|
24
|
-
end
|
25
|
-
# no exception? bytes were read. append them.
|
26
|
-
if @io == subproc.stdout_fd
|
27
|
-
subproc.stdout_buf << buf
|
28
|
-
# FIXME if we've read > maxbuf, allow closing/ignoring the fd instead of hard kill
|
29
|
-
if subproc.stdout_buf.bytesize > @mcmd.max_read_size
|
30
|
-
# self.kill_process(subproc) # can't kill this here, need a way to mark-to-kill
|
31
|
-
end
|
32
|
-
elsif @io == subproc.stderr_fd
|
33
|
-
subproc.stderr_buf << buf
|
34
|
-
# FIXME if we've read > maxbuf, allow closing/ignoring the fd instead of hard kill
|
35
|
-
if subproc.stderr_buf.bytesize > @mcmd.max_read_size
|
36
|
-
# self.kill_process(subproc) # "" above
|
37
|
-
end
|
38
|
-
end
|
39
|
-
rescue SystemCallError, EOFError => ex
|
40
|
-
puts "DEBUG: saw read exception #{ex}"
|
41
|
-
@mcmd.periodic
|
42
|
-
rescue => ex
|
43
|
-
puts "else #{ex.inspect}"
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def unbind
|
48
|
-
puts "GOT UNBIND"
|
49
|
-
@mcmd.periodic
|
50
|
-
# move periodic() bits under here, to be called as necessary
|
51
|
-
end
|
52
|
-
def notify_writable
|
53
|
-
# puts "writable: '#{@io.inspect}'"
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
class FdClass < EventMachine::Connection
|
58
|
-
include FdWatcher
|
59
|
-
end
|
60
|
-
|
61
|
-
module ProcessWatcher
|
62
|
-
def process_exited
|
63
|
-
puts 'the forked child died!'
|
64
|
-
EM.stop_event_loop
|
65
|
-
puts "stopped"
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
|
70
|
-
class MultipleCmd
|
71
|
-
|
72
|
-
attr_accessor :global_timeout, :maxflight, :perchild_timeout, :commands
|
73
|
-
attr_accessor :yield_wait, :yield_startcmd, :debug, :yield_proc_timeout
|
74
|
-
attr_accessor :verbose, :poll_period, :max_read_size
|
75
|
-
attr_accessor :subproc_by_fd
|
76
|
-
|
77
|
-
def initialize
|
78
|
-
# these are re-initialized after every run
|
79
|
-
@subproc_by_pid = Hash.new
|
80
|
-
@subproc_by_fd = Hash.new
|
81
|
-
@processed_commands = []
|
82
|
-
# end items which are re-initialized
|
83
|
-
|
84
|
-
self.commands = []
|
85
|
-
self.perchild_timeout = 60
|
86
|
-
self.global_timeout = 0
|
87
|
-
self.maxflight = 200
|
88
|
-
self.debug = false
|
89
|
-
self.poll_period = 0.5 # shouldn't need adjusting
|
90
|
-
self.max_read_size = 2 ** 19 # 512k
|
91
|
-
end
|
92
|
-
|
93
|
-
def noshell_exec(cmd)
|
94
|
-
if cmd.length == 1
|
95
|
-
Kernel.exec([cmd[0], cmd[0]])
|
96
|
-
else
|
97
|
-
Kernel.exec([cmd[0], cmd[0]], *cmd[1..-1])
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
# I should probably move this whole method
|
102
|
-
# into SubProc and make the subproc_by_* into
|
103
|
-
# class variables
|
104
|
-
def add_subprocess(cmd)
|
105
|
-
stdin_rd, stdin_wr = IO.pipe
|
106
|
-
stdout_rd, stdout_wr = IO.pipe
|
107
|
-
stderr_rd, stderr_wr = IO.pipe
|
108
|
-
subproc = MultipleCmd::SubProc.new
|
109
|
-
subproc.stdin_fd = stdin_wr
|
110
|
-
subproc.stdout_fd = stdout_rd
|
111
|
-
subproc.stderr_fd = stderr_rd
|
112
|
-
subproc.command = cmd
|
113
|
-
|
114
|
-
puts "Before fork"
|
115
|
-
pid = fork
|
116
|
-
if not pid.nil?
|
117
|
-
# parent
|
118
|
-
stdin_rd.close rescue true
|
119
|
-
stdout_wr.close rescue true
|
120
|
-
stderr_wr.close rescue true
|
121
|
-
# for mapping to subproc by pid
|
122
|
-
subproc.pid = pid
|
123
|
-
@subproc_by_pid[pid] = subproc
|
124
|
-
# for mapping to subproc by i/o handle (returned from select)
|
125
|
-
@subproc_by_fd[stdin_rd] = subproc
|
126
|
-
@subproc_by_fd[stdin_wr] = subproc
|
127
|
-
@subproc_by_fd[stdout_rd] = subproc
|
128
|
-
@subproc_by_fd[stdout_wr] = subproc
|
129
|
-
@subproc_by_fd[stderr_rd] = subproc
|
130
|
-
@subproc_by_fd[stderr_wr] = subproc
|
131
|
-
|
132
|
-
self.yield_startcmd.call(subproc) unless self.yield_startcmd.nil?
|
133
|
-
temp_self = self
|
134
|
-
# now add fds to EM
|
135
|
-
EM.watch(stdin_wr, FdClass) { |c|
|
136
|
-
c.notify_writable = true
|
137
|
-
c.instance_eval do
|
138
|
-
@mcmd = temp_self
|
139
|
-
end
|
140
|
-
}
|
141
|
-
EM.watch(stdout_rd, FdClass) { |c|
|
142
|
-
c.notify_readable = true
|
143
|
-
c.instance_eval do
|
144
|
-
@mcmd = temp_self
|
145
|
-
end
|
146
|
-
}
|
147
|
-
EM.watch(stderr_rd, FdClass) { |c|
|
148
|
-
c.notify_readable = true
|
149
|
-
c.instance_eval do
|
150
|
-
@mcmd = temp_self
|
151
|
-
end
|
152
|
-
}
|
153
|
-
# EM.watch_process(pid, ProcessWatcher) # is this kqueue only? If so, probably don't need it FIXME
|
154
|
-
else
|
155
|
-
# child
|
156
|
-
# setup stdin, out, err
|
157
|
-
stdin_wr.close rescue true
|
158
|
-
stdout_rd.close rescue true
|
159
|
-
stderr_rd.close rescue true
|
160
|
-
STDIN.reopen(stdin_rd)
|
161
|
-
STDOUT.reopen(stdout_wr)
|
162
|
-
STDERR.reopen(stderr_wr)
|
163
|
-
puts "EXEC: #{cmd}"
|
164
|
-
noshell_exec(cmd)
|
165
|
-
raise "can't be reached!!. exec failed!!"
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
def process_read_fds(read_fds)
|
170
|
-
read_fds.each do |fd|
|
171
|
-
begin
|
172
|
-
buf = fd.sysread(4096)
|
173
|
-
|
174
|
-
if buf.nil?
|
175
|
-
raise " Impossible result from sysread()"
|
176
|
-
end
|
177
|
-
# no exception? bytes were read. append them.
|
178
|
-
if fd == subproc.stdout_fd
|
179
|
-
subproc.stdout_buf << buf
|
180
|
-
# FIXME if we've read > maxbuf, allow closing/ignoring the fd instead of hard kill
|
181
|
-
if subproc.stdout_buf.bytesize > self.max_read_size
|
182
|
-
# self.kill_process(subproc) # can't kill this here, need a way to mark-to-kill
|
183
|
-
end
|
184
|
-
elsif fd == subproc.stderr_fd
|
185
|
-
subproc.stderr_buf << buf
|
186
|
-
# FIXME if we've read > maxbuf, allow closing/ignoring the fd instead of hard kill
|
187
|
-
if subproc.stderr_buf.bytesize > self.max_read_size
|
188
|
-
# self.kill_process(subproc) # "" above
|
189
|
-
end
|
190
|
-
end
|
191
|
-
rescue SystemCallError, EOFError => ex
|
192
|
-
puts "DEBUG: saw read exception #{ex}" if self.debug
|
193
|
-
detach
|
194
|
-
# clear out the read fd for this subproc
|
195
|
-
# finalize read i/o
|
196
|
-
# if we're reading, it was the process's stdout or stderr
|
197
|
-
if fd == subproc.stdout_fd
|
198
|
-
subproc.stdout_fd = nil
|
199
|
-
elsif fd == subproc.stderr_fd
|
200
|
-
subproc.stderr_fd = nil
|
201
|
-
else
|
202
|
-
raise "impossible: operating on a subproc where the fd isn't found, even though it's mapped"
|
203
|
-
end
|
204
|
-
fd.close rescue true
|
205
|
-
end
|
206
|
-
end
|
207
|
-
end # process_read_fds()
|
208
|
-
def process_write_fds(write_fds)
|
209
|
-
write_fds.each do |fd|
|
210
|
-
raise "working on an unknown fd #{fd}" unless @subproc_by_fd.has_key?(fd)
|
211
|
-
subproc = @subproc_by_fd[fd]
|
212
|
-
buf = ""
|
213
|
-
# add writing here, todo. not core feature
|
214
|
-
end
|
215
|
-
end
|
216
|
-
def process_err_fds(err_fds)
|
217
|
-
end
|
218
|
-
|
219
|
-
# iterate and service fds in child procs, collect data and status
|
220
|
-
def service_subprocess_io
|
221
|
-
write_fds = @subproc_by_pid.values.select {|x| not x.stdin_fd.nil? and not x.terminated}.map {|x| x.stdin_fd}
|
222
|
-
read_fds = @subproc_by_pid.values.select {|x| not x.terminated}.map {|x| [x.stdout_fd, x.stderr_fd].select {|x| not x.nil? } }.flatten
|
223
|
-
|
224
|
-
read_fds, write_fds, err_fds = IO.select(read_fds, write_fds, nil, self.poll_period)
|
225
|
-
|
226
|
-
self.process_read_fds(read_fds) unless read_fds.nil?
|
227
|
-
self.process_write_fds(write_fds) unless write_fds.nil?
|
228
|
-
self.process_err_fds(err_fds) unless err_fds.nil?
|
229
|
-
# errors?
|
230
|
-
end
|
231
|
-
|
232
|
-
def process_timeouts
|
233
|
-
now = Time.now.to_i
|
234
|
-
@subproc_by_pid.values.each do |p|
|
235
|
-
if (now - p.time_start) > self.perchild_timeout
|
236
|
-
# expire this child process
|
237
|
-
|
238
|
-
self.yield_proc_timeout.call(p) unless self.yield_proc_timeout.nil?
|
239
|
-
self.kill_process(p)
|
240
|
-
end
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
def kill_process(p)
|
245
|
-
# do not remove from pid list until waited on
|
246
|
-
@subproc_by_fd.delete(p.stdin_fd)
|
247
|
-
@subproc_by_fd.delete(p.stdout_fd)
|
248
|
-
@subproc_by_fd.delete(p.stderr_fd)
|
249
|
-
# must kill after deleting from maps
|
250
|
-
# kill closes fds
|
251
|
-
p.kill
|
252
|
-
end
|
253
|
-
|
254
|
-
def periodic
|
255
|
-
puts "in periodic"
|
256
|
-
self.wait
|
257
|
-
# This could probably move into the child-cleanup callback
|
258
|
-
# start up as many as maxflight processes
|
259
|
-
while @subproc_by_pid.length < self.maxflight and not @commands.empty?
|
260
|
-
# take one from @commands and start it
|
261
|
-
commands = @commands.shift
|
262
|
-
self.add_subprocess(commands)
|
263
|
-
end
|
264
|
-
|
265
|
-
puts "have #{@subproc_by_pid.length} left to go"
|
266
|
-
# if we have nothing in flight (active pid)
|
267
|
-
# and nothing pending on the input list
|
268
|
-
# then we're done
|
269
|
-
if @subproc_by_pid.length.zero? and @commands.empty?
|
270
|
-
done = true
|
271
|
-
puts "stopping ev loop"
|
272
|
-
EM.stop_event_loop
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
|
-
def run
|
277
|
-
@global_time_start = Time.now.to_i
|
278
|
-
done = false
|
279
|
-
EM.run do
|
280
|
-
EM.schedule do
|
281
|
-
puts "schedule!"
|
282
|
-
self.periodic
|
283
|
-
end
|
284
|
-
# EM.add_periodic_timer(0.5) do
|
285
|
-
# puts "TIMER!!"
|
286
|
-
# self.periodic
|
287
|
-
# end # end periodic timer
|
288
|
-
end
|
289
|
-
|
290
|
-
## These things get broken up into individual proc/fd callbacks
|
291
|
-
# service running processes
|
292
|
-
# self.service_subprocess_io
|
293
|
-
# timeout overdue processes
|
294
|
-
# self.process_timeouts
|
295
|
-
# service process cleanup
|
296
|
-
|
297
|
-
## this is still the same, post-event-loop
|
298
|
-
data = self.return_rundata
|
299
|
-
# these are re-initialized after every run
|
300
|
-
@subproc_by_pid = Hash.new
|
301
|
-
@subproc_by_fd = Hash.new
|
302
|
-
@processed_commands = []
|
303
|
-
# end items which are re-initialized
|
304
|
-
return data
|
305
|
-
end
|
306
|
-
|
307
|
-
def return_rundata
|
308
|
-
data = []
|
309
|
-
@processed_commands.each do |c|
|
310
|
-
#FIXME pass through the process object
|
311
|
-
data << {
|
312
|
-
:pid => c.pid,
|
313
|
-
:write_buf_position => c.write_buf_position,
|
314
|
-
:stdout_buf => c.stdout_buf,
|
315
|
-
:stderr_buf => c.stderr_buf,
|
316
|
-
:command => c.command,
|
317
|
-
:time_start => c.time_start,
|
318
|
-
:time_end => c.time_end,
|
319
|
-
:retval => c.retval,
|
320
|
-
}
|
321
|
-
end
|
322
|
-
return data
|
323
|
-
end
|
324
|
-
|
325
|
-
def wait
|
326
|
-
possible_children = true
|
327
|
-
just_reaped = Array.new
|
328
|
-
while possible_children
|
329
|
-
begin
|
330
|
-
pid = Process::waitpid(-1, Process::WNOHANG)
|
331
|
-
if pid.nil?
|
332
|
-
possible_children = false
|
333
|
-
else
|
334
|
-
# pid is now gone. remove from subproc_by_pid and
|
335
|
-
# add to the processed commands list
|
336
|
-
p = @subproc_by_pid[pid]
|
337
|
-
p.time_end = Time.now.to_i
|
338
|
-
p.retval = $?
|
339
|
-
@subproc_by_pid.delete(pid)
|
340
|
-
@processed_commands << p
|
341
|
-
just_reaped << p
|
342
|
-
end
|
343
|
-
rescue Errno::ECHILD => ex
|
344
|
-
# ECHILD. ignore.
|
345
|
-
possible_children = false
|
346
|
-
end
|
347
|
-
end
|
348
|
-
# We may have waited on a child before reading all its output. Collect those missing bits. No blocking.
|
349
|
-
if not just_reaped.empty?
|
350
|
-
read_fds = just_reaped.select {|x| not x.terminated}.map {|x| [x.stdout_fd, x.stderr_fd].select {|x| not x.nil? } }.flatten
|
351
|
-
read_fds, write_fds, err_fds = IO.select(read_fds, nil, nil, 0)
|
352
|
-
self.process_read_fds(read_fds) unless read_fds.nil?
|
353
|
-
end
|
354
|
-
just_reaped.each do |p|
|
355
|
-
self.yield_wait.call(p) unless self.yield_wait.nil?
|
356
|
-
end
|
357
|
-
end
|
358
|
-
|
359
|
-
end
|
360
|
-
|
361
|
-
class MultipleCmd::SubProc
|
362
|
-
attr_accessor :stdin_fd, :stdout_fd, :stderr_fd, :write_buf_position
|
363
|
-
attr_accessor :time_start, :time_end, :pid, :retval, :stdout_buf, :stderr_buf, :command, :terminated
|
364
|
-
|
365
|
-
def initialize
|
366
|
-
self.write_buf_position = 0
|
367
|
-
self.time_start = Time.now.to_i
|
368
|
-
self.stdout_buf = ""
|
369
|
-
self.stderr_buf = ""
|
370
|
-
self.terminated = false
|
371
|
-
end
|
372
|
-
|
373
|
-
# when a process has out-stayed its welcome
|
374
|
-
def kill
|
375
|
-
self.stdin_fd.close rescue true
|
376
|
-
self.stdout_fd.close rescue true
|
377
|
-
self.stderr_fd.close rescue true
|
378
|
-
#TODO configurable sig?
|
379
|
-
Process::kill("KILL", self.pid)
|
380
|
-
self.terminated = true
|
381
|
-
end
|
382
|
-
|
383
|
-
|
384
|
-
# some heuristic to determine if this job was successful
|
385
|
-
# for now, trust retval. Also check stderr?
|
386
|
-
def success?
|
387
|
-
self.retval.success?
|
388
|
-
end
|
389
|
-
end
|
390
|
-
|