Class: ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter

Inherits:
AbstractAdapter
  • Object
show all
Defined in:
lib/active_record/connection_adapters/seamless_database_pool_adapter.rb

Defined Under Namespace

Classes: AvailableConnections, DatabaseConnectionError

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(connection, logger, master_connection, read_connections, pool_weights, config) ⇒ SeamlessDatabasePoolAdapter

Returns a new instance of SeamlessDatabasePoolAdapter.



154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 154

def initialize(connection, logger, master_connection, read_connections, pool_weights, config)
  @master_connection = master_connection
  @read_connections = read_connections.dup.freeze
  
  super(connection, logger, config)

  @weighted_read_connections = []
  pool_weights.each_pair do |conn, weight|
    weight.times{@weighted_read_connections << conn}
  end
  @available_read_connections = [AvailableConnections.new(@weighted_read_connections)]
end

Instance Attribute Details

#master_connectionObject (readonly)

Returns the value of attribute master_connection.



80
81
82
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 80

def master_connection
  @master_connection
end

#read_connectionsObject (readonly)

Returns the value of attribute read_connections.



80
81
82
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 80

def read_connections
  @read_connections
end

Class Method Details

.adapter_class(master_connection) ⇒ Object

Create an anonymous class that extends this one and proxies methods to the pool connections.



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 84

