rbbt-util 5.12.3 → 5.13.0
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 +127 -109
- data/lib/rbbt/tsv/dumper.rb +0 -1
- data/lib/rbbt/tsv/parallel/traverse.rb +101 -36
- data/lib/rbbt/util/concurrency/processes.rb +5 -2
- data/lib/rbbt/util/concurrency/threads.rb +6 -4
- data/lib/rbbt/util/log.rb +1 -0
- data/lib/rbbt/util/log/progress.rb +163 -0
- data/lib/rbbt/util/misc/options.rb +1 -0
- data/lib/rbbt/util/misc/pipes.rb +63 -50
- data/lib/rbbt/util/misc/progress.rb +0 -0
- data/lib/rbbt/util/open.rb +45 -11
- data/lib/rbbt/util/simpleopt/get.rb +1 -1
- data/lib/rbbt/workflow/accessor.rb +59 -38
- data/lib/rbbt/workflow/step/run.rb +2 -3
- data/share/rbbt_commands/workflow/task +14 -4
- data/test/rbbt/tsv/parallel/test_traverse.rb +35 -1
- data/test/rbbt/util/log/test_progress.rb +49 -0
- data/test/rbbt/util/misc/test_pipes.rb +4 -4
- data/test/rbbt/util/test_open.rb +0 -3
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 50b93d44cf66b527dc5cb3171ea799ad7618024f
|
4
|
+
data.tar.gz: f8647589d8b4c281074309db7509bd4b26e50aea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b04572a90e96cc1ac50f476df87025f459a0b36258de071179ae7f702da7799e17680a5ceb9d36ecf6e243a2008cd93f45ba7523756568cd99948580cc254356
|
7
|
+
data.tar.gz: ab57d3affae47af95efebbe2d2368418f4b9eae2b7dd8ab980d5d3dfaecd414dbdc0b5ce4615a6770b3d665877c4584ae13c73d7f308f878beb0979e0364d40d
|
data/lib/rbbt/persist.rb
CHANGED
@@ -111,7 +111,17 @@ module Persist
|
|
111
111
|
res
|
112
112
|
when :marshal
|
113
113
|
Open.open(path) do |stream|
|
114
|
-
|
114
|
+
case stream
|
115
|
+
when StringIO
|
116
|
+
begin
|
117
|
+
Marshal.load(stream)
|
118
|
+
rescue
|
119
|
+
Log.exception $!
|
120
|
+
raise $!
|
121
|
+
end
|
122
|
+
else
|
123
|
+
Marshal.load(stream)
|
124
|
+
end
|
115
125
|
end
|
116
126
|
when :yaml
|
117
127
|
Open.open(path) do |stream|
|
@@ -208,7 +218,7 @@ module Persist
|
|
208
218
|
def self.tee_stream_thread(stream, path, type, callback = nil)
|
209
219
|
file, out = Misc.tee_stream(stream)
|
210
220
|
|
211
|
-
saver_thread = Thread.new(Thread.current
|
221
|
+
saver_thread = Thread.new(Thread.current) do |parent|
|
212
222
|
begin
|
213
223
|
Thread.current["name"] = "file saver: " + path
|
214
224
|
Misc.lock(path) do
|
@@ -216,11 +226,11 @@ module Persist
|
|
216
226
|
end
|
217
227
|
rescue Aborted
|
218
228
|
Log.error "Persist stream thread aborted: #{ Log.color :blue, path }"
|
219
|
-
|
229
|
+
file.abort if file.respond_to? :abort
|
220
230
|
rescue Exception
|
221
231
|
Log.error "Persist stream thread exception: #{ Log.color :blue, path }"
|
222
232
|
Log.exception $!
|
223
|
-
|
233
|
+
file.abort if file.respond_to? :abort
|
224
234
|
parent.raise $!
|
225
235
|
end
|
226
236
|
end
|
@@ -290,8 +300,119 @@ module Persist
|
|
290
300
|
alias tee_stream tee_stream_thread
|
291
301
|
end
|
292
302
|
|
303
|
+
def self.get_result(path, type, persist_options, lockfile, &block)
|
304
|
+
res = yield
|
305
|
+
|
306
|
+
if persist_options[:no_load] == :stream
|
307
|
+
case res
|
308
|
+
when IO
|
309
|
+
res = tee_stream(res, path, type, res.respond_to?(:callback)? res.callback : nil)
|
310
|
+
ConcurrentStream.setup res do
|
311
|
+
begin
|
312
|
+
lockfile.unlock if lockfile.locked?
|
313
|
+
rescue
|
314
|
+
Log.exception $!
|
315
|
+
Log.warn "Lockfile exception: " << $!.message
|
316
|
+
end
|
317
|
+
end
|
318
|
+
res.abort_callback = Proc.new do
|
319
|
+
begin
|
320
|
+
lockfile.unlock if lockfile.locked?
|
321
|
+
rescue
|
322
|
+
Log.exception $!
|
323
|
+
Log.warn "Lockfile exception: " << $!.message
|
324
|
+
end
|
325
|
+
end
|
326
|
+
raise KeepLocked.new res
|
327
|
+
when TSV::Dumper
|
328
|
+
res = tee_stream(res.stream, path, type, res.respond_to?(:callback)? res.callback : nil)
|
329
|
+
ConcurrentStream.setup res do
|
330
|
+
begin
|
331
|
+
lockfile.unlock
|
332
|
+
rescue
|
333
|
+
Log.exception $!
|
334
|
+
Log.warn "Lockfile exception: " << $!.message
|
335
|
+
end
|
336
|
+
end
|
337
|
+
res.abort_callback = Proc.new do
|
338
|
+
begin
|
339
|
+
lockfile.unlock
|
340
|
+
rescue
|
341
|
+
Log.exception $!
|
342
|
+
Log.warn "Lockfile exception: " << $!.message
|
343
|
+
end
|
344
|
+
end
|
345
|
+
raise KeepLocked.new res
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
case res
|
350
|
+
when IO
|
351
|
+
begin
|
352
|
+
res = case
|
353
|
+
when :array
|
354
|
+
res.read.split "\n"
|
355
|
+
when :tsv
|
356
|
+
TSV.open(res)
|
357
|
+
else
|
358
|
+
res.read
|
359
|
+
end
|
360
|
+
res.join if res.respond_to? :join
|
361
|
+
rescue
|
362
|
+
res.abort if res.respond_to? :abort
|
363
|
+
raise $!
|
364
|
+
end
|
365
|
+
when (defined? TSV and TSV::Dumper)
|
366
|
+
begin
|
367
|
+
io = res.stream
|
368
|
+
res = TSV.open(io)
|
369
|
+
io.join if io.respond_to? :join
|
370
|
+
rescue
|
371
|
+
io.abort if io.respond_to? :abort
|
372
|
+
raise $!
|
373
|
+
end
|
374
|
+
end
|
375
|
+
res
|
376
|
+
end
|
377
|
+
|
378
|
+
def self.persist_file(path, type, persist_options, &block)
|
379
|
+
|
380
|
+
if is_persisted?(path, persist_options)
|
381
|
+
Log.low "Persist up-to-date: #{ path } - #{Misc.fingerprint persist_options}"
|
382
|
+
return path if persist_options[:no_load]
|
383
|
+
return load_file(path, type)
|
384
|
+
end
|
385
|
+
|
386
|
+
begin
|
387
|
+
|
388
|
+
lock_filename = Persist.persistence_path(path + '.persist', {:dir => Persist.lock_dir})
|
389
|
+
Misc.lock lock_filename do |lockfile|
|
390
|
+
|
391
|
+
if is_persisted?(path, persist_options)
|
392
|
+
Log.low "Persist up-to-date (suddenly): #{ path } - #{Misc.fingerprint persist_options}"
|
393
|
+
return path if persist_options[:no_load]
|
394
|
+
return load_file(path, type)
|
395
|
+
end
|
396
|
+
|
397
|
+
Log.medium "Persist create: #{ path } - #{type} #{Misc.fingerprint persist_options}"
|
398
|
+
|
399
|
+
res = get_result(path, type, persist_options, lockfile, &block)
|
400
|
+
|
401
|
+
Misc.lock(path) do
|
402
|
+
save_file(path, type, res)
|
403
|
+
end
|
404
|
+
|
405
|
+
persist_options[:no_load] ? path : res
|
406
|
+
end
|
293
407
|
|
294
|
-
|
408
|
+
rescue
|
409
|
+
Log.error "Error in persist: #{path}#{Open.exists?(path) ? Log.color(:red, " Erasing") : ""}"
|
410
|
+
FileUtils.rm path if Open.exists? path
|
411
|
+
raise $!
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
def self.persist(name, type = nil, persist_options = {}, &block)
|
295
416
|
type ||= :marshal
|
296
417
|
|
297
418
|
return (persist_options[:repo] || Persist::MEMORY)[persist_options[:file]] ||= yield if type ==:memory and persist_options[:file] and persist_options[:persist] and persist_options[:persist] != :update
|
@@ -388,110 +509,7 @@ module Persist
|
|
388
509
|
end
|
389
510
|
|
390
511
|
else
|
391
|
-
|
392
|
-
if is_persisted?(path, persist_options)
|
393
|
-
Log.low "Persist up-to-date: #{ path } - #{Misc.fingerprint persist_options}"
|
394
|
-
return path if persist_options[:no_load]
|
395
|
-
return load_file(path, type)
|
396
|
-
end
|
397
|
-
|
398
|
-
begin
|
399
|
-
|
400
|
-
lock_filename = Persist.persistence_path(path + '.persist', {:dir => Persist.lock_dir})
|
401
|
-
Misc.lock lock_filename do |lockfile|
|
402
|
-
|
403
|
-
if is_persisted?(path, persist_options)
|
404
|
-
Log.low "Persist up-to-date (suddenly): #{ path } - #{Misc.fingerprint persist_options}"
|
405
|
-
return path if persist_options[:no_load]
|
406
|
-
return load_file(path, type)
|
407
|
-
end
|
408
|
-
|
409
|
-
Log.medium "Persist create: #{ path } - #{Misc.fingerprint persist_options}"
|
410
|
-
|
411
|
-
res = yield
|
412
|
-
|
413
|
-
if persist_options[:no_load] == :stream
|
414
|
-
case res
|
415
|
-
when IO
|
416
|
-
res = tee_stream(res, path, type, res.respond_to?(:callback)? res.callback : nil)
|
417
|
-
ConcurrentStream.setup res do
|
418
|
-
begin
|
419
|
-
lockfile.unlock if lockfile.locked?
|
420
|
-
rescue
|
421
|
-
Log.exception $!
|
422
|
-
Log.warn "Lockfile exception: " << $!.message
|
423
|
-
end
|
424
|
-
end
|
425
|
-
res.abort_callback = Proc.new do
|
426
|
-
begin
|
427
|
-
lockfile.unlock if lockfile.locked?
|
428
|
-
rescue
|
429
|
-
Log.exception $!
|
430
|
-
Log.warn "Lockfile exception: " << $!.message
|
431
|
-
end
|
432
|
-
end
|
433
|
-
raise KeepLocked.new res
|
434
|
-
when TSV::Dumper
|
435
|
-
res = tee_stream(res.stream, path, type, res.respond_to?(:callback)? res.callback : nil)
|
436
|
-
ConcurrentStream.setup res do
|
437
|
-
begin
|
438
|
-
lockfile.unlock
|
439
|
-
rescue
|
440
|
-
Log.exception $!
|
441
|
-
Log.warn "Lockfile exception: " << $!.message
|
442
|
-
end
|
443
|
-
end
|
444
|
-
res.abort_callback = Proc.new do
|
445
|
-
begin
|
446
|
-
lockfile.unlock
|
447
|
-
rescue
|
448
|
-
Log.exception $!
|
449
|
-
Log.warn "Lockfile exception: " << $!.message
|
450
|
-
end
|
451
|
-
end
|
452
|
-
raise KeepLocked.new res
|
453
|
-
end
|
454
|
-
end
|
455
|
-
|
456
|
-
case res
|
457
|
-
when IO
|
458
|
-
begin
|
459
|
-
res = case
|
460
|
-
when :array
|
461
|
-
res.read.split "\n"
|
462
|
-
when :tsv
|
463
|
-
TSV.open(res)
|
464
|
-
else
|
465
|
-
res.read
|
466
|
-
end
|
467
|
-
res.join if res.respond_to? :join
|
468
|
-
rescue
|
469
|
-
res.abort if res.respond_to? :abort
|
470
|
-
raise $!
|
471
|
-
end
|
472
|
-
when (defined? TSV and TSV::Dumper)
|
473
|
-
begin
|
474
|
-
io = res.stream
|
475
|
-
res = TSV.open(io)
|
476
|
-
io.join if io.respond_to? :join
|
477
|
-
rescue
|
478
|
-
io.abort if io.respond_to? :abort
|
479
|
-
raise $!
|
480
|
-
end
|
481
|
-
end
|
482
|
-
|
483
|
-
Misc.lock(path) do
|
484
|
-
save_file(path, type, res)
|
485
|
-
end
|
486
|
-
|
487
|
-
persist_options[:no_load] ? path : res
|
488
|
-
end
|
489
|
-
|
490
|
-
rescue
|
491
|
-
Log.error "Error in persist: #{path}#{Open.exists?(path) ? Log.color(:red, " Erasing") : ""}"
|
492
|
-
FileUtils.rm path if Open.exists? path
|
493
|
-
raise $!
|
494
|
-
end
|
512
|
+
persist_file(path, type, persist_options, &block)
|
495
513
|
end
|
496
514
|
|
497
515
|
end
|
data/lib/rbbt/tsv/dumper.rb
CHANGED
@@ -13,8 +13,8 @@ module TSV
|
|
13
13
|
def self.stream_name(obj)
|
14
14
|
filename_obj = obj.respond_to?(:filename) ? obj.filename : nil
|
15
15
|
filename_obj ||= obj.respond_to?(:path) ? obj.path : nil
|
16
|
-
stream_obj = obj_stream(obj)
|
17
|
-
filename_obj.nil? ? stream_obj
|
16
|
+
stream_obj = obj_stream(obj) || obj
|
17
|
+
filename_obj.nil? ? Misc.fingerprint(stream_obj) : filename_obj + "(#{Misc.fingerprint(stream_obj)})"
|
18
18
|
end
|
19
19
|
|
20
20
|
def self.report(msg, obj, into)
|
@@ -23,71 +23,101 @@ module TSV
|
|
23
23
|
Log.error "#{ msg } #{stream_name(obj)} -> #{stream_name(into)}"
|
24
24
|
end
|
25
25
|
|
26
|
+
#{{{ TRAVERSE OBJECTS
|
27
|
+
|
26
28
|
def self.traverse_tsv(tsv, options = {}, &block)
|
27
|
-
callback = Misc.process_options options, :callback
|
29
|
+
callback, bar, join = Misc.process_options options, :callback, :bar, :join
|
28
30
|
|
29
31
|
if callback
|
30
32
|
tsv.through options[:key_field], options[:fields] do |k,v|
|
31
|
-
|
33
|
+
begin
|
34
|
+
callback.call yield(k,v)
|
35
|
+
ensure
|
36
|
+
bar.tick if bar
|
37
|
+
end
|
32
38
|
end
|
33
39
|
else
|
34
40
|
tsv.through options[:key_field], options[:fields] do |k,v|
|
35
|
-
|
41
|
+
begin
|
42
|
+
yield k,v
|
43
|
+
ensure
|
44
|
+
bar.tick if bar
|
45
|
+
end
|
36
46
|
end
|
37
47
|
end
|
48
|
+
join.call if join
|
38
49
|
end
|
39
50
|
|
40
51
|
def self.traverse_hash(hash, options = {}, &block)
|
41
|
-
callback = Misc.process_options options, :callback
|
52
|
+
callback, bar, join = Misc.process_options options, :callback, :bar, :join
|
42
53
|
|
43
54
|
if callback
|
44
55
|
hash.each do |k,v|
|
45
|
-
|
56
|
+
begin
|
57
|
+
callback.call yield(k,v)
|
58
|
+
ensure
|
59
|
+
bar.tick if bar
|
60
|
+
end
|
46
61
|
end
|
47
62
|
else
|
48
63
|
hash.each do |k,v|
|
49
|
-
|
64
|
+
begin
|
65
|
+
yield k,v
|
66
|
+
ensure
|
67
|
+
bar.tick if bar
|
68
|
+
end
|
50
69
|
end
|
51
70
|
end
|
71
|
+
join.call if join
|
52
72
|
end
|
53
73
|
|
54
74
|
def self.traverse_array(array, options = {}, &block)
|
55
|
-
callback = Misc.process_options options, :callback
|
75
|
+
callback, bar, join = Misc.process_options options, :callback, :bar, :join
|
56
76
|
|
57
77
|
if callback
|
58
78
|
array.each do |e|
|
59
|
-
|
60
|
-
|
79
|
+
begin
|
80
|
+
callback.call yield(e)
|
81
|
+
ensure
|
82
|
+
bar.tick if bar
|
83
|
+
end
|
61
84
|
end
|
62
85
|
else
|
63
86
|
array.each do |e|
|
64
|
-
|
87
|
+
begin
|
88
|
+
yield e
|
89
|
+
ensure
|
90
|
+
bar.tick if bar
|
91
|
+
end
|
65
92
|
end
|
66
93
|
end
|
94
|
+
join.call if join
|
67
95
|
end
|
68
96
|
|
69
97
|
def self.traverse_io_array(io, options = {}, &block)
|
70
|
-
callback = Misc.process_options options, :callback
|
98
|
+
callback, bar, join = Misc.process_options options, :callback, :bar, :join
|
71
99
|
if callback
|
72
100
|
while line = io.gets
|
73
|
-
|
74
|
-
|
101
|
+
begin
|
102
|
+
callback.call yield line.strip
|
103
|
+
ensure
|
104
|
+
bar.tick if bar
|
105
|
+
end
|
75
106
|
end
|
76
107
|
else
|
77
108
|
while line = io.gets
|
78
109
|
yield line.strip
|
79
110
|
end
|
80
111
|
end
|
112
|
+
join.call if join
|
81
113
|
end
|
82
114
|
|
83
115
|
def self.traverse_io(io, options = {}, &block)
|
84
|
-
|
85
|
-
callback = Misc.process_options options, :callback
|
116
|
+
callback, bar, join = Misc.process_options options, :callback, :bar, :join
|
86
117
|
begin
|
87
118
|
if callback
|
88
119
|
TSV::Parser.traverse(io, options) do |k,v|
|
89
|
-
|
90
|
-
callback.call res
|
120
|
+
callback.call yield k, v
|
91
121
|
end
|
92
122
|
else
|
93
123
|
TSV::Parser.traverse(io, options, &block)
|
@@ -96,10 +126,10 @@ module TSV
|
|
96
126
|
Log.error "Traverse IO error"
|
97
127
|
raise $!
|
98
128
|
end
|
129
|
+
join.call if join
|
99
130
|
end
|
100
131
|
|
101
132
|
def self.traverse_obj(obj, options = {}, &block)
|
102
|
-
filename = obj.filename if obj.respond_to? :filename
|
103
133
|
if options[:type] == :keys
|
104
134
|
options[:fields] = []
|
105
135
|
options[:type] = :single
|
@@ -114,8 +144,7 @@ module TSV
|
|
114
144
|
callback = Misc.process_options options, :callback
|
115
145
|
if callback
|
116
146
|
obj.traverse(options) do |k,v|
|
117
|
-
|
118
|
-
callback.call res
|
147
|
+
callback.call yield k, v
|
119
148
|
end
|
120
149
|
else
|
121
150
|
obj.traverse(options, &block)
|
@@ -195,9 +224,8 @@ module TSV
|
|
195
224
|
|
196
225
|
def self.traverse_cpus(num, obj, options, &block)
|
197
226
|
begin
|
198
|
-
|
199
|
-
|
200
|
-
q = RbbtProcessQueue.new num, cleanup
|
227
|
+
callback, cleanup, join = Misc.process_options options, :callback, :cleanup, :join
|
228
|
+
q = RbbtProcessQueue.new num, cleanup, join
|
201
229
|
|
202
230
|
q.callback &callback
|
203
231
|
q.init &block
|
@@ -209,8 +237,16 @@ module TSV
|
|
209
237
|
end
|
210
238
|
|
211
239
|
thread.join
|
240
|
+
rescue Interrupt, Aborted
|
241
|
+
Log.error "Aborted traversal in CPUs for #{stream_name(obj) || Misc.fingerprint(obj)}"
|
242
|
+
stream = obj_stream(obj)
|
243
|
+
stream.abort if stream.respond_to? :abort
|
244
|
+
stream = obj_stream(options[:into])
|
245
|
+
stream.abort if stream.respond_to? :abort
|
246
|
+
q.abort
|
247
|
+
raise $!
|
212
248
|
rescue Exception
|
213
|
-
Log.error "Exception
|
249
|
+
Log.error "Exception during traversal in CPUs for #{stream_name(obj) || Misc.fingerprint(obj)}"
|
214
250
|
Log.exception $!
|
215
251
|
|
216
252
|
stream = obj_stream(obj)
|
@@ -295,11 +331,13 @@ module TSV
|
|
295
331
|
close_streams.concat(get_streams_to_close(obj))
|
296
332
|
options[:close_streams] = close_streams
|
297
333
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
334
|
+
if close_streams and close_streams.any?
|
335
|
+
options[:cleanup] = Proc.new do
|
336
|
+
close_streams.uniq.each do |s|
|
337
|
+
s.close unless s.closed?
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
303
341
|
|
304
342
|
traverse_cpus cpus, obj, options, &block
|
305
343
|
end
|
@@ -323,13 +361,8 @@ module TSV
|
|
323
361
|
end
|
324
362
|
|
325
363
|
def self.traverse(obj, options = {}, &block)
|
326
|
-
threads = Misc.process_options options, :threads
|
327
|
-
cpus = Misc.process_options options, :cpus
|
328
364
|
into = options[:into]
|
329
365
|
|
330
|
-
threads = nil if threads and threads.to_i <= 1
|
331
|
-
cpus = nil if cpus and cpus.to_i <= 1
|
332
|
-
|
333
366
|
if into == :stream
|
334
367
|
sout = Misc.open_pipe false, false do |sin|
|
335
368
|
begin
|
@@ -342,13 +375,45 @@ module TSV
|
|
342
375
|
return sout
|
343
376
|
end
|
344
377
|
|
378
|
+
threads = Misc.process_options options, :threads
|
379
|
+
cpus = Misc.process_options options, :cpus
|
380
|
+
threads = nil if threads and threads.to_i <= 1
|
381
|
+
cpus = nil if cpus and cpus.to_i <= 1
|
382
|
+
|
383
|
+
bar = Misc.process_options options, :bar
|
384
|
+
bar ||= Misc.process_options options, :progress
|
385
|
+
options[:bar] = case bar
|
386
|
+
when String
|
387
|
+
Log::ProgressBar.new_bar(nil, {:desc => bar})
|
388
|
+
when TrueClass
|
389
|
+
Log::ProgressBar.new_bar(nil, nil)
|
390
|
+
when Fixnum
|
391
|
+
Log::ProgressBar.new_bar(bar)
|
392
|
+
when Hash
|
393
|
+
max = Misc.process_options bar, :max
|
394
|
+
Log::ProgressBar.new_bar(max, bar)
|
395
|
+
else
|
396
|
+
bar
|
397
|
+
end
|
398
|
+
|
345
399
|
if into
|
400
|
+
bar = Misc.process_options options, :bar
|
401
|
+
|
402
|
+
options[:join] = Proc.new do
|
403
|
+
Log::ProgressBar.remove_bar(bar)
|
404
|
+
end if bar
|
405
|
+
|
346
406
|
options[:callback] = Proc.new do |e|
|
347
407
|
begin
|
348
408
|
store_into into, e
|
409
|
+
rescue Aborted
|
410
|
+
Log.error "Traversal info #{stream_name into} aborted"
|
411
|
+
raise $!
|
349
412
|
rescue Exception
|
350
413
|
Log.exception $!
|
351
414
|
raise $!
|
415
|
+
ensure
|
416
|
+
bar.tick if bar
|
352
417
|
end
|
353
418
|
end
|
354
419
|
|