db_sucker 3.0.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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/CHANGELOG.md +45 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +193 -0
  7. data/Rakefile +1 -0
  8. data/VERSION +1 -0
  9. data/bin/db_sucker +12 -0
  10. data/bin/db_sucker.sh +14 -0
  11. data/db_sucker.gemspec +29 -0
  12. data/doc/config_example.rb +53 -0
  13. data/doc/container_example.yml +150 -0
  14. data/lib/db_sucker/adapters/mysql2.rb +103 -0
  15. data/lib/db_sucker/application/colorize.rb +28 -0
  16. data/lib/db_sucker/application/container/accessors.rb +60 -0
  17. data/lib/db_sucker/application/container/ssh.rb +225 -0
  18. data/lib/db_sucker/application/container/validations.rb +53 -0
  19. data/lib/db_sucker/application/container/variation/accessors.rb +45 -0
  20. data/lib/db_sucker/application/container/variation/helpers.rb +21 -0
  21. data/lib/db_sucker/application/container/variation/worker_api.rb +65 -0
  22. data/lib/db_sucker/application/container/variation.rb +60 -0
  23. data/lib/db_sucker/application/container.rb +70 -0
  24. data/lib/db_sucker/application/container_collection.rb +47 -0
  25. data/lib/db_sucker/application/core.rb +222 -0
  26. data/lib/db_sucker/application/dispatch.rb +364 -0
  27. data/lib/db_sucker/application/evented_resultset.rb +149 -0
  28. data/lib/db_sucker/application/fake_channel.rb +22 -0
  29. data/lib/db_sucker/application/output_helper.rb +197 -0
  30. data/lib/db_sucker/application/sklaven_treiber/log_spool.rb +57 -0
  31. data/lib/db_sucker/application/sklaven_treiber/worker/accessors.rb +105 -0
  32. data/lib/db_sucker/application/sklaven_treiber/worker/core.rb +168 -0
  33. data/lib/db_sucker/application/sklaven_treiber/worker/helpers.rb +144 -0
  34. data/lib/db_sucker/application/sklaven_treiber/worker/io/base.rb +240 -0
  35. data/lib/db_sucker/application/sklaven_treiber/worker/io/file_copy.rb +81 -0
  36. data/lib/db_sucker/application/sklaven_treiber/worker/io/file_gunzip.rb +58 -0
  37. data/lib/db_sucker/application/sklaven_treiber/worker/io/file_import_sql.rb +80 -0
  38. data/lib/db_sucker/application/sklaven_treiber/worker/io/file_shasum.rb +49 -0
  39. data/lib/db_sucker/application/sklaven_treiber/worker/io/pv_wrapper.rb +73 -0
  40. data/lib/db_sucker/application/sklaven_treiber/worker/io/sftp_download.rb +57 -0
  41. data/lib/db_sucker/application/sklaven_treiber/worker/io/throughput.rb +219 -0
  42. data/lib/db_sucker/application/sklaven_treiber/worker/routines.rb +313 -0
  43. data/lib/db_sucker/application/sklaven_treiber/worker.rb +48 -0
  44. data/lib/db_sucker/application/sklaven_treiber.rb +281 -0
  45. data/lib/db_sucker/application/slot_pool.rb +137 -0
  46. data/lib/db_sucker/application/tie.rb +25 -0
  47. data/lib/db_sucker/application/window/core.rb +185 -0
  48. data/lib/db_sucker/application/window/dialog.rb +142 -0
  49. data/lib/db_sucker/application/window/keypad/core.rb +85 -0
  50. data/lib/db_sucker/application/window/keypad.rb +174 -0
  51. data/lib/db_sucker/application/window/prompt.rb +124 -0
  52. data/lib/db_sucker/application/window.rb +329 -0
  53. data/lib/db_sucker/application.rb +168 -0
  54. data/lib/db_sucker/patches/beta-warning.rb +374 -0
  55. data/lib/db_sucker/patches/developer.rb +29 -0
  56. data/lib/db_sucker/patches/net-sftp.rb +20 -0
  57. data/lib/db_sucker/patches/thread-count.rb +30 -0
  58. data/lib/db_sucker/version.rb +4 -0
  59. data/lib/db_sucker.rb +81 -0
  60. metadata +217 -0
