1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Merge pull request #40615 from eileencodes/fix-preventing-writes

Fix preventing_writes for granular swapping
This commit is contained in:
Eileen M. Uchitelle 2020-11-17 13:24:53 -05:00 committed by GitHub
commit 8e9d550aee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 90 additions and 14 deletions

View file

@ -23,6 +23,10 @@ module ActiveRecord
include ConnectionAdapters::AbstractPool
attr_accessor :schema_cache
def owner_name
nil
end
end
# Connection pool base class for managing Active Record database
@ -356,7 +360,7 @@ module ActiveRecord
include ConnectionAdapters::AbstractPool
attr_accessor :automatic_reconnect, :checkout_timeout
attr_reader :db_config, :size, :reaper, :pool_config
attr_reader :db_config, :size, :reaper, :pool_config, :owner_name
delegate :schema_cache, :schema_cache=, to: :pool_config
@ -371,6 +375,7 @@ module ActiveRecord
@pool_config = pool_config
@db_config = pool_config.db_config
@owner_name = pool_config.connection_specification_name
@checkout_timeout = db_config.checkout_timeout
@idle_timeout = db_config.idle_timeout

View file

@ -113,14 +113,20 @@ module ActiveRecord
# Determines whether writes are currently being prevents.
#
# Returns true if the connection is a replica, or if +prevent_writes+
# is set to true.
# Returns true if the connection is a replica.
#
# If the application is using legacy handling, returns
# true if `connection_handler.prevent_writes` is set.
#
# If the application is using the new connection handling
# will return true based on `current_preventing_writes`.
def preventing_writes?
if ActiveRecord::Base.legacy_connection_handling
replica? || ActiveRecord::Base.connection_handler.prevent_writes
else
replica? || ActiveRecord::Base.current_preventing_writes
end
return true if replica?
return ActiveRecord::Base.connection_handler.prevent_writes if ActiveRecord::Base.legacy_connection_handling
return false if owner_name.nil?
klass = self.owner_name.safe_constantize
klass&.current_preventing_writes
end
def migrations_paths # :nodoc:
@ -196,6 +202,10 @@ module ActiveRecord
@owner = Thread.current
end
def owner_name # :nodoc:
@pool.owner_name
end
def schema_cache
@pool.get_schema_cache(self)
end

View file

@ -11,9 +11,7 @@ module ActiveRecord
self.use_transactional_tests = false
def setup
@conn = Base.sqlite3_connection database: ":memory:",
adapter: "sqlite3",
timeout: 100
@conn = ActiveRecord::Base.connection
end
def test_errors_when_an_insert_query_is_called_while_preventing_writes
@ -100,9 +98,7 @@ module ActiveRecord
@old_value = ActiveRecord::Base.legacy_connection_handling
ActiveRecord::Base.legacy_connection_handling = true
@conn = Base.sqlite3_connection database: ":memory:",
adapter: "sqlite3",
timeout: 100
@conn = ActiveRecord::Base.connection
@connection_handler = ActiveRecord::Base.connection_handler
end

View file

@ -349,6 +349,71 @@ module ActiveRecord
ActiveRecord::Base.establish_connection(:arunit)
ENV["RAILS_ENV"] = previous_env
end
def test_prevent_writes_can_be_changed_granularly
previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"
# replica: true is purposefully left out so we can test the pools behavior
config = {
"default_env" => {
"primary" => { "adapter" => "sqlite3", "database" => "test/db/primary.sqlite3" },
"primary_replica" => { "adapter" => "sqlite3", "database" => "test/db/primary.sqlite3" },
"secondary" => { "adapter" => "sqlite3", "database" => "test/db/secondary.sqlite3" },
"secondary_replica" => { "adapter" => "sqlite3", "database" => "test/db/secondary_replica.sqlite3" }
}
}
@prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
PrimaryBase.connects_to database: { writing: :primary, reading: :primary_replica }
SecondaryBase.connects_to database: { writing: :secondary, reading: :secondary_replica }
# Switch everything to writing
ActiveRecord::Base.connected_to(role: :writing) do
assert_not_predicate ActiveRecord::Base.connection, :preventing_writes?
assert_not_predicate PrimaryBase.connection, :preventing_writes?
assert_not_predicate SecondaryBase.connection, :preventing_writes?
# Switch only primary to reading
PrimaryBase.connected_to(role: :reading) do
assert_predicate PrimaryBase.connection, :preventing_writes?
assert_not_predicate SecondaryBase.connection, :preventing_writes?
# Switch global to reading
ActiveRecord::Base.connected_to(role: :reading) do
assert_predicate PrimaryBase.connection, :preventing_writes?
assert_predicate SecondaryBase.connection, :preventing_writes?
# Switch only secondary to writing
SecondaryBase.connected_to(role: :writing) do
assert_predicate PrimaryBase.connection, :preventing_writes?
assert_not_predicate SecondaryBase.connection, :preventing_writes?
end
# Ensure restored to global reading
assert_predicate PrimaryBase.connection, :preventing_writes?
assert_predicate SecondaryBase.connection, :preventing_writes?
end
# Switch everything to writing
ActiveRecord::Base.connected_to(role: :writing) do
assert_not_predicate PrimaryBase.connection, :preventing_writes?
assert_not_predicate SecondaryBase.connection, :preventing_writes?
end
assert_predicate PrimaryBase.connection, :preventing_writes?
assert_not_predicate SecondaryBase.connection, :preventing_writes?
end
# Ensure restored to global writing
assert_not_predicate PrimaryBase.connection, :preventing_writes?
assert_not_predicate SecondaryBase.connection, :preventing_writes?
end
ensure
ActiveRecord::Base.configurations = @prev_configs
ActiveRecord::Base.establish_connection(:arunit)
ENV["RAILS_ENV"] = previous_env
end
end
end
end