def adapter_class(master_connection)
  adapter_class_name = master_connection.adapter_name.classify
  return const_get(adapter_class_name) if const_defined?(adapter_class_name, false)
  
  # Define methods to proxy to the appropriate pool
  read_only_methods = [:select, :select_rows, :execute, :tables, :columns]
  clear_cache_methods = [:insert, :update, :delete]
  
  # Get a list of all methods redefined by the underlying adapter. These will be
  # proxied to the master connection.
  master_methods = []
  override_classes = (master_connection.class.ancestors - AbstractAdapter.ancestors)
  override_classes.each do |connection_class|
    master_methods.concat(connection_class.public_instance_methods(false))
    master_methods.concat(connection_class.protected_instance_methods(false))
    master_methods.concat(connection_class.private_instance_methods(false))
  end
  master_methods = master_methods.collect{|m| m.to_sym}.uniq
  master_methods -= public_instance_methods(false) + protected_instance_methods(false) + private_instance_methods(false)
  master_methods -= read_only_methods
  master_methods -= [:select_all, :select_one, :select_value, :select_values]
  master_methods -= clear_cache_methods

  klass = Class.new(self)
  master_methods.each do |method_name|
    klass.class_eval <<-EOS, __FILE__, __LINE__ + 1
      def #{method_name}(*args, &block)
        use_master_connection do
          return proxy_connection_method(master_connection, :#{method_name}, :master, *args, &block)
        end
      end
    EOS
  end
  
  clear_cache_methods.each do |method_name|
    klass.class_eval <<-EOS, __FILE__, __LINE__ + 1
      def #{method_name}(*args, &block)
        clear_query_cache if query_cache_enabled
        use_master_connection do
          return proxy_connection_method(master_connection, :#{method_name}, :master, *args, &block)
        end
      end
    EOS
  end
  
  read_only_methods.each do |method_name|
    klass.class_eval <<-EOS, __FILE__, __LINE__ + 1
      def #{method_name}(*args, &block)
        connection = @use_master ? master_connection : current_read_connection
        proxy_connection_method(connection, :#{method_name}, :read, *args, &block)
      end
    EOS
  end
  klass.send :protected, :select

  const_set(adapter_class_name, klass)
  
  return klass
end

.visitor_for(pool) ⇒ Object

Set the arel visitor on the connections.



145
146
147
148
149
150
151
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 145

def visitor_for(pool)
  # This is ugly, but then again, so is the code in ActiveRecord for setting the arel
  # visitor. There is a note in the code indicating the method signatures should be updated.
  config = pool.spec.config.with_indifferent_access
  adapter = config[:master][:adapter] || config[:pool_adapter]
  SeamlessDatabasePool.adapter_class_for(adapter).visitor_for(pool)
end

Instance Method Details

#active?Boolean

Returns:

  • (Boolean)


199
200
201
202
203
204
205
206
207
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 199

def active?
  if SeamlessDatabasePool.read_only_connection_type == :master
    @master_connection.active?
  else
    active = true
    do_to_connections {|conn| active &= conn.active?}
    active
  end
end

#adapter_nameObject

:nodoc:



167
168
169
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 167

def adapter_name #:nodoc:
  'Seamless_Database_Pool'
end

#all_connectionsObject

Returns an array of the master connection and the read pool connections



172
173
174
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 172

def all_connections
  [@master_connection] + @read_connections
end

#available_read_connectionsObject

Get the available weighted connections. When a connection is dead and cannot be reconnected, it will be temporarily removed from the read pool so we don’t keep trying to reconnect to a database that isn’t listening.



302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 302

def available_read_connections
  available = @available_read_connections.last
  if available.expired?
    begin
      @logger.info("Adding dead database connection back to the pool") if @logger
      available.reconnect!
    rescue => e
      # Couldn't reconnect so try again in a little bit
      if @logger
        @logger.warn("Failed to reconnect to database when adding connection back to the pool")
        @logger.warn(e)
      end
      available.expires = 30.seconds.from_now
      return available.connections
    end
    
    # If reconnect is successful, the connection will have been re-added to @available_read_connections list,
    # so let's pop this old version of the connection
    @available_read_connections.pop
    
    # Now we'll try again after either expiring our bad connection or re-adding our good one
    return available_read_connections
  else
    return available.connections
  end
end

#current_read_connectionObject

Get the current read connection



247
248
249
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 247

def current_read_connection
  return SeamlessDatabasePool.read_only_connection(self)
end

#disconnect!Object



213
214
215
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 213

def disconnect!
  do_to_connections {|conn| conn.disconnect!}
end

#inspectObject



270
271
272
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 270

def inspect
  to_s
end

#pool_weight(connection) ⇒ Object

Get the pool weight of a connection



177
178
179
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 177

def pool_weight(connection)
  return @weighted_read_connections.select{|conn| conn == connection}.size
end

#random_read_connectionObject

Get a random read connection from the pool. If the connection is not active, it will attempt to reconnect to the database. If that fails, it will be removed from the pool for one minute.



237
238
239
240
241
242
243
244
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 237

def random_read_connection
  weighted_read_connections = available_read_connections
  if @use_master || weighted_read_connections.empty?
    return master_connection
  else
    weighted_read_connections[rand(weighted_read_connections.length)]
  end
end

#reconnect!Object



209
210
211
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 209

def reconnect!
  do_to_connections {|conn| conn.reconnect!}
end

#requires_reloading?Boolean

Returns:

  • (Boolean)


181
182
183
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 181

def requires_reloading?
  false
end

#reset!Object



217
218
219
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 217

def reset!
  do_to_connections {|conn| conn.reset!}
end

#reset_available_read_connectionsObject



329
330
331
332
333
334
335
336
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 329

def reset_available_read_connections
  @available_read_connections.slice!(1, @available_read_connections.length)
  @available_read_connections.first.connections.each do |connection|
    unless connection.active?
      connection.reconnect! rescue nil
    end
  end
end

#reset_runtimeObject



229
230
231
232
233
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 229

def reset_runtime
  total = 0.0
  do_to_connections {|conn| total += conn.reset_runtime}
  total
end

#suppress_read_connection(conn, expire) ⇒ Object

Temporarily remove a connection from the read pool.



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 339

def suppress_read_connection(conn, expire)
  available = available_read_connections
  connections = available.reject{|c| c == conn}

  # This wasn't a read connection so don't suppress it
  return if connections.length == available.length

  if connections.empty?
    @logger.warn("All read connections are marked dead; trying them all again.") if @logger
    # No connections available so we might as well try them all again
    reset_available_read_connections
  else
    @logger.warn("Removing #{conn.inspect} from the connection pool for #{expire} seconds") if @logger
    # Available connections will now not include the suppressed connection for a while
    @available_read_connections.push(AvailableConnections.new(connections, conn, expire.seconds.from_now))
  end
end

#to_sObject



266
267
268
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 266

def to_s
  "#<#{self.class.name}:0x#{object_id.to_s(16)} #{all_connections.size} connections>"
end

#transaction(options = {}) ⇒ Object



185
186
187
188
189
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 185

def transaction(options = {})
  use_master_connection do
    super
  end
end

#use_master_connectionObject

Force using the master connection in a block.



256
257
258
259
260
261
262
263
264
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 256

def use_master_connection
  save_val = @use_master
  begin
    @use_master = true
    yield if block_given?
  ensure
    @use_master = save_val
  end
end

#using_master_connection?Boolean

Returns:

  • (Boolean)


251
252
253
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 251

def using_master_connection?
  !!@use_master
end

#verify!(*ignored) ⇒ Object



221
222
223
224
225
226
227
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 221

def verify!(*ignored)
  if SeamlessDatabasePool.read_only_connection_type == :master
    @master_connection.verify!(*ignored)
  else
    do_to_connections {|conn| conn.verify!(*ignored)}
  end
end

#visitorObject



195
196
197
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 195

def visitor
  master_connection.visitor
end

#visitor=(visitor) ⇒ Object



191
192
193
# File 'lib/active_record/connection_adapters/seamless_database_pool_adapter.rb', line 191

def visitor=(visitor)
  all_connections.each{|conn| conn.visitor = visitor}
end