Skip to content

Commit fdde675

Browse files
committed
Implement with_connection(prevent_permanent_checkout: true) option
When `.connection` is called inside a `.with_connection` block it cause the lease to become permanent (or sticky). This is to ensure that legacy code that may hold onto this connection won't cause thread safety issues. However if you autidted the code that calls `.connection` and know for sure it won't hold into the returned connection, you can wrap it with `with_connection(prevent_permanent_checkout: true)` to prevent the lease from becoming permanent and ensure the connection is released at the end of the block.
1 parent dd8fd52 commit fdde675

File tree

3 files changed

+48
-11
lines changed

3 files changed

+48
-11
lines changed

activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,20 +123,20 @@ class Lease # :nodoc:
123123

124124
def initialize
125125
@connection = nil
126-
@sticky = false
126+
@sticky = nil
127127
end
128128

129129
def release
130130
conn = @connection
131131
@connection = nil
132-
@sticky = false
132+
@sticky = nil
133133
conn
134134
end
135135

136136
def clear(connection)
137137
if @connection == connection
138138
@connection = nil
139-
@sticky = false
139+
@sticky = nil
140140
true
141141
else
142142
false
@@ -298,8 +298,8 @@ def lease_connection
298298
lease.connection ||= checkout
299299
end
300300

301-
def sticky_lease? # :nodoc:
302-
connection_lease.sticky
301+
def permanent_lease? # :nodoc:
302+
connection_lease.sticky.nil?
303303
end
304304

305305
def connection
@@ -357,6 +357,7 @@ def connection_class # :nodoc:
357357
def active_connection?
358358
connection_lease.connection
359359
end
360+
alias_method :active_connection, :active_connection? # :nodoc:
360361

361362
# Signal that the thread is finished with the current connection.
362363
# #release_connection releases the connection-thread association
@@ -382,14 +383,22 @@ def release_connection(existing_lease = nil)
382383
# automatically at the end of the block; it is expected that such an existing
383384
# connection will be properly returned to the pool by the code that checked
384385
# it out.
385-
def with_connection
386+
def with_connection(prevent_permanent_checkout: false)
386387
lease = connection_lease
388+
sticky_was = lease.sticky
389+
lease.sticky = false if prevent_permanent_checkout
390+
387391
if lease.connection
388-
yield lease.connection
392+
begin
393+
yield lease.connection
394+
ensure
395+
lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
396+
end
389397
else
390398
begin
391399
yield lease.connection = checkout
392400
ensure
401+
lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
393402
release_connection(lease) unless lease.sticky
394403
end
395404
end

activerecord/lib/active_record/connection_handling.rb

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ def lease_connection
259259
# Soft deprecated. Use +#with_connection+ or +#lease_connection+ instead.
260260
def connection
261261
pool = connection_pool
262-
unless pool.sticky_lease?
262+
if pool.permanent_lease?
263263
case ActiveRecord.permanent_connection_checkout
264264
when :deprecated
265265
ActiveRecord.deprecator.warn <<~MESSAGE
@@ -274,8 +274,10 @@ def connection
274274
Either use `with_connection` or `lease_connection`.
275275
MESSAGE
276276
end
277+
pool.lease_connection
278+
else
279+
pool.active_connection
277280
end
278-
pool.lease_connection
279281
end
280282

281283
# Return the currently leased connection into the pool
@@ -286,8 +288,12 @@ def release_connection
286288
# Checkouts a connection from the pool, yield it and then check it back in.
287289
# If a connection was already leased via #lease_connection or a parent call to
288290
# #with_connection, that same connection is yieled.
289-
def with_connection(&block)
290-
connection_pool.with_connection(&block)
291+
# If #lease_connection is called inside the block, the connection won't be checked
292+
# back in.
293+
# If #connection is called inside the block, the connection won't be checked back in
294+
# unless the +prevent_permanent_checkout+ argument is set to +true+.
295+
def with_connection(prevent_permanent_checkout: false, &block)
296+
connection_pool.with_connection(prevent_permanent_checkout: prevent_permanent_checkout, &block)
291297
end
292298

293299
attr_writer :connection_specification_name

activerecord/test/cases/connection_handling_test.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ class ConnectionHandlingTest < ActiveRecord::TestCase
4141
assert_same conn, ActiveRecord::Base.lease_connection
4242
end
4343

44+
test "#lease_connection makes the lease permanent even inside #with_connection(prevent_permanent_checkout: true)" do
45+
ActiveRecord::Base.release_connection
46+
47+
ActiveRecord::Base.with_connection(prevent_permanent_checkout: true) do |connection|
48+
assert_same connection, ActiveRecord::Base.lease_connection
49+
end
50+
51+
assert_not_predicate ActiveRecord::Base.connection_pool, :active_connection?
52+
end
53+
4454
test "#with_connection use the already leased connection if available" do
4555
leased_connection = ActiveRecord::Base.lease_connection
4656
assert_predicate ActiveRecord::Base.connection_pool, :active_connection?
@@ -142,6 +152,18 @@ class ConnectionHandlingTest < ActiveRecord::TestCase
142152
ActiveRecord::Base.connection
143153
end
144154
end
155+
156+
test "#connection doesn't make the lease permanent if inside #with_connection(prevent_permanent_checkout: true)" do
157+
ActiveRecord.permanent_connection_checkout = :disallowed
158+
159+
ActiveRecord::Base.release_connection
160+
161+
ActiveRecord::Base.with_connection(prevent_permanent_checkout: true) do |connection|
162+
assert_same connection, ActiveRecord::Base.connection
163+
end
164+
165+
assert_not_predicate ActiveRecord::Base.connection_pool, :active_connection?
166+
end
145167
end
146168
end
147169
end

0 commit comments

Comments
 (0)