@@ -0,0 +1,149 @@
1
+ module DbSucker
2
+ class Application
3
+ class EventedResultset
4
+ SetAlreadyClosedError = Class.new(::RuntimeError)
5
+ include Enumerable
6
+
7
+ def initialize
8
+ @store = []
9
+ @monitor = Monitor.new
10
+ @closed = false
11
+ @close_signal = @monitor.new_cond
12
+ @value_signal = @monitor.new_cond
13
+ end
14
+
15
+ def sync &block
16
+ @monitor.synchronize(&block)
17
+ end
18
+
19
+ def enq data, group = nil
20
+ sync do
21
+ raise SetAlreadyClosedError, "failed to enqueue data: resultset is already closed" if closed?
22
+ @store << [group.try(:to_sym), data]
23
+ @value_signal.broadcast
24
+ end
25
+ end
26
+
27
+ def push *args
28
+ sync do
29
+ args.each {|a| enq(a) }
30
+ @store
31
+ end
32
+ end
33
+ alias_method :<<, :push
34
+
35
+ def close!
36
+ sync do
37
+ @closed = true
38
+ @value_signal.broadcast
39
+ @close_signal.broadcast
40
+ end
41
+ true
42
+ end
43
+
44
+ def closed?
45
+ sync { @closed }
46
+ end
47
+
48
+ def empty?
49
+ sync { @store.empty? }
50
+ end
51
+
52
+ def wait
53
+ sync do
54
+ return if closed?
55
+ @close_signal.wait
56
+ end
57
+ true
58
+ end
59
+
60
+ def for_group group
61
+ @store.map{|grp, data| grp == group.try(:to_sym) ? data : nil }.compact
62
+ end
63
+
64
+ def each &block
65
+ wait
66
+ if block
67
+ @store.each do |group, data|
68
+ block.call(data)
69
+ end
70
+ else
71
+ @store.map(&:second)
72
+ end
73
+ end
74
+
75
+ def join *a
76
+ wait
77
+ each.join(*a)
78
+ end
79
+
80
+ def [] which
81
+ wait
82
+ @store[which].try(:last)
83
+ end
84
+
85
+ def eachx &block
86
+ wait
87
+ @store.each(&block)
88
+ end
89
+
90
+ def each_line &block
91
+ Thread.current[self.object_id.to_s] = nil
92
+ loop do
93
+ data = gets
94
+ if !data
95
+ break if closed?
96
+ next
97
+ end
98
+ block.call(data)
99
+ end
100
+ ensure
101
+ Thread.current[self.object_id.to_s] = nil
102
+ end
103
+
104
+ def each_linex &block
105
+ Thread.current[self.object_id.to_s] = nil
106
+ loop do
107
+ group, entry = getx
108
+ unless entry
109
+ break if closed?
110
+ next
111
+ end
112
+ block.call(group, entry)
113
+ end
114
+ ensure
115
+ Thread.current[self.object_id.to_s] = nil
116
+ end
117
+
118
+ def gets
119
+ sync do
120
+ Thread.current[self.object_id.to_s] ||= -1
121
+ if !closed? && !@store[Thread.current[self.object_id.to_s]+1]
122
+ @value_signal.wait
123
+ end
124
+ if @store[Thread.current[self.object_id.to_s]+1]
125
+ Thread.current[self.object_id.to_s] += 1
126
+ @store[Thread.current[self.object_id.to_s]].try(:last)
127
+ else
128
+ false
129
+ end
130
+ end
131
+ end
132
+
133
+ def getx
134
+ sync do
135
+ Thread.current[self.object_id.to_s] ||= -1
136
+ if !closed? && !@store[Thread.current[self.object_id.to_s]+1]
137
+ @value_signal.wait
138
+ end
139
+ if @store[Thread.current[self.object_id.to_s]+1]
140
+ Thread.current[self.object_id.to_s] += 1
141
+ @store[Thread.current[self.object_id.to_s]]
142
+ else
143
+ false
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,22 @@
1
+ module DbSucker
2
+ class Application
3
+ class FakeChannel
4
+ def initialize &block
5
+ @storage = {}
6
+ @termination = block
7
+ end
8
+
9
+ def [] k
10
+ @storage[k]
11
+ end
12
+
13
+ def []= k, v
14
+ @storage[k] = v
15
+ end
16
+
17
+ def alive?
18
+ !@termination.try(:call, self)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,197 @@
1
+ module DbSucker
2
+ class Application
3
+ module OutputHelper
4
+ def self.hook ctx
5
+ [:puts, :print, :warn, :debug, :log, :warning, :error, :abort, :decolorize, :db_table_listing, :print_db_table_list, :decolorize, :render_table, :f_percentage, :human_bytes, :human_number, :human_percentage, :human_seconds, :human_seconds2, :rll, :c, :strbool].each do |meth|
6
+ ctx.__send__(:define_method, meth) do |*a|
7
+ Thread.main[:app].__send__(meth, *a)
8
+ end
9
+ end
10
+ end
11
+
12
+ def puts *a
13
+ outsync { @opts[:stdout].send(:puts, *a) }
14
+ end
15
+
16
+ def print *a
17
+ outsync { @opts[:stdout].send(:print, *a) }
18
+ end
19
+
20
+ def warn *a
21
+ outsync { @opts[:stdout].send(:warn, *a) }
22
+ end
23
+
24
+ def debug msg, lvl = 1
25
+ puts c("[DEBUG] #{msg}", :black) if @opts[:debug] && @opts[:debug] >= lvl
26
+ end
27
+
28
+ def log msg
29
+ puts c("#{msg}")
30
+ end
31
+
32
+ def warning msg
33
+ warn c("[WARN] #{msg}", :red)
34
+ end
35
+
36
+ def error msg
37
+ warn c("[ERROR] #{msg}", :red)
38
+ end
39
+
40
+ def abort msg, exit_code = 1
41
+ warn c("[ABORT] #{msg}", :red)
42
+ exit(exit_code) if exit_code
43
+ end
44
+
45
+ def rll
46
+ print "\033[A"
47
+ print "\033[2K\r"
48
+ end
49
+
50
+ def render_table table, headers = []
51
+ [].tap do |r|
52
+ col_sizes = table.map{|col| col.map{|i| decolorize(i.to_s) }.map(&:length).max }
53
+ headers.map{|i| decolorize(i.to_s) }.map(&:length).each_with_index do |length, header|
54
+ col_sizes[header] = [col_sizes[header] || 0, length || 0].max
55
+ end
56
+
57
+ # header
58
+ if headers.any?
59
+ r << [].tap do |line|
60
+ col_sizes.count.times do |col|
61
+ line << headers[col].ljust(col_sizes[col] + (headers[col].length - decolorize(headers[col]).length))
62
+ end
63
+ end.join(" | ")
64
+ r << "".ljust(col_sizes.inject(&:+) + ((col_sizes.count - 1) * 3), "-")
65
+ end
66
+
67
+ # records
68
+ table[0].count.times do |row|
69
+ r << [].tap do |line|
70
+ col_sizes.count.times do |col|
71
+ line << "#{table[col][row]}".ljust(col_sizes[col] + (table[col][row].to_s.length - decolorize(table[col][row]).length))
72
+ end
73
+ end.join(" | ")
74
+ end
75
+ end
76
+ end
77
+
78
+ def db_table_listing col
79
+ col.each do |id, ccfg|
80
+ log ""
81
+ log "====================="
82
+ log "=== #{c id, :magenta}"
83
+ log "====================="
84
+ a, b = [], []
85
+ ccfg.variations.map do |name, vd|
86
+ a << c(name, :blue)
87
+ b << (vd.label.present? ? c(vd.label) : c("no label", :black))
88
+ end
89
+ render_table([a, b], [c("variation"), c("label")]).each{|l| log("#{l}") }
90
+ end
91
+ end
92
+
93
+ def print_db_table_list host, dbs
94
+ log ""
95
+ log c(host, :red)
96
+
97
+ dbs.each_with_index do |db, i|
98
+ if db.is_a?(Array)
99
+ d = c(db[0], :magenta) << c(" (#{db[1].length})", :black)
100
+ if i == dbs.count - 1
101
+ log("#{db[1].any? ? "├──" : "└──"} #{d}")
102
+ else
103
+ log("├── #{d}")
104
+ end
105
+
106
+ table = render_table([db[1].map{|r| c(r[1], :cyan) }, db[1].map{|r| c(r[0], :green) }])
107
+ table.each_with_index do |l, i2|
108
+ if i2 == table.count - 1
109
+ log("│ └── #{l}")
110
+ else
111
+ log("│ ├── #{l}")
112
+ end
113
+ end
114
+ else
115
+ log " #{c db, :blue}"
116
+ end
117
+ end
118
+ end
119
+
120
+ def strbool v
121
+ v = true if ["true", "t", "1", "y", "yes", "on"].include?(v)
122
+ v = false if ["false", "f", "0", "n", "no", "off"].include?(v)
123
+ v
124
+ end
125
+
126
+ def f_percentage have, total, nn = 2
127
+ human_percentage(total == 0 ? 100 : have == 0 ? 0 : (have.to_d / total.to_d * 100.to_d), nn)
128
+ end
129
+
130
+ def human_bytes bytes
131
+ return false unless bytes
132
+ {
133
+ 'B' => 1024,
134
+ 'KB' => 1024 * 1024,
135
+ 'MB' => 1024 * 1024 * 1024,
136
+ 'GB' => 1024 * 1024 * 1024 * 1024,
137
+ 'TB' => 1024 * 1024 * 1024 * 1024 * 1024
138
+ }.each_pair { |e, s| return "#{"%.2f" % (bytes.to_d / (s / 1024)).round(2)} #{e}" if bytes < s }
139
+ end
140
+
141
+ def human_number(n)
142
+ n.to_s.reverse.gsub(/...(?=.)/,'\&,').reverse
143
+ end
144
+
145
+ def human_percentage(n, nn = 2)
146
+ "%.#{nn}f%%" % n
147
+ end
148
+
149
+ def human_seconds secs, sprec = 0
150
+ t_minute = 60
151
+ t_hour = t_minute * 60
152
+ t_day = t_hour * 24
153
+ t_week = t_day * 7
154
+ t_month = t_day * 30
155
+ t_year = t_month * 12
156
+ "".tap do |r|
157
+ if secs >= t_year
158
+ r << "#{"%i" % (secs / t_year)}y "
159
+ secs = secs % t_year
160
+ end
161
+
162
+ if secs >= t_month
163
+ r << "#{"%i" % (secs / t_month)}m "
164
+ secs = secs % t_month
165
+ end
166
+
167
+ if secs >= t_week
168
+ r << "#{"%i" % (secs / t_week)}w "
169
+ secs = secs % t_week
170
+ end
171
+
172
+ if secs >= t_day || !r.blank?
173
+ r << "#{"%i" % (secs / t_day)}d "
174
+ secs = secs % t_day
175
+ end
176
+
177
+ if secs >= t_hour || !r.blank?
178
+ r << "#{"%i" % (secs / t_hour)}h "
179
+ secs = secs % t_hour
180
+ end
181
+
182
+ if secs >= t_minute || !r.blank?
183
+ r << "#{"%i" % (secs / t_minute)}m "
184
+ secs = secs % t_minute
185
+ end
186
+
187
+ r << "#{"%.#{sprec}f" % secs.round(sprec)}s" unless r.include?("d")
188
+ end.strip
189
+ end
190
+
191
+ def human_seconds2 secs
192
+ return "?:¿?:¿?" if secs.try(:infinite?)
193
+ Time.at(secs.round).utc.strftime("%k:%M:%S").strip
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,57 @@
1
+ module DbSucker
2
+ class Application
3
+ class SklavenTreiber
4
+ class LogSpool
5
+ attr_reader :original, :spool
6
+
7
+ def initialize original
8
+ @original = original
9
+ @enabled = true
10
+ @spool = []
11
+ @monitor = Monitor.new
12
+ end
13
+
14
+ def sync
15
+ @monitor.synchronize { yield }
16
+ end
17
+
18
+ def enable
19
+ sync do
20
+ @enabled = true
21
+ end
22
+ end
23
+
24
+ def clear
25
+ sync { @spool.clear }
26
+ end
27
+
28
+ def disable void_spool = false, &block
29
+ sync do
30
+ @enabled = false
31
+ (void_spool && clear) || (block && spooldown(&block))
32
+ end
33
+ end
34
+
35
+ def spooldown
36
+ sync do
37
+ while e = @spool.shift
38
+ yield(e + [original])
39
+ end
40
+ end
41
+ end
42
+
43
+ def puts *args
44
+ sync { @enabled ? (@spool << [:puts, args, Time.current]) : @original.send(:puts, *args) }
45
+ end
46
+
47
+ def print *args
48
+ sync { @enabled ? (@spool << [:print, args, Time.current]) : @original.send(:print, *args) }
49
+ end
50
+
51
+ def warn *args
52
+ sync { @enabled ? (@spool << [:warn, args, Time.current]) : @original.send(:warn, *args) }
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,105 @@
1
+ module DbSucker
2
+ class Application
3
+ class SklavenTreiber
4
+ class Worker
5
+ module Accessors
6
+ def pending?
7
+ @state == :pending
8
+ end
9
+
10
+ def done?
11
+ succeeded? || failed? || canceled?
12
+ end
13
+
14
+ def sshing?
15
+ @sshing
16
+ end
17
+
18
+ def failed?
19
+ @state == :failed
20
+ end
21
+
22
+ def succeeded?
23
+ @state == :done
24
+ end
25
+
26
+ def canceled?
27
+ @state == :canceled
28
+ end
29
+
30
+ def running?
31
+ @state == :running
32
+ end
33
+
34
+ def paused?
35
+ @state == :paused
36
+ end
37
+
38
+ def pausing?
39
+ @state == :pausing
40
+ end
41
+
42
+ def deferred?
43
+ @deferred
44
+ end
45
+
46
+ def status
47
+ @status
48
+ end
49
+
50
+ def state
51
+ @state
52
+ end
53
+
54
+ def trxid
55
+ sklaventreiber.trxid
56
+ end
57
+
58
+ def app
59
+ sklaventreiber.app
60
+ end
61
+
62
+ def descriptive
63
+ "#{ctn.source["database"]}-#{table}"
64
+ end
65
+
66
+ def identifier
67
+ "#{trxid}_table"
68
+ end
69
+
70
+ def to_s
71
+ "#<#{self.class}:#{self.object_id}-#{descriptive}(#{@state})>"
72
+ end
73
+
74
+ def tmp_filename tmp_suffix = false
75
+ "#{ctn.tmp_path}/#{trxid}_#{ctn.source["database"]}_#{table}.dbsc#{".tmp" if tmp_suffix}"
76
+ end
77
+
78
+ def local_tmp_path
79
+ sklaventreiber.app.core_tmp_path
80
+ end
81
+
82
+ def local_tmp_file file
83
+ "#{local_tmp_path}/#{file}"
84
+ end
85
+
86
+ def spinner_frame
87
+ @spinner_frames.unshift(@spinner_frames.pop)[0]
88
+ end
89
+
90
+ def copy_file_destination dstfile
91
+ d, dt = Time.current.strftime("%Y-%m-%d"), Time.current.strftime("%H-%M-%S")
92
+
93
+ File.expand_path dstfile.dup
94
+ .gsub!(":combined", ":datetime_-_:table")
95
+ .gsub!(":datetime", "#{d}_#{dt}")
96
+ .gsub!(":date", d)
97
+ .gsub!(":time", dt)
98
+ .gsub!(":table", table)
99
+ .gsub!(":id", sklaventreiber.trxid)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,168 @@
1
+ module DbSucker
2
+ class Application
3
+ class SklavenTreiber
4
+ class Worker
5
+ module Core
6
+ def sync
7
+ @monitor.synchronize { yield }
8
+ end
9
+
10
+ def aquire thread
11
+ @thread = thread
12
+ thread[:current_task] = descriptive
13
+ thread[:current_worker] = self
14
+ if m = thread[:managed_worker]
15
+ debug "Consumer thread ##{m} aquired worker #{descriptive}"
16
+ else
17
+ debug "Main thread aquired worker #{descriptive}"
18
+ end
19
+ @status = ["initializing...", "gray"]
20
+ @state = :aquired
21
+ self
22
+ end
23
+
24
+ def pause wait = false
25
+ sync do
26
+ return if done?
27
+ return if @state == :pausing || @state == :paused
28
+ @pause_data = { state_was: @state, signal: @monitor.new_cond }
29
+ if @state == :pending
30
+ @state = :paused
31
+ else
32
+ @state = :pausing
33
+ @pause_data[:signal].wait if wait
34
+ end
35
+ end
36
+ end
37
+
38
+ def _pausepoint
39
+ sync do
40
+ return if !(@state == :pausing || @state == :paused)
41
+ return unless @pause_data
42
+ return unless @thread == Thread.current
43
+ @state = :paused
44
+ @pause_data[:signal].broadcast
45
+ end
46
+ @thread[:paused] = true
47
+ Thread.stop
48
+ @thread[:paused] = false
49
+ _cancelpoint
50
+ end
51
+
52
+ def unpause
53
+ sync do
54
+ return if !(@state == :pausing || @state == :paused)
55
+ return unless @pause_data
56
+ @state = @pause_data[:state_was]
57
+ @pause_data = false
58
+ @thread.wakeup if @thread
59
+ end
60
+ end
61
+
62
+ def cancel! reason = nil, now = false
63
+ return if done?
64
+ @should_cancel = reason || true
65
+ unpause
66
+ sync { _cancelpoint(reason) if pending? || now }
67
+ end
68
+
69
+ def fail! reason, now = false
70
+ @status = ["FAILED(#{@current_perform}) #{reason}", "red"]
71
+ @state = :failed
72
+ throw :abort_execution, true if now
73
+ end
74
+
75
+ def _cancelpoint reason = nil
76
+ if @should_cancel
77
+ reason ||= @should_cancel if @should_cancel.is_a?(String)
78
+ reason ||= @status[0]
79
+ @should_cancel = false
80
+ @state = :canceled
81
+ @status = ["CANCELED#{" (was #{reason.to_s.gsub("[CLOSING] ", "")})" if reason}", "red"]
82
+ throw :abort_execution, true
83
+ true
84
+ elsif @state == :failed
85
+ throw :abort_execution, true
86
+ true
87
+ end
88
+ _pausepoint
89
+ end
90
+
91
+ def priority
92
+ 100 - ({
93
+ running: 50,
94
+ aquired: 50,
95
+ pausing: 50,
96
+ paused: 30,
97
+ canceled: 35,
98
+ pending: 30,
99
+ failed: 20,
100
+ done: 10,
101
+ }[@state] || 0)
102
+ end
103
+
104
+ def run
105
+ @state = :running
106
+ @sshing = true
107
+ @started = Time.current
108
+ @download_state = { state: :idle, offset: 0 }
109
+ @remote_files_to_remove = []
110
+ @local_files_to_remove = []
111
+ @current_perform = nil
112
+
113
+ app.fire(:worker_routine_before_all, self)
114
+ catch :abort_execution do
115
+ perform.each_with_index do |m, i|
116
+ @current_perform = m
117
+ _cancelpoint
118
+ @step = i + 1
119
+ r = catch(:abort_execution) {
120
+ aquire_slots(*app.opts[:routine_pools][m.to_sym]) do
121
+ begin
122
+ r0 = Time.current
123
+ app.fire(:worker_routine_before, self, @current_perform)
124
+ send(:"_#{m}")
125
+ ensure
126
+ app.fire(:worker_routine_after, self, @current_perform)
127
+ @timings[m] = Time.current - r0
128
+ end
129
+ end
130
+ nil
131
+ }
132
+ throw :abort_execution if r
133
+ _cancelpoint
134
+ end
135
+ @status = ["DONE (#{runtime})", "green"]
136
+ end
137
+ rescue StandardError => ex
138
+ @exception = ex
139
+ fail! "#{ex.class}: #{ex.message}"
140
+ Thread.main[:app].notify_exception("SklavenTreiber::Worker encountered an error in `#{@current_perform}' (ctn: #{ctn.name}, var: #{var.name}, db: #{ctn.source["database"]}, table: #{table})", ex)
141
+ rescue Interrupt => ex
142
+ @state = :failed
143
+ ensure
144
+ # cleanup temp files
145
+ ctn.sftp_start do |sftp|
146
+ @remote_files_to_remove.each do |file|
147
+ sftp.remove!(file) rescue false
148
+ end
149
+ end if @remote_files_to_remove.any?
150
+
151
+ # cleanup local temp files
152
+ @local_files_to_remove.each do |file|
153
+ File.unlink(file) rescue false
154
+ end
155
+
156
+ app.fire(:worker_routine_after_all, self)
157
+ @state = :done if !canceled? && !failed?
158
+ @ended = Time.current
159
+ @sshing = false
160
+
161
+ # debug timings
162
+ debug "[Timings(#{table})] all: #{human_seconds(@timings.values.sum, 3)}, #{@timings.map{|a,t| "#{a}: #{human_seconds(t, 3)}" } * ", "}", 50
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end