master_slave_adapter 1.0.0.beta1 → 1.0.0.beta2

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.
@@ -1,4 +1,5 @@
1
1
  require 'active_record'
2
+ require 'active_record/connection_adapters/abstract_adapter'
2
3
  require 'active_record/connection_adapters/master_slave_adapter/circuit_breaker'
3
4
 
4
5
  module ActiveRecord
@@ -31,21 +32,13 @@ module ActiveRecord
31
32
  end
32
33
  end
33
34
 
34
- def on_commit(&blk)
35
- connection.on_commit(&blk) if connection.respond_to? :on_commit
36
- end
37
-
38
- def on_rollback(&blk)
39
- connection.on_rollback(&blk) if connection.respond_to? :on_rollback
40
- end
41
-
42
35
  def master_slave_connection(config)
43
36
  config = massage(config)
44
- name = config.fetch(:connection_adapter)
45
- adapter = "#{name.classify}MasterSlaveAdapter"
37
+ adapter = config.fetch(:connection_adapter)
38
+ name = "#{adapter}_master_slave"
46
39
 
47
- load_adapter("#{name}_master_slave")
48
- ConnectionAdapters.const_get(adapter).new(config, logger)
40
+ load_adapter(name)
41
+ send(:"#{name}_connection", config)
49
42
  end
50
43
 
51
44
  private
@@ -83,418 +76,418 @@ module ActiveRecord
83
76
 
84
77
  module ConnectionAdapters
85
78
  class AbstractAdapter
86
- alias_method :orig_log_info, :log_info
87
- def log_info(sql, name, ms)
88
- connection_name =
89
- [ @config[:name], @config[:host], @config[:port] ].compact.join(":")
90
- orig_log_info sql, "[#{connection_name}] #{name || 'SQL'}", ms
79
+ if instance_methods.map(&:to_sym).include?(:log_info)
80
+ # ActiveRecord v2.x
81
+ alias_method :orig_log_info, :log_info
82
+ def log_info(sql, name, ms)
83
+ orig_log_info(sql, "[#{connection_info}] #{name || 'SQL'}", ms)
84
+ end
85
+ else
86
+ # ActiveRecord v3.x
87
+ alias_method :orig_log, :log
88
+ def log(sql, name = 'SQL', *args, &block)
89
+ orig_log(sql, "[#{connection_info}] #{name || 'SQL'}", *args, &block)
90
+ end
91
+ end
92
+
93
+ private
94
+ def connection_info
95
+ @connection_info ||= @config.values_at(:name, :host, :port).compact.join(':')
91
96
  end
92
97
  end
93
98
 
94
99
  module MasterSlaveAdapter
95
- class Base < AbstractAdapter
96
- def initialize(config, logger)
97
- super(nil, logger)
100
+ def initialize(config, logger)
101
+ super(nil, logger)
102
+
103
+ @config = config
104
+ @connections = {}
105
+ @connections[:master] = connect_to_master
106
+ @connections[:slaves] = @config.fetch(:slaves).map { |cfg| connect(cfg, :slave) }
107
+ @last_seen_slave_clocks = {}
108
+ @disable_connection_test = @config[:disable_connection_test] == 'true'
109
+ @circuit = CircuitBreaker.new(logger)
110
+
111
+ self.current_connection = slave_connection!
112
+ end
98
113
 
99
- @config = config
114
+ # MASTER SLAVE ADAPTER INTERFACE ========================================
100
115
 
101
- @connections = {}
102
- @connections[:master] = connect_to_master
103
- @connections[:slaves] = @config.fetch(:slaves).map { |cfg| connect(cfg, :slave) }
116
+ def with_master
117
+ with(master_connection) { yield }
118
+ end
104
119
 
105
- @disable_connection_test = @config[:disable_connection_test] == 'true'
106
- @circuit = CircuitBreaker.new(logger)
120
+ def with_slave
121
+ with(slave_connection!) { yield }
122
+ end
107
123
 
108
- self.current_connection = slave_connection!
124
+ def with_consistency(clock)
125
+ if clock.nil?
126
+ raise ArgumentError, "consistency must be a valid comparable value"
109
127
  end
110
128
 
111
- # MASTER SLAVE ADAPTER INTERFACE ========================================
112
-
113
- def with_master
114
- with(master_connection) { yield }
115
- end
129
+ # try random slave, else fall back to master
130
+ slave = slave_connection!
131
+ conn =
132
+ if !open_transaction? && slave_consistent?(slave, clock)
133
+ slave
134
+ else
135
+ master_connection
136
+ end
116
137
 
