rbbt-util 5.20.2 → 5.20.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/rbbt/association/index.rb +3 -4
- data/lib/rbbt/tsv/accessor.rb +1 -1
- data/lib/rbbt/tsv/manipulate.rb +0 -1
- data/lib/rbbt/tsv/parallel/traverse.rb +8 -2
- data/lib/rbbt/util/cmd.rb +1 -1
- data/lib/rbbt/util/concurrency/processes.rb +4 -3
- data/lib/rbbt/util/concurrency/processes/worker.rb +4 -2
- data/lib/rbbt/util/log/progress/util.rb +20 -1
- data/lib/rbbt/util/misc/bgzf.rb +1 -1
- data/lib/rbbt/util/misc/inspect.rb +7 -3
- data/lib/rbbt/util/misc/multipart_payload.rb +8 -8
- data/lib/rbbt/util/misc/pipes.rb +115 -51
- data/lib/rbbt/util/open.rb +1 -1
- data/lib/rbbt/workflow.rb +1 -0
- data/lib/rbbt/workflow/accessor.rb +86 -15
- data/lib/rbbt/workflow/definition.rb +10 -17
- data/lib/rbbt/workflow/step.rb +4 -16
- data/lib/rbbt/workflow/step/dependencies.rb +451 -0
- data/lib/rbbt/workflow/step/run.rb +43 -226
- data/lib/rbbt/workflow/task.rb +7 -1
- data/share/rbbt_commands/association/subset +6 -1
- data/share/rbbt_commands/workflow/task +21 -9
- data/test/rbbt/util/misc/test_pipes.rb +54 -0
- data/test/rbbt/workflow/step/test_dependencies.rb +206 -0
- metadata +5 -2
@@ -1,156 +1,12 @@
|
|
1
|
+
require 'rbbt/workflow/step/dependencies'
|
2
|
+
|
1
3
|
class Step
|
2
4
|
|
3
5
|
attr_reader :stream, :dupped, :saved_stream, :inputs
|
4
6
|
|
5
|
-
STREAM_CACHE = {}
|
6
|
-
STREAM_CACHE_MUTEX = Mutex.new
|
7
|
-
def self.purge_stream_cache
|
8
|
-
Log.medium "Purging dup. stream cache"
|
9
|
-
STREAM_CACHE_MUTEX.synchronize do
|
10
|
-
#STREAM_CACHE.collect{|k,s|
|
11
|
-
# next
|
12
|
-
# Thread.new do
|
13
|
-
# Misc.consume_stream s
|
14
|
-
# end
|
15
|
-
#}
|
16
|
-
STREAM_CACHE.clear
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.dup_stream(stream)
|
21
|
-
case stream
|
22
|
-
when IO, File, Step
|
23
|
-
return stream if stream.respond_to?(:closed?) and stream.closed?
|
24
|
-
return stream if stream.respond_to?(:done?) and stream.done?
|
25
|
-
|
26
|
-
STREAM_CACHE_MUTEX.synchronize do
|
27
|
-
stream_key = Misc.fingerprint(stream)
|
28
|
-
current = STREAM_CACHE[stream_key]
|
29
|
-
case current
|
30
|
-
when nil
|
31
|
-
Log.medium "Not duplicating stream #{stream_key}"
|
32
|
-
STREAM_CACHE[stream_key] = stream
|
33
|
-
when File
|
34
|
-
if Open.exists? current.path
|
35
|
-
Log.medium "Reopening file #{stream_key}"
|
36
|
-
Open.open(current.path)
|
37
|
-
else
|
38
|
-
new = Misc.dup_stream(current)
|
39
|
-
Log.medium "Duplicating file #{stream_key} #{current.inspect} => #{Misc.fingerprint(new)}"
|
40
|
-
new
|
41
|
-
end
|
42
|
-
when Step
|
43
|
-
job = current
|
44
|
-
current = job.result
|
45
|
-
new = Misc.dup_stream(current)
|
46
|
-
job.result = current
|
47
|
-
Log.medium "Duplicating step #{stream_key} #{current.inspect} => #{Misc.fingerprint(new)}"
|
48
|
-
new
|
49
|
-
else
|
50
|
-
new = Misc.dup_stream(current)
|
51
|
-
Log.medium "Duplicating stream #{stream_key} #{ Misc.fingerprint(stream) } => #{Misc.fingerprint(new)}"
|
52
|
-
new
|
53
|
-
end
|
54
|
-
end
|
55
|
-
when TSV::Dumper#, TSV::Parser
|
56
|
-
stream = stream.stream
|
57
|
-
return stream if stream.closed?
|
58
|
-
|
59
|
-
STREAM_CACHE_MUTEX.synchronize do
|
60
|
-
if STREAM_CACHE[stream].nil?
|
61
|
-
Log.high "Not duplicating dumper #{ stream.inspect }"
|
62
|
-
STREAM_CACHE[stream] = stream
|
63
|
-
else
|
64
|
-
new = Misc.dup_stream(STREAM_CACHE[stream])
|
65
|
-
Log.high "Duplicating dumper #{ stream.inspect } into #{new.inspect}"
|
66
|
-
new
|
67
|
-
end
|
68
|
-
end
|
69
|
-
else
|
70
|
-
stream
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def self.prepare_for_execution(job)
|
75
|
-
return if (job.done? and not job.dirty?) or
|
76
|
-
(job.streaming? and job.running?) or
|
77
|
-
(defined? WorkflowRESTClient and WorkflowRESTClient::RemoteStep === job and not (job.error? or job.aborted?))
|
78
|
-
|
79
|
-
job.clean if job.aborted? or (job.started? and not job.running? and not job.error?)
|
80
|
-
|
81
|
-
raise DependencyError, job if job.error?
|
82
|
-
end
|
83
|
-
|
84
|
-
def execute_dependency(dependency)
|
85
|
-
task_name = self.task_name
|
86
|
-
begin
|
87
|
-
|
88
|
-
if dependency.done?
|
89
|
-
Log.info "#{Log.color :cyan, "dependency"} #{Log.color :yellow, task_name.to_s || ""} => #{Log.color :yellow, dependency.task_name.to_s || ""} done -- #{Log.color :blue, dependency.path} -- #{Log.color :yellow, self.short_path}"
|
90
|
-
return
|
91
|
-
end
|
92
|
-
|
93
|
-
if not dependency.started?
|
94
|
-
Log.info "#{Log.color :cyan, "dependency"} #{Log.color :yellow, task_name.to_s || ""} => #{Log.color :yellow, dependency.task_name.to_s || ""} starting -- #{Log.color :blue, dependency.path} -- #{Log.color :yellow, self.short_path}"
|
95
|
-
dependency.run(:stream)
|
96
|
-
raise TryAgain
|
97
|
-
end
|
98
|
-
|
99
|
-
dependency.grace
|
100
|
-
|
101
|
-
if dependency.aborted?
|
102
|
-
Log.warn "#{Log.color :cyan, "dependency"} #{Log.color :yellow, task_name.to_s || ""} => #{Log.color :yellow, dependency.task_name.to_s || ""} aborted (clean and retry) -- #{Log.color :blue, dependency.path} -- #{Log.color :yellow, self.short_path}"
|
103
|
-
dependency.clean
|
104
|
-
raise TryAgain
|
105
|
-
end
|
106
|
-
|
107
|
-
if dependency.error?
|
108
|
-
Log.error "#{Log.color :cyan, "dependency"} #{Log.color :yellow, task_name.to_s || ""} => #{Log.color :yellow, dependency.task_name.to_s || ""} error -- #{Log.color :blue, dependency.path} -- #{Log.color :yellow, self.short_path}"
|
109
|
-
raise DependencyError, [dependency.path, dependency.messages.last] * ": " if dependency.error?
|
110
|
-
end
|
111
|
-
|
112
|
-
if dependency.streaming?
|
113
|
-
Log.info "#{Log.color :cyan, "dependency"} #{Log.color :yellow, task_name.to_s || ""} => #{Log.color :yellow, dependency.task_name.to_s || ""} streaming -- #{Log.color :blue, dependency.path} -- #{Log.color :yellow, self.short_path}"
|
114
|
-
return
|
115
|
-
end
|
116
|
-
|
117
|
-
begin
|
118
|
-
Log.info "#{Log.color :cyan, "dependency"} #{Log.color :yellow, task_name.to_s || ""} => #{Log.color :yellow, dependency.task_name.to_s || ""} joining -- #{Log.color :blue, dependency.path} -- #{Log.color :yellow, self.short_path}"
|
119
|
-
dependency.join
|
120
|
-
raise TryAgain unless dependency.done?
|
121
|
-
Log.info "#{Log.color :cyan, "dependency"} #{Log.color :yellow, task_name.to_s || ""} => #{Log.color :yellow, dependency.task_name.to_s || ""} joined -- #{Log.color :blue, dependency.path} -- #{Log.color :yellow, self.short_path}"
|
122
|
-
rescue Aborted
|
123
|
-
raise TryAgain
|
124
|
-
end
|
125
|
-
|
126
|
-
rescue TryAgain
|
127
|
-
retry
|
128
|
-
rescue Aborted
|
129
|
-
Log.error "Aborted dep. #{Log.color :red, dependency.task_name.to_s}"
|
130
|
-
raise $!
|
131
|
-
rescue Interrupt
|
132
|
-
Log.error "Interrupted while in dep. #{Log.color :red, dependency.task_name.to_s}"
|
133
|
-
raise $!
|
134
|
-
rescue Exception
|
135
|
-
Log.error "Exception in dep. #{ Log.color :red, dependency.task_name.to_s }"
|
136
|
-
Log.exception $!
|
137
|
-
raise $!
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
def dup_inputs
|
142
|
-
return if @dupped or ENV["RBBT_NO_STREAM"] == 'true'
|
143
|
-
Log.low "Dupping inputs for #{path}"
|
144
|
-
dupped_inputs = @inputs.collect do |input|
|
145
|
-
Step.dup_stream input
|
146
|
-
end
|
147
|
-
@inputs.replace dupped_inputs
|
148
|
-
@dupped = true
|
149
|
-
end
|
150
|
-
|
151
7
|
def get_stream
|
152
8
|
@mutex.synchronize do
|
153
|
-
Log.low "Getting stream from #{path} #{!@saved_stream}"
|
9
|
+
Log.low "Getting stream from #{path} #{!@saved_stream} [#{object_id}]"
|
154
10
|
begin
|
155
11
|
return nil if @saved_stream
|
156
12
|
if IO === @result
|
@@ -162,14 +18,33 @@ class Step
|
|
162
18
|
end
|
163
19
|
end
|
164
20
|
|
21
|
+
def resolve_input_steps
|
22
|
+
step = false
|
23
|
+
new_inputs = @inputs.collect do |i|
|
24
|
+
if Step === i
|
25
|
+
step = true
|
26
|
+
if i.done?
|
27
|
+
i.load
|
28
|
+
elsif i.streaming?
|
29
|
+
TSV.get_stream i
|
30
|
+
else
|
31
|
+
i.join
|
32
|
+
i.load
|
33
|
+
end
|
34
|
+
else
|
35
|
+
i
|
36
|
+
end
|
37
|
+
end
|
38
|
+
@inputs.replace new_inputs if step
|
39
|
+
end
|
165
40
|
|
166
41
|
def _exec
|
42
|
+
resolve_input_steps
|
167
43
|
@exec = true if @exec.nil?
|
168
44
|
@task.exec_in((bindings ? bindings : self), *@inputs)
|
169
45
|
end
|
170
46
|
|
171
47
|
def exec(no_load=false)
|
172
|
-
dup_inputs
|
173
48
|
dependencies.each{|dependency| dependency.exec(no_load) }
|
174
49
|
@mutex.synchronize do
|
175
50
|
@result = self._exec
|
@@ -182,7 +57,6 @@ class Step
|
|
182
57
|
rec_dependencies.collect{|dependency| (defined? WorkflowRESTClient and WorkflowRESTClient::RemoteStep === dependency) ? nil : dependency.path }.compact.uniq
|
183
58
|
end
|
184
59
|
|
185
|
-
|
186
60
|
def kill_children
|
187
61
|
begin
|
188
62
|
children_pids = info[:children_pids]
|
@@ -202,60 +76,6 @@ class Step
|
|
202
76
|
end
|
203
77
|
end
|
204
78
|
|
205
|
-
def run_dependencies
|
206
|
-
@seen ||= []
|
207
|
-
seen_paths ||= Set.new
|
208
|
-
|
209
|
-
dependencies.uniq.each do |dependency|
|
210
|
-
dependency_path = dependency.path
|
211
|
-
next if seen_paths.include? dependency_path
|
212
|
-
@seen.concat dependency.rec_dependencies
|
213
|
-
seen_paths.union(dependency.rec_dependencies.collect{|d| d.path})
|
214
|
-
@seen << dependency
|
215
|
-
seen_paths << dependency_path
|
216
|
-
end
|
217
|
-
|
218
|
-
@seen.uniq!
|
219
|
-
@seen.delete self
|
220
|
-
|
221
|
-
return if @seen.empty?
|
222
|
-
|
223
|
-
log :dependencies, "#{Log.color :magenta, "Dependencies"} for step #{Log.color :yellow, task.name.to_s || ""}"
|
224
|
-
|
225
|
-
@seen.each do |dependency|
|
226
|
-
Step.prepare_for_execution(dependency)
|
227
|
-
end
|
228
|
-
|
229
|
-
pre_deps = []
|
230
|
-
last_deps = []
|
231
|
-
@seen.each do |dependency|
|
232
|
-
if dependencies.include? dependency
|
233
|
-
if dependency.inputs.flatten.select{|i| Step === i}.any?
|
234
|
-
last_deps << dependency
|
235
|
-
else
|
236
|
-
pre_deps << dependency
|
237
|
-
end
|
238
|
-
|
239
|
-
else
|
240
|
-
pre_deps << dependency
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
pre_deps.each do |dependency|
|
245
|
-
dependency.dup_inputs
|
246
|
-
execute_dependency(dependency)
|
247
|
-
end
|
248
|
-
|
249
|
-
last_deps.each do |dependency|
|
250
|
-
dependency.dup_inputs
|
251
|
-
end
|
252
|
-
|
253
|
-
last_deps.each do |dependency|
|
254
|
-
execute_dependency(dependency)
|
255
|
-
end
|
256
|
-
|
257
|
-
end
|
258
|
-
|
259
79
|
def run(no_load = false)
|
260
80
|
result = nil
|
261
81
|
|
@@ -280,7 +100,7 @@ class Step
|
|
280
100
|
:clean_name => clean_name,
|
281
101
|
})
|
282
102
|
|
283
|
-
dup_inputs
|
103
|
+
#dup_inputs
|
284
104
|
begin
|
285
105
|
run_dependencies
|
286
106
|
rescue Exception
|
@@ -294,11 +114,11 @@ class Step
|
|
294
114
|
set_info :inputs, Misc.remove_long_items(Misc.zip2hash(task.inputs, @inputs)) unless task.inputs.nil?
|
295
115
|
|
296
116
|
set_info :started, (start_time = Time.now)
|
297
|
-
log :started, "
|
117
|
+
log :started, "Starting step #{Log.color :yellow, task.name.to_s || ""}"
|
298
118
|
|
299
119
|
begin
|
300
120
|
result = _exec
|
301
|
-
rescue Aborted
|
121
|
+
rescue Aborted, Interrupt
|
302
122
|
log(:aborted, "Aborted")
|
303
123
|
raise $!
|
304
124
|
rescue Exception
|
@@ -326,7 +146,7 @@ class Step
|
|
326
146
|
end
|
327
147
|
|
328
148
|
if stream
|
329
|
-
log :streaming, "
|
149
|
+
log :streaming, "Streaming step #{Log.color :yellow, task.name.to_s || ""}"
|
330
150
|
ConcurrentStream.setup stream do
|
331
151
|
begin
|
332
152
|
if status != :done
|
@@ -334,14 +154,14 @@ class Step
|
|
334
154
|
set_info :done, (done_time = Time.now)
|
335
155
|
set_info :total_time_elapsed, (total_time_elapsed = done_time - issue_time)
|
336
156
|
set_info :time_elapsed, (time_elapsed = done_time - start_time)
|
337
|
-
log :done, "
|
157
|
+
log :done, "Completed step #{Log.color :yellow, task.name.to_s || ""} in #{time_elapsed.to_i}+#{(total_time_elapsed - time_elapsed).to_i} sec."
|
338
158
|
end
|
339
159
|
end
|
340
160
|
rescue
|
341
161
|
Log.exception $!
|
342
162
|
ensure
|
343
163
|
join
|
344
|
-
Step.purge_stream_cache
|
164
|
+
#Step.purge_stream_cache
|
345
165
|
FileUtils.rm pid_file if File.exists?(pid_file)
|
346
166
|
end
|
347
167
|
end
|
@@ -358,8 +178,8 @@ class Step
|
|
358
178
|
set_info :done, (done_time = Time.now)
|
359
179
|
set_info :total_time_elapsed, (total_time_elapsed = done_time - issue_time)
|
360
180
|
set_info :time_elapsed, (time_elapsed = done_time - start_time)
|
361
|
-
log :done, "
|
362
|
-
Step.purge_stream_cache
|
181
|
+
log :done, "Completed step #{Log.color :yellow, task.name.to_s || ""} in #{time_elapsed.to_i}+#{(total_time_elapsed - time_elapsed).to_i} sec."
|
182
|
+
#Step.purge_stream_cache
|
363
183
|
FileUtils.rm pid_file if File.exists?(pid_file)
|
364
184
|
end
|
365
185
|
|
@@ -370,10 +190,14 @@ class Step
|
|
370
190
|
@result ||= result
|
371
191
|
self
|
372
192
|
else
|
373
|
-
Step.purge_stream_cache
|
193
|
+
#Step.purge_stream_cache
|
374
194
|
@result = prepare_result result, @task.result_description
|
375
195
|
end
|
376
196
|
end
|
197
|
+
rescue Aborted, Interrupt
|
198
|
+
abort
|
199
|
+
stop_dependencies
|
200
|
+
raise $!
|
377
201
|
rescue Exception
|
378
202
|
exception $!
|
379
203
|
stop_dependencies
|
@@ -392,9 +216,9 @@ class Step
|
|
392
216
|
end
|
393
217
|
end
|
394
218
|
|
395
|
-
clean if dirty? or not running?
|
219
|
+
clean if dirty? or (not running? and not done?)
|
396
220
|
|
397
|
-
run(
|
221
|
+
run(:stream) unless started?
|
398
222
|
|
399
223
|
join unless done?
|
400
224
|
|
@@ -412,7 +236,7 @@ class Step
|
|
412
236
|
@forked = true
|
413
237
|
res = run true
|
414
238
|
set_info :forked, true
|
415
|
-
rescue Aborted
|
239
|
+
rescue Aborted, Interrupt
|
416
240
|
Log.debug{"Forked process aborted: #{path}"}
|
417
241
|
log :aborted, "Job aborted (#{Process.pid})"
|
418
242
|
raise $!
|
@@ -452,13 +276,6 @@ class Step
|
|
452
276
|
self
|
453
277
|
end
|
454
278
|
|
455
|
-
def stop_dependencies
|
456
|
-
dependencies.each do |dep|
|
457
|
-
dep.abort
|
458
|
-
end
|
459
|
-
kill_children
|
460
|
-
end
|
461
|
-
|
462
279
|
def abort_pid
|
463
280
|
@pid ||= info[:pid]
|
464
281
|
|
@@ -491,7 +308,7 @@ class Step
|
|
491
308
|
Log.medium "Aborting job stream #{stream.inspect} -- #{Log.color :blue, path}"
|
492
309
|
stream.abort
|
493
310
|
#stream.close unless stream.closed?
|
494
|
-
rescue Aborted
|
311
|
+
rescue Aborted, Interrupt
|
495
312
|
Log.medium "Aborting job stream #{stream.inspect} ABORTED RETRY -- #{Log.color :blue, path}"
|
496
313
|
Log.exception $!
|
497
314
|
retry
|
@@ -507,11 +324,12 @@ class Step
|
|
507
324
|
begin
|
508
325
|
stop_dependencies
|
509
326
|
abort_stream
|
510
|
-
abort_pid if defined? @forked and @forked
|
511
|
-
rescue Aborted
|
327
|
+
abort_pid if defined? @forked and @forked and running?
|
328
|
+
rescue Aborted, Interrupt
|
512
329
|
Log.medium{"#{Log.color :red, "Aborting ABORTED RETRY"} #{Log.color :blue, path}"}
|
513
330
|
retry
|
514
331
|
rescue Exception
|
332
|
+
Log.exception $!
|
515
333
|
retry
|
516
334
|
ensure
|
517
335
|
if Open.exists? path
|
@@ -523,7 +341,6 @@ class Step
|
|
523
341
|
end
|
524
342
|
end
|
525
343
|
end
|
526
|
-
Log.medium{"#{Log.color :red, "Aborted"} #{Log.color :blue, path}"}
|
527
344
|
end
|
528
345
|
|
529
346
|
def abort
|
data/lib/rbbt/workflow/task.rb
CHANGED
@@ -75,7 +75,13 @@ module Task
|
|
75
75
|
seen = []
|
76
76
|
task_inputs = {}
|
77
77
|
deps.each do |dep|
|
78
|
-
|
78
|
+
if Array === dep and dep.first
|
79
|
+
wf, task = (Array === dep ? [dep.first, dep.first.tasks[dep[1].to_sym]] : [workflow, workflow.tasks[dep.to_sym]])
|
80
|
+
elsif Symbol === dep
|
81
|
+
wf, task = [workflow, workflow.tasks[dep.to_sym]]
|
82
|
+
else
|
83
|
+
next
|
84
|
+
end
|
79
85
|
maps = (Array === dep and Hash === dep.last) ? dep.last.keys : []
|
80
86
|
raise "Dependency task not found: #{dep}" if task.nil?
|
81
87
|
next if seen.include? [wf, task.name]
|
@@ -36,9 +36,14 @@ matches = file.subset(source, target)
|
|
36
36
|
if options[:tsv]
|
37
37
|
puts matches.tsv.to_s
|
38
38
|
else
|
39
|
+
fields = file.fields
|
39
40
|
matches.each do |item|
|
40
41
|
puts Log.color(:magenta, item)
|
41
|
-
|
42
|
+
if fields.any?
|
43
|
+
info = file.fields.zip(file[item])
|
44
|
+
else
|
45
|
+
info = {}
|
46
|
+
end
|
42
47
|
source,_sep, target = item.partition "~"
|
43
48
|
puts " " << Misc.format_definition_list_item("source", source)
|
44
49
|
puts " " << Misc.format_definition_list_item("target", target)
|
@@ -433,9 +433,13 @@ case res
|
|
433
433
|
when (defined?(WorkflowRESTClient) and WorkflowRESTClient::RemoteStep)
|
434
434
|
res = job.result
|
435
435
|
if res.respond_to? :gets
|
436
|
-
|
437
|
-
|
438
|
-
|
436
|
+
begin
|
437
|
+
Misc.consume_stream(res, false, out)
|
438
|
+
#while block = res.readpartial(Misc::BLOCK_SIZE) do
|
439
|
+
# out.write block
|
440
|
+
#end #unless io.closed?
|
441
|
+
rescue EOFError, IOError
|
442
|
+
end
|
439
443
|
res.join if res.respond_to? :join
|
440
444
|
else
|
441
445
|
puts res.to_s
|
@@ -443,16 +447,24 @@ when (defined?(WorkflowRESTClient) and WorkflowRESTClient::RemoteStep)
|
|
443
447
|
when Step
|
444
448
|
if res.streaming?
|
445
449
|
io = TSV.get_stream res
|
446
|
-
|
447
|
-
|
448
|
-
|
450
|
+
Misc.consume_stream(io, false, out)
|
451
|
+
#begin
|
452
|
+
# while block = io.readpartial(Misc::BLOCK_SIZE) do
|
453
|
+
# out.write block
|
454
|
+
# end #unless io.closed?
|
455
|
+
#rescue EOFError, IOError
|
456
|
+
#end
|
449
457
|
io.join if io.respond_to? :join
|
450
458
|
elsif IO === res.result
|
451
459
|
begin
|
452
460
|
io = res.get_stream
|
453
|
-
|
454
|
-
|
455
|
-
|
461
|
+
Misc.consume_stream(io, false, out)
|
462
|
+
#begin
|
463
|
+
# while block = io.readpartial(Misc::BLOCK_SIZE) do
|
464
|
+
# out.write block
|
465
|
+
# end #unless io.closed?
|
466
|
+
#rescue EOFError, IOError
|
467
|
+
#end
|
456
468
|
io.join if io.respond_to? :join
|
457
469
|
rescue Aborted, Interrupt
|
458
470
|
Log.error "Process interrupted. Aborting step"
|