aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoreileencodes <eileencodes@gmail.com>2019-08-02 12:04:38 -0400
committereileencodes <eileencodes@gmail.com>2019-08-02 12:10:32 -0400
commitf2de448106ca3bb947dc88f96ca7d81d37b5be9a (patch)
tree4a12db093039c6d9546d37668a53996e80e54e61
parentf3c68c59ed57302ca54f4dfad0e91dbff426962d (diff)
downloadrails-f2de448106ca3bb947dc88f96ca7d81d37b5be9a.tar.gz
rails-f2de448106ca3bb947dc88f96ca7d81d37b5be9a.tar.bz2
rails-f2de448106ca3bb947dc88f96ca7d81d37b5be9a.zip
Add ability to unset preventing writes
Previously if an app attempts to do a write inside a read request it will be impossilbe to switch back to writing to the primary. This PR adds an argument to the `while_preventing_writes` so that we can make sure to turn it off if we're doing a write on a primary. Fixes #36830 Co-authored-by: John Crepezzi <john.crepezzi@gmail.com>
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb4
-rw-r--r--activerecord/lib/active_record/middleware/database_selector/resolver.rb12
-rw-r--r--activerecord/test/cases/database_selector_test.rb34
3 files changed, 43 insertions, 7 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 36001efdd5..276d5a25d4 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -1020,8 +1020,8 @@ module ActiveRecord
# In some cases you may want to prevent writes to the database
# even if you are on a database that can write. `while_preventing_writes`
# will prevent writes to the database for the duration of the block.
- def while_preventing_writes
- original, @prevent_writes = @prevent_writes, true
+ def while_preventing_writes(enabled = true)
+ original, @prevent_writes = @prevent_writes, enabled
yield
ensure
@prevent_writes = original
diff --git a/activerecord/lib/active_record/middleware/database_selector/resolver.rb b/activerecord/lib/active_record/middleware/database_selector/resolver.rb
index 3eb1039c50..b2aa453a8e 100644
--- a/activerecord/lib/active_record/middleware/database_selector/resolver.rb
+++ b/activerecord/lib/active_record/middleware/database_selector/resolver.rb
@@ -46,7 +46,7 @@ module ActiveRecord
private
def read_from_primary(&blk)
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role) do
- ActiveRecord::Base.connection_handler.while_preventing_writes do
+ ActiveRecord::Base.connection_handler.while_preventing_writes(true) do
instrumenter.instrument("database_selector.active_record.read_from_primary") do
yield
end
@@ -64,10 +64,12 @@ module ActiveRecord
def write_to_primary(&blk)
ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role) do
- instrumenter.instrument("database_selector.active_record.wrote_to_primary") do
- yield
- ensure
- context.update_last_write_timestamp
+ ActiveRecord::Base.connection_handler.while_preventing_writes(false) do
+ instrumenter.instrument("database_selector.active_record.wrote_to_primary") do
+ yield
+ ensure
+ context.update_last_write_timestamp
+ end
end
end
end
diff --git a/activerecord/test/cases/database_selector_test.rb b/activerecord/test/cases/database_selector_test.rb
index fd02d2acb4..340151e6db 100644
--- a/activerecord/test/cases/database_selector_test.rb
+++ b/activerecord/test/cases/database_selector_test.rb
@@ -123,6 +123,40 @@ module ActiveRecord
assert read
end
+ def test_preventing_writes_turns_off_for_primary_write
+ resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver.new(@session, delay: 5.seconds)
+
+ # Session should start empty
+ assert_nil @session_store[:last_write]
+
+ called = false
+ resolver.write do
+ assert ActiveRecord::Base.connected_to?(role: :writing)
+ called = true
+ end
+ assert called
+
+ # and be populated by the last write time
+ assert @session_store[:last_write]
+
+ read = false
+ write = false
+ resolver.read do
+ assert ActiveRecord::Base.connected_to?(role: :writing)
+ assert ActiveRecord::Base.connection_handler.prevent_writes
+ read = true
+
+ resolver.write do
+ assert ActiveRecord::Base.connected_to?(role: :writing)
+ assert_not ActiveRecord::Base.connection_handler.prevent_writes
+ write = true
+ end
+ end
+
+ assert write
+ assert read
+ end
+
def test_read_from_replica_with_no_delay
resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver.new(@session, delay: 0.seconds)