117
- def with_slave
118
- with(slave_connection!) { yield }
119
- end
138
+ with(conn) { yield }
120
139
 
121
- def with_consistency(clock)
122
- if clock.nil?
123
- raise ArgumentError, "consistency must be a valid comparable value"
124
- end
140
+ current_clock || clock
141
+ end
125
142
 
126
- # try random slave, else fall back to master
127
- slave = slave_connection!
128
- conn =
129
- if !open_transaction? && slave_consistent?(slave, clock)
130
- slave
131
- else
132
- master_connection
133
- end
143
+ def on_commit(&blk)
144
+ on_commit_callbacks.push blk
145
+ end
134
146
 
135
- with(conn) { yield }
147
+ def on_rollback(&blk)
148
+ on_rollback_callbacks.push blk
149
+ end
136
150
 
137
- current_clock || clock
138
- end
151
+ # ADAPTER INTERFACE OVERRIDES ===========================================
139
152
 
140
- def on_commit(&blk)
141
- on_commit_callbacks.push blk
142
- end
153
+ def insert(*args)
154
+ on_write { |conn| conn.insert(*args) }
155
+ end
143
156
 
144
- def on_rollback(&blk)
145
- on_rollback_callbacks.push blk
146
- end
157
+ def update(*args)
158
+ on_write { |conn| conn.update(*args) }
159
+ end
147
160
 
148
- # ADAPTER INTERFACE OVERRIDES ===========================================
161
+ def delete(*args)
162
+ on_write { |conn| conn.delete(*args) }
163
+ end
149
164
 
150
- def insert(*args)
151
- on_write { |conn| conn.insert(*args) }
152
- end
165
+ def execute(*args)
166
+ on_write { |conn| conn.execute(*args) }
167
+ end
153
168
 
154
- def update(*args)
155
- on_write { |conn| conn.update(*args) }
156
- end
169
+ def commit_db_transaction
170
+ on_write { |conn| conn.commit_db_transaction }
171
+ on_commit_callbacks.shift.call(current_clock) until on_commit_callbacks.blank?
172
+ end
157
173
 
158
- def delete(*args)
159
- on_write { |conn| conn.delete(*args) }
160
- end
174
+ def rollback_db_transaction
175
+ on_commit_callbacks.clear
176
+ with(master_connection) { |conn| conn.rollback_db_transaction }
177
+ on_rollback_callbacks.shift.call until on_rollback_callbacks.blank?
178
+ end
161
179
 
162
- def commit_db_transaction
163
- on_write { |conn| conn.commit_db_transaction }
164
- on_commit_callbacks.shift.call(current_clock) until on_commit_callbacks.blank?
165
- end
180
+ def active?
181
+ return true if @disable_connection_test
182
+ connections.map { |c| c.active? }.all?
183
+ end
166
184
 
167
- def rollback_db_transaction
168
- on_commit_callbacks.clear
169
- with(master_connection) { |conn| conn.rollback_db_transaction }
170
- on_rollback_callbacks.shift.call until on_rollback_callbacks.blank?
171
- end
185
+ def reconnect!
186
+ connections.each { |c| c.reconnect! }
187
+ end
172
188
 
173
- def active?
174
- return true if @disable_connection_test
175
- connections.map { |c| c.active? }.all?
176
- end
189
+ def disconnect!
190
+ connections.each { |c| c.disconnect! }
191
+ end
177
192
 
178
- def reconnect!
179
- connections.each { |c| c.reconnect! }
180
- end
193
+ def reset!
194
+ connections.each { |c| c.reset! }
195
+ end
181
196
 
182
- def disconnect!
183
- connections.each { |c| c.disconnect! }
184
- end
197
+ def cache(&blk)
198
+ connections.inject(blk) do |block, connection|
199
+ lambda { connection.cache(&block) }
200
+ end.call
201
+ end
185
202
 
186
- def reset!
187
- connections.each { |c| c.reset! }
188
- end
203
+ def uncached(&blk)
204
+ connections.inject(blk) do |block, connection|
205
+ lambda { connection.uncached(&block) }
206
+ end.call
207
+ end
189
208
 
190
- def cache(&blk)
191
- connections.inject(blk) do |block, connection|
192
- lambda { connection.cache(&block) }
193
- end.call
194
- end
209
+ def clear_query_cache
210
+ connections.each { |connection| connection.clear_query_cache }
211
+ end
195
212
 
