Skip to content

Commit

Permalink
Decorating the ActiveRecord @config object in a thread-safe manner
Browse files Browse the repository at this point in the history
Not sure if this is needed as @config[:database] doesn't seem to affect any tests.
  • Loading branch information
zdennis committed Jun 10, 2020
1 parent 2ddf249 commit e68fc01
Showing 1 changed file with 41 additions and 2 deletions.
43 changes: 41 additions & 2 deletions lib/active_record_host_pool/connection_adapter_mixin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,55 @@
require "active_record/connection_adapters/mysql2_adapter"

module ActiveRecordHostPool
module PerThreadHashAccess
# PerThreadHashAccess.module_for(...) returns a module which scopes hash accessor methods
# to the given connection_id and uses `super` as a fallbakc. This is to make the access
# to the config object on a ActiveRecord::ConnectionAdapters::Mysql2Adapter thread-safe.
#
# If any of the config key/value(s) change then those changes will be used for that thread.
# It will fallback to config's original key/value pairs if none have been set in a thread specific way.
#
# Note: A module is being used in order to remain ignorant about how/where the config object is used in
# ActiveRecord itself.
def self.module_for(connection_id)
generate_storage_key = ->(key) { "config/#{key}" }

Module.new do
define_method(:[]=) do |key, value|
storage_key = generate_storage_key.call(key)
DatabaseSwitch.thread_local_storage[connection_id][storage_key] = value
end

define_method(:[]) do |key|
storage_key = generate_storage_key.call(key)
if DatabaseSwitch.thread_local_storage[connection_id].key?(storage_key)
DatabaseSwitch.thread_local_storage[connection_id][storage_key]
else
DatabaseSwitch.thread_local_storage[connection_id][storage_key] = super(key)
end
end

define_method(:fetch) do |*args, &blk|
key, _ = args
storage_key = generate_storage_key.call(key)
DatabaseSwitch.thread_local_storage[connection_id].fetch(storage_key) do
DatabaseSwitch.thread_local_storage[connection_id][storage_key] = super(*args, &blk)
end
end
end
end
end

module DatabaseSwitch
def self.included(base)
base.class_eval do
attr_reader :_host_pool_current_database
def _host_pool_current_database
_ahrp_per_thread_per_connection_storage[:_host_pool_current_database]
end

def _host_pool_current_database=(database)
_ahrp_per_thread_per_connection_storage[:_host_pool_current_database] = database
# @config[:database] = _host_pool_current_database if ActiveRecord::VERSION::MAJOR >= 5
@config[:database] = _host_pool_current_database if ActiveRecord::VERSION::MAJOR >= 5
end

alias_method :execute_without_switching, :execute
Expand All @@ -37,6 +75,7 @@ def self.thread_local_storage
def initialize(*)
self._cached_current_database = nil
super
@config.singleton_class.prepend PerThreadHashAccess.module_for(@connection.object_id)
end

def execute_with_switching(*args)
Expand Down

0 comments on commit e68fc01

Please sign in to comment.