Skip to content

Commit 42e9891

Browse files
committed
[Fix rails#53758] Raise ActiveRecord::ReadOnlyError when pessimistically locking with a readonly role
1 parent 972a52d commit 42e9891

File tree

4 files changed

+131
-0
lines changed

4 files changed

+131
-0
lines changed

activerecord/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
* Raise `ActiveRecord::ReadOnlyError` when pessimistically locking with a readonly role.
2+
3+
*Joshua Young*
4+
15
* Fix using the `SQLite3Adapter`'s `dbconsole` method outside of a Rails application.
26

37
*Hartley McGuire*

activerecord/lib/active_record/locking/pessimistic.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ module Pessimistic
6767
# or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
6868
# the locked record.
6969
def lock!(lock = true)
70+
if self.class.current_preventing_writes
71+
raise ActiveRecord::ReadOnlyError, "Lock query attempted while in readonly mode"
72+
end
73+
7074
if persisted?
7175
if has_changes_to_save?
7276
raise(<<-MSG.squish)
@@ -79,6 +83,7 @@ def lock!(lock = true)
7983

8084
reload(lock: lock)
8185
end
86+
8287
self
8388
end
8489

activerecord/lib/active_record/relation.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1413,6 +1413,10 @@ def _increment_attribute(attribute, value = 1)
14131413
end
14141414

14151415
def exec_queries(&block)
1416+
if lock_value && model.current_preventing_writes
1417+
raise ActiveRecord::ReadOnlyError, "Lock query attempted while in readonly mode"
1418+
end
1419+
14161420
skip_query_cache_if_necessary do
14171421
rows = if scheduled?
14181422
future = @future_result

activerecord/test/cases/locking_test.rb

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,3 +856,121 @@ def duel(&block)
856856
end
857857
end
858858
end
859+
860+
class PessimisticLockingWhilePreventingWritesTest < ActiveRecord::TestCase
861+
CUSTOM_LOCK = if current_adapter?(:SQLite3Adapter)
862+
true # no-op
863+
else
864+
"FOR SHARE"
865+
end
866+
867+
fixtures :people
868+
869+
def test_lock_when_not_preventing_writes
870+
person = Person.last!
871+
assert_nothing_raised do
872+
person.lock!
873+
end
874+
end
875+
876+
def test_lock_when_preventing_writes
877+
person = Person.last!
878+
ActiveRecord::Base.while_preventing_writes do
879+
assert_raises(ActiveRecord::ReadOnlyError) do
880+
person.lock!
881+
end
882+
end
883+
end
884+
885+
def test_lock_when_not_preventing_writes_nested
886+
person = Person.last!
887+
ActiveRecord::Base.while_preventing_writes do
888+
ActiveRecord::Base.while_preventing_writes(false) do
889+
assert_nothing_raised do
890+
person.lock!
891+
end
892+
end
893+
end
894+
end
895+
896+
def test_custom_lock_when_preventing_writes
897+
person = Person.last!
898+
ActiveRecord::Base.while_preventing_writes do
899+
assert_raises(ActiveRecord::ReadOnlyError) do
900+
person.lock!(CUSTOM_LOCK)
901+
end
902+
end
903+
end
904+
905+
def test_with_lock_when_not_preventing_writes
906+
person = Person.last!
907+
assert_nothing_raised do
908+
person.with_lock do
909+
end
910+
end
911+
end
912+
913+
def test_with_lock_when_preventing_writes
914+
person = Person.last!
915+
ActiveRecord::Base.while_preventing_writes do
916+
assert_raises(ActiveRecord::ReadOnlyError) do
917+
person.with_lock do
918+
end
919+
end
920+
end
921+
end
922+
923+
def test_with_lock_when_not_preventing_writes_nested
924+
person = Person.last!
925+
ActiveRecord::Base.while_preventing_writes do
926+
ActiveRecord::Base.while_preventing_writes(false) do
927+
assert_nothing_raised do
928+
person.with_lock do
929+
end
930+
end
931+
end
932+
end
933+
end
934+
935+
def test_custom_with_lock_when_preventing_writes
936+
person = Person.last!
937+
ActiveRecord::Base.while_preventing_writes do
938+
assert_raises(ActiveRecord::ReadOnlyError) do
939+
person.with_lock(CUSTOM_LOCK) do
940+
end
941+
end
942+
end
943+
end
944+
945+
def test_relation_lock_when_not_preventing_writes
946+
assert_nothing_raised do
947+
Person.lock.find_by id: 1
948+
end
949+
end
950+
951+
def test_relation_lock_when_preventing_writes
952+
ActiveRecord::Base.while_preventing_writes do
953+
assert_raises(ActiveRecord::ReadOnlyError) do
954+
Person.lock.find_by id: 1
955+
end
956+
end
957+
end
958+
959+
def test_relation_lock_when_not_preventing_writes_nested
960+
ActiveRecord::Base.while_preventing_writes do
961+
ActiveRecord::Base.while_preventing_writes(false) do
962+
assert_nothing_raised do
963+
Person.lock.find_by id: 1
964+
end
965+
end
966+
end
967+
end
968+
969+
def test_custom_relation_lock_when_preventing_writes
970+
ActiveRecord::Base.while_preventing_writes do
971+
assert_raises(ActiveRecord::ReadOnlyError) do
972+
Person.lock(CUSTOM_LOCK).find_by id: 1
973+
end
974+
end
975+
end
976+
end

0 commit comments

Comments
 (0)