196
- def uncached(&blk)
197
- connections.inject(blk) do |block, connection|
198
- lambda { connection.uncached(&block) }
199
- end.call
200
- end
213
+ # ADAPTER INTERFACE DELEGATES ===========================================
201
214
 
202
- def clear_query_cache
203
- connections.each { |connection| connection.clear_query_cache }
204
- end
215
+ def self.rescued_delegate(*methods)
216
+ options = methods.pop
217
+ to = options[:to]
205
218
 
206
- # Someone calling execute directly on the connection is likely to be a
207
- # write, respectively some DDL statement. People really shouldn't do that,
208
- # but let's delegate this to master, just to be sure.
209
- def execute(*args)
210
- on_write { |conn| conn.execute(*args) }
211
- end
219
+ file, line = caller.first.split(':', 2)
220
+ line = line.to_i
212
221
 
213
- # ADAPTER INTERFACE DELEGATES ===========================================
214
-
215
- def self.rescued_delegate(*methods)
216
- options = methods.pop
217
- to = options[:to]
218
-
219
- file, line = caller.first.split(':', 2)
220
- line = line.to_i
221
-
222
- methods.each do |method|
223
- module_eval(<<-EOS, file, line)
224
- def #{method}(*args, &block)
225
- begin
226
- #{to}.__send__(:#{method}, *args, &block)
227
- rescue => exception
228
- if master_connection?(#{to}) && connection_error?(exception)
229
- reset_master_connection
230
- raise MasterUnavailable
231
- else
232
- raise
233
- end
234
- end
222
+ methods.each do |method|
223
+ module_eval(<<-EOS, file, line)
224
+ def #{method}(*args, &block)
225
+ begin
226
+ #{to}.__send__(:#{method}, *args, &block)
227
+ rescue ActiveRecord::StatementInvalid => error
228
+ master_connection?(#{to}) ? handle_master_error(error) : raise
235
229
  end
236
- EOS
237
- end
238
- end
239
- class << self; private :rescued_delegate; end
240
-
241
- # === must go to master
242
- rescued_delegate :adapter_name,
243
- :supports_migrations?,
244
- :supports_primary_key?,
245
- :supports_savepoints?,
246
- :native_database_types,
247
- :raw_connection,
248
- :open_transactions,
249
- :increment_open_transactions,
250
- :decrement_open_transactions,
251
- :transaction_joinable=,
252
- :create_savepoint,
253
- :rollback_to_savepoint,
254
- :release_savepoint,
255
- :current_savepoint_name,
256
- :begin_db_transaction,
257
- :outside_transaction?,
258
- :add_limit!,
259
- :default_sequence_name,
260
- :reset_sequence!,
261
- :insert_fixture,
262
- :empty_insert_statement,
263
- :case_sensitive_equality_operator,
264
- :limited_update_conditions,
265
- :insert_sql,
266
- :update_sql,
267
- :delete_sql,
268
- :to => :master_connection
269
- # schema statements
270
- rescued_delegate :table_exists?,
271
- :create_table,
272
- :change_table,
273
- :rename_table,
274
- :drop_table,
275
- :add_column,
276
- :remove_column,
277
- :remove_columns,
278
- :change_column,
279
- :change_column_default,
280
- :rename_column,
281
- :add_index,
282
- :remove_index,
283
- :remove_index!,
284
- :rename_index,
285
- :index_name,
286
- :index_exists?,
287
- :structure_dump,
288
- :dump_schema_information,
289
- :initialize_schema_migrations_table,
290
- :assume_migrated_upto_version,
291
- :type_to_sql,
292
- :add_column_options!,
293
- :distinct,
294
- :add_order_by_for_association_limiting!,
295
- :add_timestamps,
296
- :remove_timestamps,
297
- :to => :master_connection
298
- # ActiveRecord 3.0
299
- rescued_delegate :visitor,
300
- :to => :master_connection
301
- # no clear interface contract:
302
- rescued_delegate :tables, # commented in SchemaStatements
303
- :truncate_table, # monkeypatching database_cleaner gem
304
- :primary_key, # is Base#primary_key meant to be the contract?
305
- :to => :master_connection
306
- # No need to be so picky about these methods
307
- rescued_delegate :add_limit_offset!, # DatabaseStatements
308
- :add_lock!, #DatabaseStatements
309
- :columns,
310
- :table_alias_for,
311
- :to => :prefer_master_connection
312
-
313
- # ok, we might have missed more
314
- def method_missing(name, *args, &blk)
315
- master_connection.send(name.to_sym, *args, &blk).tap do
316
- @logger.try(:warn, %Q{
317
- You called the unsupported method '#{name}' on #{self.class.name}.
318
- In order to help us improve master_slave_adapter, please report this
319
- to: https://github.com/soundcloud/master_slave_adapter/issues
320
-
321
- Thank you.
322
- })
323
- end
324
- rescue => exception
325
- if connection_error?(exception)
326
- reset_master_connection
327
- raise MasterUnavailable
328
- else
329
- raise
330
- end
230
+ end
231
+ EOS
331
232
  end
233
+ end
234
+ class << self; private :rescued_delegate; end
235
+
236
+ # === must go to master
237
+ rescued_delegate :adapter_name,
238
+ :supports_migrations?,
239
+ :supports_primary_key?,
240
+ :supports_savepoints?,
241
+ :native_database_types,
242
+ :raw_connection,
243
+ :open_transactions,
244
+ :increment_open_transactions,
245
+ :decrement_open_transactions,
246
+ :transaction_joinable=,
247
+ :create_savepoint,
248
+ :rollback_to_savepoint,
249
+ :release_savepoint,
250
+ :current_savepoint_name,
251
+ :begin_db_transaction,
252
+ :outside_transaction?,
253
+ :add_limit!,
254
+ :default_sequence_name,
255
+ :reset_sequence!,
256
+ :insert_fixture,
257
+ :empty_insert_statement,
258
+ :case_sensitive_equality_operator,
259
+ :limited_update_conditions,
260
+ :insert_sql,
261
+ :update_sql,
262
+ :delete_sql,
263
+ :visitor,
264
+ :to => :master_connection
265
+ # schema statements
266
+ rescued_delegate :table_exists?,
267
+ :column_exists?,
268
+ :index_name_exists?,
269
+ :create_table,
270
+ :change_table,
271
+ :rename_table,
272
+ :drop_table,
273
+ :add_column,
274
+ :remove_column,
275
+ :remove_columns,
276
+ :change_column,
277
+ :change_column_default,
278
+ :rename_column,
279
+ :add_index,
280
+ :remove_index,
281
+ :remove_index!,
282
+ :rename_index,
283
+ :index_name,
284
+ :index_exists?,
285
+ :structure_dump,
286
+ :dump_schema_information,
287
+ :initialize_schema_migrations_table,
288
+ :assume_migrated_upto_version,
289
+ :type_to_sql,
290
+ :add_column_options!,
291
+ :distinct,
292
+ :add_order_by_for_association_limiting!,
293
+ :add_timestamps,
294
+ :remove_timestamps,
295
+ :to => :master_connection
296
+ # no clear interface contract:
297
+ rescued_delegate :tables, # commented in SchemaStatements
298
+ :truncate_table, # monkeypatching database_cleaner gem
299
+ :primary_key, # is Base#primary_key meant to be the contract?
300
+ :to => :master_connection
301
+ # No need to be so picky about these methods
302
+ rescued_delegate :add_limit_offset!, # DatabaseStatements
303
+ :add_lock!, #DatabaseStatements
304
+ :columns,
305
+ :table_alias_for,
306
+ :to => :prefer_master_connection
307
+
308
+ # === determine read connection
309
+ rescued_delegate :select_all,
310
+ :select_one,
311
+ :select_rows,
312
+ :select_value,
313
+ :select_values,
314
+ :to => :connection_for_read
315
+
316
+ # === doesn't really matter, but must be handled by underlying adapter
317
+ rescued_delegate *(ActiveRecord::ConnectionAdapters::Quoting.instance_methods + [{
318
+ :to => :current_connection }])
319
+ # issue #4: current_database is not supported by all adapters, though
320
+ rescued_delegate :current_database, :to => :current_connection
321
+
322
+ # ok, we might have missed more
323
+ def method_missing(name, *args, &blk)
324
+ master_connection.send(name.to_sym, *args, &blk).tap do
325
+ @logger.try(:warn, %Q{
326
+ You called the unsupported method '#{name}' on #{self.class.name}.
327
+ In order to help us improve master_slave_adapter, please report this
328
+ to: https://github.com/soundcloud/master_slave_adapter/issues
329
+
330
+ Thank you.
331
+ })
332
+ end
333
+ rescue ActiveRecord::StatementInvalid => exception
334
+ handle_master_error(exception)
335
+ end
332
336
 
333
- # === determine read connection
334
- rescued_delegate :select_all,
335
- :select_one,
336
- :select_rows,
337
- :select_value,
338
- :select_values,
339
- :to => :connection_for_read
337
+ # UTIL ==================================================================
340
338
 
341
- def connection_for_read
342
- open_transaction? ? master_connection : current_connection
339
+ def master_connection
340
+ if circuit.tripped?
341
+ raise MasterUnavailable
343
342
  end
344
- private :connection_for_read
345
-
346
- # === doesn't really matter, but must be handled by underlying adapter
347
- rescued_delegate *(ActiveRecord::ConnectionAdapters::Quoting.instance_methods + [{
348
- :to => :current_connection }])
349
- # issue #4: current_database is not supported by all adapters, though
350
- rescued_delegate :current_database, :to => :current_connection
351
-
352
- # UTIL ==================================================================
353
343
 
354
- def master_connection
355
- if circuit.tripped?
356
- raise MasterUnavailable
357
- end
358
-
359
- @connections[:master] ||= connect_to_master
360
- if @connections[:master]
361
- circuit.success!
362
- @connections[:master]
363
- else
364
- circuit.fail!
365
- raise MasterUnavailable
366
- end
344
+ @connections[:master] ||= connect_to_master
345
+ if @connections[:master]
346
+ circuit.success!
347
+ @connections[:master]
348
+ else
349
+ circuit.fail!
350
+ raise MasterUnavailable
367
351
  end
352
+ end
368
353
 
369
- def master_available?
370
- !@connections[:master].nil?
371
- end
354
+ def master_available?
355
+ !@connections[:master].nil?
356
+ end
372
357
 
373
- # Returns a random slave connection
374
- # Note: the method is not referentially transparent, hence the bang
375
- def slave_connection!
376
- @connections[:slaves].sample
377
- end
358
+ # Returns a random slave connection
359
+ # Note: the method is not referentially transparent, hence the bang
360
+ def slave_connection!
361
+ @connections[:slaves].sample
362
+ end
378
363
 
379
- def connections
380
- @connections.values.flatten.compact
381
- end
364
+ def connections
365
+ @connections.values.flatten.compact
366
+ end
382
367
 
383
- def current_connection
384
- connection_stack.first
385
- end
368
+ def current_connection
369
+ connection_stack.first
370
+ end
386
371
 
387
- def current_clock
388
- @master_slave_clock
389
- end
372
+ def current_clock
373
+ @master_slave_clock
374
+ end
390
375
 
391
- def master_clock
392
- raise NotImplementedError
393
- end
376
+ def master_clock
377
+ raise NotImplementedError
378
+ end
394
379
 
395
- def slave_clock(conn)
396
- raise NotImplementedError
397
- end
380
+ def slave_clock(conn)
381
+ raise NotImplementedError
382
+ end
398
383
 
399
- protected
384
+ protected
400
385
 
401
- def prefer_master_connection
402
- master_available? ? master_connection : slave_connection!
403
- end
386
+ def open_transaction?
387
+ master_available? ? (master_connection.open_transactions > 0) : false
388
+ end
404
389
 
405
- def master_connection?(connection)
406
- @connections[:master] == connection
407
- end
390
+ def connection_for_read
391
+ open_transaction? ? master_connection : current_connection
392
+ end
408
393
 
409
- def reset_master_connection
410
- @connections[:master] = nil
411
- end
394
+ def prefer_master_connection
395
+ master_available? ? master_connection : slave_connection!
396
+ end
412
397
 
413
- def current_clock=(clock)
414
- @master_slave_clock = clock
415
- end
398
+ def master_connection?(connection)
399
+ @connections[:master] == connection
400
+ end
416
401
 
417
- def slave_consistent?(conn, clock)
418
- if (last_seen_clock = get_last_seen_slave_clock(conn))
419
- last_seen_clock >= clock
420
- elsif (slave_clk = slave_clock(conn))
421
- set_last_seen_slave_clock(conn, slave_clk)
422
- slave_clk >= clock
423
- else
424
- false
425
- end
426
- end
402
+ def reset_master_connection
403
+ @connections[:master] = nil
404
+ end
427
405
 
428
- def on_write
429
- with(master_connection) do |conn|
430
- yield(conn).tap do
431
- unless open_transaction?
432
- if mc = master_clock
433
- self.current_clock = mc unless current_clock.try(:>=, mc)
434
- end
435
- # keep using master after write
436
- connection_stack.replace([ conn ])
437
- end
438
- end
439
- end
406
+ def slave_consistent?(conn, clock)
407
+ if @last_seen_slave_clocks[conn].try(:>=, clock)
408
+ true
409
+ elsif (slave_clk = slave_clock(conn))
410
+ @last_seen_slave_clocks[conn] = clock
411
+ slave_clk >= clock
412
+ else
413
+ false
440
414
  end
415
+ end
441
416
 
442
- def with(conn)
443
- self.current_connection = conn
444
- yield(conn).tap { connection_stack.shift if connection_stack.size > 1 }
445
- end
417
+ def current_clock=(clock)
418
+ @master_slave_clock = clock
419
+ end
446
420
 
447
- private
421
+ def connection_stack
422
+ @master_slave_connection ||= []
423
+ end
448
424
 
449
- def connect(cfg, name)
450
- adapter_method = "#{cfg.fetch(:adapter)}_connection".to_sym
451
- ActiveRecord::Base.send(adapter_method, { :name => name }.merge(cfg))
452
- end
425
+ def current_connection=(conn)
426
+ connection_stack.unshift(conn)
427
+ end
453
428
 
454
- def open_transaction?
455
- master_available? ? (master_connection.open_transactions > 0) : false
456
- end
429
+ def on_write
430
+ with(master_connection) do |conn|
431
+ yield(conn).tap do
432
+ unless open_transaction?
433
+ master_clk = master_clock
434
+ unless current_clock.try(:>=, master_clk)
435
+ self.current_clock = master_clk
436
+ end
457
437
 
458
- def connection_stack
459
- @master_slave_connection ||= []
438
+ # keep using master after write
439
+ connection_stack.replace([ conn ])
440
+ end
441
+ end
460
442
  end
443
+ end
461
444
 
462
- def current_connection=(conn)
463
- connection_stack.unshift(conn)
464
- end
445
+ def with(conn)
446
+ self.current_connection = conn
447
+ yield(conn).tap { connection_stack.shift if connection_stack.size > 1 }
448
+ rescue ActiveRecord::StatementInvalid => exception
449
+ handle_master_error(exception)
450
+ end
465
451
 
466
- def on_commit_callbacks
467
- Thread.current[:on_commit_callbacks] ||= []
468
- end
452
+ def connect(cfg, name)
453
+ adapter_method = "#{cfg.fetch(:adapter)}_connection".to_sym
454
+ ActiveRecord::Base.send(adapter_method, { :name => name }.merge(cfg))
455
+ end
469
456
 
470
- def on_rollback_callbacks
471
- Thread.current[:on_rollback_callbacks] ||= []
457
+ def connect_to_master
458
+ connect(@config.fetch(:master), :master)
459
+ rescue => exception
460
+ if connection_error?(exception)
461
+ @logger.try(:warn, "Can't connect to master. #{exception.message}")
462
+ nil
463
+ else
464
+ raise
472
465
  end
466
+ end
473
467
 
474
- def get_last_seen_slave_clock(conn)
475
- conn.instance_variable_get(:@last_seen_slave_clock)
476
- end
468
+ def on_commit_callbacks
469
+ @on_commit_callbacks ||= []
470
+ end
477
471
 
478
- def set_last_seen_slave_clock(conn, clock)
479
- last_seen = get_last_seen_slave_clock(conn)
480
- if last_seen.nil? || last_seen < clock
481
- conn.instance_variable_set(:@last_seen_slave_clock, clock)
482
- end
483
- end
472
+ def on_rollback_callbacks
473
+ @on_rollback_callbacks ||= []
474
+ end
484
475
 
485
- def connect_to_master
486
- connect(@config.fetch(:master), :master)
487
- rescue => exception
488
- connection_error?(exception) ? nil : raise
489
- end
476
+ def connection_error?(exception)
477
+ raise NotImplementedError
478
+ end
490
479
 
491
- def connection_error?(exception)
492
- raise NotImplementedError
480
+ def handle_master_error(exception)
481
+ if connection_error?(exception)
482
+ reset_master_connection
483
+ raise MasterUnavailable
484
+ else
485
+ raise exception
493
486
  end
487
+ end
494
488
 
495
- def circuit
496
- @circuit
497
- end
489
+ def circuit
490
+ @circuit
498
491
  end
499
492
  end
500
493
  end