master_slave_adapter 1.0.0.beta1 → 1.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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