rbbt-util 5.13.1 → 5.13.2
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.
- checksums.yaml +4 -4
- data/lib/rbbt/persist.rb +10 -35
- data/lib/rbbt/tsv/parallel/traverse.rb +118 -78
- data/lib/rbbt/tsv/parser.rb +6 -4
- data/lib/rbbt/tsv/stream.rb +107 -2
- data/lib/rbbt/tsv/util.rb +7 -15
- data/lib/rbbt/util/concurrency/processes.rb +16 -17
- data/lib/rbbt/util/concurrency/processes/worker.rb +5 -3
- data/lib/rbbt/util/log.rb +2 -1
- data/lib/rbbt/util/log/progress.rb +46 -20
- data/lib/rbbt/util/misc/exceptions.rb +11 -1
- data/lib/rbbt/util/misc/inspect.rb +1 -1
- data/lib/rbbt/util/misc/lock.rb +1 -1
- data/lib/rbbt/util/misc/pipes.rb +66 -25
- data/lib/rbbt/util/semaphore.rb +3 -3
- data/lib/rbbt/workflow/accessor.rb +34 -12
- data/lib/rbbt/workflow/definition.rb +7 -3
- data/lib/rbbt/workflow/step.rb +4 -3
- data/lib/rbbt/workflow/step/run.rb +104 -30
- data/lib/rbbt/workflow/usage.rb +1 -1
- data/share/rbbt_commands/workflow/task +11 -1
- data/test/rbbt/tsv/parallel/test_traverse.rb +31 -34
- data/test/rbbt/tsv/test_filter.rb +6 -6
- data/test/rbbt/util/concurrency/test_processes.rb +0 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52c2b4f2b6f065da491646388575b22ffccddee4
|
4
|
+
data.tar.gz: 48c8543fb121288f15a998bf4ce0237e56bdc2a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b218fc2960eb7f021d8628fa08b5363362efdc6bbf4b04ea1002140dcdf93dd7556f39f6265d77c8d35c3c234a0ecedcfe94197499ea080c663249d748ef963e
|
7
|
+
data.tar.gz: 3562d3fb1ccbf97705e4923642814b2c4519942c89dcfc97d9cc74802a220e32c338f2bfce65d689dffc0b35e99982f817236a75b67a0ebc8faeda7b5b516024
|
data/lib/rbbt/persist.rb
CHANGED
@@ -116,7 +116,6 @@ module Persist
|
|
116
116
|
begin
|
117
117
|
Marshal.load(stream)
|
118
118
|
rescue
|
119
|
-
Log.exception $!
|
120
119
|
raise $!
|
121
120
|
end
|
122
121
|
else
|
@@ -135,7 +134,7 @@ module Persist
|
|
135
134
|
raise "Unknown persistence: #{ type }"
|
136
135
|
end
|
137
136
|
rescue
|
138
|
-
Log.
|
137
|
+
Log.warn "Exception loading #{ type } #{ path }: #{$!.message}"
|
139
138
|
raise $!
|
140
139
|
end
|
141
140
|
end
|
@@ -191,30 +190,6 @@ module Persist
|
|
191
190
|
end
|
192
191
|
end
|
193
192
|
|
194
|
-
#def self.tee_stream_fork(stream, path, type, callback = nil)
|
195
|
-
# file, out = Misc.tee_stream(stream)
|
196
|
-
|
197
|
-
# saver_pid = Process.fork do
|
198
|
-
# out.close
|
199
|
-
# stream.close
|
200
|
-
# Misc.purge_pipes
|
201
|
-
# begin
|
202
|
-
# Misc.lock(path) do
|
203
|
-
# save_file(path, type, file)
|
204
|
-
# end
|
205
|
-
# rescue Aborted
|
206
|
-
# stream.abort if stream.respond_to? :abort
|
207
|
-
# raise $!
|
208
|
-
# rescue Exception
|
209
|
-
# Log.exception $!
|
210
|
-
# Kernel.exit! -1
|
211
|
-
# end
|
212
|
-
# Kernel.exit! 0
|
213
|
-
# end
|
214
|
-
# file.close
|
215
|
-
# ConcurrentStream.setup(out, :pids => [saver_pid], :filename => path)
|
216
|
-
#end
|
217
|
-
|
218
193
|
def self.tee_stream_thread(stream, path, type, callback = nil)
|
219
194
|
file, out = Misc.tee_stream(stream)
|
220
195
|
|
@@ -225,11 +200,11 @@ module Persist
|
|
225
200
|
save_file(path, type, file)
|
226
201
|
end
|
227
202
|
rescue Aborted
|
228
|
-
Log.
|
203
|
+
Log.warn "Persist stream thread aborted: #{ Log.color :blue, path }"
|
229
204
|
file.abort if file.respond_to? :abort
|
205
|
+
parent.raise $!
|
230
206
|
rescue Exception
|
231
|
-
Log.
|
232
|
-
Log.exception $!
|
207
|
+
Log.warn "Persist stream thread exception: #{ Log.color :blue, path }"
|
233
208
|
file.abort if file.respond_to? :abort
|
234
209
|
parent.raise $!
|
235
210
|
end
|
@@ -250,10 +225,10 @@ module Persist
|
|
250
225
|
end
|
251
226
|
Log.high "Stream pipe saved: #{path}"
|
252
227
|
rescue Aborted
|
253
|
-
Log.
|
228
|
+
Log.warn "Persist stream pipe exception: #{ Log.color :blue, path }"
|
254
229
|
stream.abort if stream.respond_to? :abort
|
255
230
|
rescue Exception
|
256
|
-
Log.
|
231
|
+
Log.warn "Persist stream pipe exception: #{ Log.color :blue, path }"
|
257
232
|
Log.exception $!
|
258
233
|
stream.abort if stream.respond_to? :abort
|
259
234
|
parent.raise $!
|
@@ -266,7 +241,7 @@ module Persist
|
|
266
241
|
sin.write block
|
267
242
|
end
|
268
243
|
rescue Aborted
|
269
|
-
Log.
|
244
|
+
Log.warn "Tee stream thread aborted"
|
270
245
|
sout.abort if sout.respond_to? :abort
|
271
246
|
sin.abort if sin.respond_to? :abort
|
272
247
|
rescue Exception
|
@@ -328,7 +303,7 @@ module Persist
|
|
328
303
|
res = tee_stream(res.stream, path, type, res.respond_to?(:callback)? res.callback : nil)
|
329
304
|
ConcurrentStream.setup res do
|
330
305
|
begin
|
331
|
-
lockfile.unlock
|
306
|
+
lockfile.unlock if lockfile.locked?
|
332
307
|
rescue
|
333
308
|
Log.exception $!
|
334
309
|
Log.warn "Lockfile exception: " << $!.message
|
@@ -336,7 +311,7 @@ module Persist
|
|
336
311
|
end
|
337
312
|
res.abort_callback = Proc.new do
|
338
313
|
begin
|
339
|
-
lockfile.unlock
|
314
|
+
lockfile.unlock if lockfile.locked?
|
340
315
|
rescue
|
341
316
|
Log.exception $!
|
342
317
|
Log.warn "Lockfile exception: " << $!.message
|
@@ -406,7 +381,7 @@ module Persist
|
|
406
381
|
end
|
407
382
|
|
408
383
|
rescue
|
409
|
-
Log.
|
384
|
+
Log.warn "Error in persist: #{path}#{Open.exists?(path) ? Log.color(:red, " Erasing") : ""}"
|
410
385
|
FileUtils.rm path if Open.exists? path
|
411
386
|
raise $!
|
412
387
|
end
|
@@ -14,13 +14,13 @@ module TSV
|
|
14
14
|
filename_obj = obj.respond_to?(:filename) ? obj.filename : nil
|
15
15
|
filename_obj ||= obj.respond_to?(:path) ? obj.path : nil
|
16
16
|
stream_obj = obj_stream(obj) || obj
|
17
|
-
|
17
|
+
obj.class.to_s << "-" << Misc.fingerprint(stream_obj)
|
18
18
|
end
|
19
19
|
|
20
20
|
def self.report(msg, obj, into)
|
21
21
|
into = into[:into] if Hash === into and into.include? :into
|
22
22
|
|
23
|
-
Log.
|
23
|
+
Log.medium "#{ msg } #{stream_name(obj)} -> #{stream_name(into)}"
|
24
24
|
end
|
25
25
|
|
26
26
|
#{{{ TRAVERSE OBJECTS
|
@@ -96,13 +96,20 @@ module TSV
|
|
96
96
|
|
97
97
|
def self.traverse_io_array(io, options = {}, &block)
|
98
98
|
callback, bar, join = Misc.process_options options, :callback, :bar, :join
|
99
|
+
if File === io and io.closed?
|
100
|
+
begin
|
101
|
+
Log.medium "Rewinding stream #{stream_name(io)}"
|
102
|
+
io.reopen io.filename, "r"
|
103
|
+
rescue
|
104
|
+
Log.exception $!
|
105
|
+
raise "File closed and could not reopen #{stream_name(io)}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
99
109
|
if callback
|
100
110
|
while line = io.gets
|
101
|
-
|
102
|
-
|
103
|
-
ensure
|
104
|
-
bar.tick if bar
|
105
|
-
end
|
111
|
+
callback.call yield line.strip
|
112
|
+
bar.tick if bar
|
106
113
|
end
|
107
114
|
else
|
108
115
|
while line = io.gets
|
@@ -114,17 +121,22 @@ module TSV
|
|
114
121
|
|
115
122
|
def self.traverse_io(io, options = {}, &block)
|
116
123
|
callback, bar, join = Misc.process_options options, :callback, :bar, :join
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
+
if File === io and io.closed?
|
125
|
+
begin
|
126
|
+
Log.medium "Rewinding stream #{stream_name(io)}"
|
127
|
+
io.reopen io.filename, "r"
|
128
|
+
rescue
|
129
|
+
Log.exception $!
|
130
|
+
raise "File closed and could not reopen #{stream_name(io)}"
|
124
131
|
end
|
125
|
-
|
126
|
-
|
127
|
-
|
132
|
+
end
|
133
|
+
|
134
|
+
if callback
|
135
|
+
TSV::Parser.traverse(io, options) do |k,v|
|
136
|
+
callback.call yield k, v
|
137
|
+
end
|
138
|
+
else
|
139
|
+
TSV::Parser.traverse(io, options, &block)
|
128
140
|
end
|
129
141
|
join.call if join
|
130
142
|
end
|
@@ -135,64 +147,90 @@ module TSV
|
|
135
147
|
options[:type] = :single
|
136
148
|
end
|
137
149
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
when IO, File
|
153
|
-
begin
|
154
|
-
if options[:type] == :array
|
155
|
-
traverse_io_array(obj, options, &block)
|
150
|
+
Log.medium "Traversing #{stream_name(obj)} #{Log.color :green, "->"} #{stream_name(options[:into])}"
|
151
|
+
begin
|
152
|
+
sleep 1
|
153
|
+
case obj
|
154
|
+
when TSV
|
155
|
+
traverse_tsv(obj, options, &block)
|
156
|
+
when Hash
|
157
|
+
traverse_hash(obj, options, &block)
|
158
|
+
when TSV::Parser
|
159
|
+
callback = Misc.process_options options, :callback
|
160
|
+
if callback
|
161
|
+
obj.traverse(options) do |k,v|
|
162
|
+
callback.call yield k, v
|
163
|
+
end
|
156
164
|
else
|
157
|
-
|
165
|
+
obj.traverse(options, &block)
|
158
166
|
end
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
167
|
+
when IO, File
|
168
|
+
begin
|
169
|
+
if options[:type] == :array
|
170
|
+
traverse_io_array(obj, options, &block)
|
171
|
+
else
|
172
|
+
traverse_io(obj, options, &block)
|
173
|
+
end
|
174
|
+
rescue Aborted
|
175
|
+
obj.abort if obj.respond_to? :abort
|
176
|
+
raise $!
|
177
|
+
rescue Exception
|
178
|
+
obj.abort if obj.respond_to? :abort
|
179
|
+
raise $!
|
180
|
+
ensure
|
181
|
+
obj.close if obj.respond_to? :close and not obj.closed?
|
182
|
+
obj.join if obj.respond_to? :join
|
183
|
+
end
|
184
|
+
when Path
|
185
|
+
obj.open do |stream|
|
186
|
+
traverse_obj(stream, options, &block)
|
187
|
+
end
|
188
|
+
when TSV::Dumper
|
189
|
+
traverse_obj(obj.stream, options, &block)
|
190
|
+
when (defined? Step and Step)
|
173
191
|
|
174
|
-
|
192
|
+
stream = obj.get_stream
|
175
193
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
end
|
182
|
-
when Array
|
183
|
-
traverse_array(obj, options, &block)
|
184
|
-
when String
|
185
|
-
if Open.remote? obj or Misc.is_filename? obj
|
186
|
-
Open.open(obj) do |s|
|
187
|
-
traverse_obj(s, options, &block)
|
194
|
+
if stream
|
195
|
+
traverse_obj(stream, options, &block)
|
196
|
+
else
|
197
|
+
obj.join
|
198
|
+
traverse_obj(obj.path, options, &block)
|
188
199
|
end
|
200
|
+
when Array
|
201
|
+
traverse_array(obj, options, &block)
|
202
|
+
when String
|
203
|
+
if Open.remote? obj or Misc.is_filename? obj
|
204
|
+
Open.open(obj) do |s|
|
205
|
+
traverse_obj(s, options, &block)
|
206
|
+
end
|
207
|
+
else
|
208
|
+
raise "Can not open obj for traversal #{Misc.fingerprint obj}"
|
209
|
+
end
|
210
|
+
when nil
|
211
|
+
raise "Can not traverse nil object into #{stream_name(options[:into])}"
|
189
212
|
else
|
190
|
-
raise "
|
213
|
+
raise "Unknown object for traversal: #{Misc.fingerprint obj }"
|
191
214
|
end
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
215
|
+
rescue IOError
|
216
|
+
Log.warn "IOError traversing #{stream_name(obj)}: #{$!.message}"
|
217
|
+
stream = obj_stream(obj)
|
218
|
+
stream.abort if stream and stream.respond_to? :abort
|
219
|
+
raise $!
|
220
|
+
rescue Errno::EPIPE
|
221
|
+
Log.warn "Pipe closed while traversing #{stream_name(obj)}: #{$!.message}"
|
222
|
+
raise $!
|
223
|
+
rescue Aborted
|
224
|
+
Log.warn "Aborted traversing #{stream_name(obj)}"
|
225
|
+
stream = obj_stream(obj)
|
226
|
+
stream.abort if stream and stream.respond_to? :abort
|
227
|
+
raise $!
|
228
|
+
rescue Exception
|
229
|
+
Log.warn "Exception traversing #{stream_name(obj)}"
|
230
|
+
Log.exception $!
|
231
|
+
stream = obj_stream(obj)
|
232
|
+
stream.abort if stream and stream.respond_to? :abort
|
233
|
+
raise $!
|
196
234
|
end
|
197
235
|
end
|
198
236
|
|
@@ -236,9 +274,13 @@ module TSV
|
|
236
274
|
end
|
237
275
|
end
|
238
276
|
|
239
|
-
|
277
|
+
begin
|
278
|
+
thread.join
|
279
|
+
rescue
|
280
|
+
raise $!
|
281
|
+
end
|
240
282
|
rescue Interrupt, Aborted
|
241
|
-
Log.
|
283
|
+
Log.warn "Aborted traversal in CPUs for #{stream_name(obj) || Misc.fingerprint(obj)}"
|
242
284
|
stream = obj_stream(obj)
|
243
285
|
stream.abort if stream.respond_to? :abort
|
244
286
|
stream = obj_stream(options[:into])
|
@@ -246,9 +288,7 @@ module TSV
|
|
246
288
|
q.abort
|
247
289
|
raise $!
|
248
290
|
rescue Exception
|
249
|
-
Log.
|
250
|
-
Log.exception $!
|
251
|
-
|
291
|
+
Log.warn "Exception during traversal in CPUs for #{stream_name(obj) || Misc.fingerprint(obj)}"
|
252
292
|
stream = obj_stream(obj)
|
253
293
|
stream.abort if stream.respond_to? :abort
|
254
294
|
stream = obj_stream(options[:into])
|
@@ -350,11 +390,12 @@ module TSV
|
|
350
390
|
begin
|
351
391
|
traverse_run(obj, threads, cpus, options, &block)
|
352
392
|
into.close if into.respond_to? :close
|
353
|
-
rescue
|
354
|
-
|
355
|
-
|
393
|
+
rescue
|
394
|
+
stream = obj_stream(obj)
|
395
|
+
stream.abort if stream and stream.respond_to? :abort
|
356
396
|
stream = obj_stream(into)
|
357
397
|
stream.abort if stream and stream.respond_to? :abort
|
398
|
+
parent.raise $!
|
358
399
|
end
|
359
400
|
end
|
360
401
|
ConcurrentStream.setup(obj_stream(into), :threads => thread)
|
@@ -398,7 +439,7 @@ module TSV
|
|
398
439
|
|
399
440
|
if into
|
400
441
|
bar = Misc.process_options options, :bar
|
401
|
-
|
442
|
+
|
402
443
|
options[:join] = Proc.new do
|
403
444
|
Log::ProgressBar.remove_bar(bar)
|
404
445
|
end if bar
|
@@ -407,7 +448,6 @@ module TSV
|
|
407
448
|
begin
|
408
449
|
store_into into, e
|
409
450
|
rescue Aborted
|
410
|
-
Log.error "Traversal info #{stream_name into} aborted"
|
411
451
|
raise $!
|
412
452
|
rescue Exception
|
413
453
|
Log.exception $!
|
data/lib/rbbt/tsv/parser.rb
CHANGED
@@ -134,7 +134,6 @@ module TSV
|
|
134
134
|
return parts.shift.split(@sep2, -1).first, parts.collect{|value| value.split(@sep2, -1)}.flatten if
|
135
135
|
field_positions.nil? and (key_position.nil? or key_position == 0)
|
136
136
|
rescue
|
137
|
-
eee [:rescue, orig]
|
138
137
|
raise $!
|
139
138
|
end
|
140
139
|
|
@@ -536,11 +535,14 @@ module TSV
|
|
536
535
|
end
|
537
536
|
rescue END_PARSING
|
538
537
|
break
|
539
|
-
|
540
|
-
|
541
|
-
|
538
|
+
rescue Errno::EPIPE
|
539
|
+
Log.error "Pipe closed while parsing #{Misc.fingerprint stream}: #{$!.message}"
|
540
|
+
raise $!
|
542
541
|
rescue Exception
|
542
|
+
Log.error "Exception parsing #{Misc.fingerprint stream}: #{$!.message}"
|
543
|
+
Log.exception $!
|
543
544
|
stream.abort if stream.respond_to? :abort
|
545
|
+
stream.join if stream.respond_to? :join
|
544
546
|
raise $!
|
545
547
|
end
|
546
548
|
end
|
data/lib/rbbt/tsv/stream.rb
CHANGED
@@ -28,8 +28,8 @@ module TSV
|
|
28
28
|
input_options = []
|
29
29
|
|
30
30
|
input_source_streams = inputs.collect do |input|
|
31
|
-
stream = TSV.get_stream
|
32
|
-
stream
|
31
|
+
stream = sort ? Misc.sort_stream(input) : TSV.get_stream(input)
|
32
|
+
stream
|
33
33
|
end
|
34
34
|
|
35
35
|
input_source_streams.each do |stream|
|
@@ -52,4 +52,109 @@ module TSV
|
|
52
52
|
dumper.stream = Misc.paste_streams input_streams, input_lines, options[:sep], header
|
53
53
|
dumper
|
54
54
|
end
|
55
|
+
|
56
|
+
def self.paste_streams(streams, options = {})
|
57
|
+
options = Misc.add_defaults options, :sep => "\t", :sort => true
|
58
|
+
sort, sep = Misc.process_options options, :sort, :sep
|
59
|
+
|
60
|
+
Misc.open_pipe do |sin|
|
61
|
+
num_streams = streams.length
|
62
|
+
|
63
|
+
streams = streams.collect do |stream|
|
64
|
+
if defined? Step and Step === stream
|
65
|
+
stream.get_stream || stream.join.path.open
|
66
|
+
else
|
67
|
+
stream
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
streams = streams.collect do |stream|
|
72
|
+
Misc.sort_stream(stream)
|
73
|
+
end if sort
|
74
|
+
|
75
|
+
lines = []
|
76
|
+
fields = []
|
77
|
+
key_fields = []
|
78
|
+
input_options = []
|
79
|
+
empty = []
|
80
|
+
|
81
|
+
streams = streams.collect do |stream|
|
82
|
+
parser = TSV::Parser.new stream, options
|
83
|
+
lines << parser.first_line
|
84
|
+
empty << stream if parser.first_line.nil?
|
85
|
+
key_fields << parser.key_field
|
86
|
+
fields << parser.fields
|
87
|
+
input_options << parser.options
|
88
|
+
|
89
|
+
parser.stream
|
90
|
+
end
|
91
|
+
|
92
|
+
key_field = key_fields.compact.first
|
93
|
+
fields = fields.compact.flatten
|
94
|
+
options = options.merge(input_options.first)
|
95
|
+
|
96
|
+
sin.puts TSV.header_lines(key_field, fields, options)
|
97
|
+
|
98
|
+
empty.each do |stream|
|
99
|
+
i = streams.index stream
|
100
|
+
lines.delete_at i
|
101
|
+
fields.delete_at i
|
102
|
+
key_fields.delete_at i
|
103
|
+
input_options.delete_at i
|
104
|
+
end
|
105
|
+
|
106
|
+
begin
|
107
|
+
done_streams = []
|
108
|
+
|
109
|
+
keys = []
|
110
|
+
parts = []
|
111
|
+
lines.each_with_index do |line,i|
|
112
|
+
key, *p = line.strip.split(sep, -1)
|
113
|
+
keys[i] = key
|
114
|
+
parts[i] = p
|
115
|
+
end
|
116
|
+
sizes = parts.collect{|p| p.length }
|
117
|
+
last_min = nil
|
118
|
+
while lines.compact.any?
|
119
|
+
min = keys.compact.sort.first
|
120
|
+
str = []
|
121
|
+
keys.each_with_index do |key,i|
|
122
|
+
case key
|
123
|
+
when min
|
124
|
+
str << [parts[i] * sep]
|
125
|
+
line = lines[i] = begin
|
126
|
+
streams[i].gets
|
127
|
+
rescue
|
128
|
+
Log.exception $!
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
if line.nil?
|
132
|
+
stream = streams[i]
|
133
|
+
#stream.join if stream.respond_to? :join
|
134
|
+
keys[i] = nil
|
135
|
+
parts[i] = nil
|
136
|
+
else
|
137
|
+
k, *p = line.strip.split(sep, -1)
|
138
|
+
keys[i] = k
|
139
|
+
parts[i] = p
|
140
|
+
end
|
141
|
+
else
|
142
|
+
str << [sep * (sizes[i]-1)] if sizes[i] > 0
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
sin.puts [min, str*sep] * sep
|
147
|
+
end
|
148
|
+
streams.each do |stream|
|
149
|
+
#stream.join if stream.respond_to? :join
|
150
|
+
end
|
151
|
+
rescue
|
152
|
+
Log.exception $!
|
153
|
+
streams.each do |stream|
|
154
|
+
stream.abort if stream.respond_to? :abort
|
155
|
+
end
|
156
|
+
raise $!
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
55
160
|
end
|