@@ -113,6 +113,90 @@ def db_config
113
113
# * private methods that require being called in a +synchronize+ blocks
114
114
# are now explicitly documented
115
115
class ConnectionPool
116
+ class Lease # :nodoc:
117
+ attr_accessor :connection , :sticky
118
+
119
+ def initialize
120
+ @connection = nil
121
+ @sticky = false
122
+ end
123
+
124
+ def release
125
+ conn = @connection
126
+ @connection = nil
127
+ @sticky = false
128
+ conn
129
+ end
130
+
131
+ def clear ( connection )
132
+ if @connection == connection
133
+ @connection = nil
134
+ @sticky = false
135
+ true
136
+ else
137
+ false
138
+ end
139
+ end
140
+ end
141
+
142
+ if ObjectSpace . const_defined? ( :WeakKeyMap ) # RUBY_VERSION >= 3.3
143
+ WeakKeyMap = ::ObjectSpace ::WeakKeyMap # :nodoc:
144
+ else
145
+ class WeakKeyMap # :nodoc:
146
+ def initialize
147
+ @map = ObjectSpace ::WeakMap . new
148
+ @values = nil
149
+ @size = 0
150
+ end
151
+
152
+ alias_method :clear , :initialize
153
+
154
+ def []( key )
155
+ prune if @map . size != @size
156
+ @map [ key ]
157
+ end
158
+
159
+ def []=( key , value )
160
+ @map [ key ] = value
161
+ prune if @map . size != @size
162
+ value
163
+ end
164
+
165
+ def delete ( key )
166
+ if value = self [ key ]
167
+ self [ key ] = nil
168
+ prune
169
+ end
170
+ value
171
+ end
172
+
173
+ private
174
+ def prune ( force = false )
175
+ @values = @map . values
176
+ @size = @map . size
177
+ end
178
+ end
179
+ end
180
+
181
+ class LeaseRegistry # :nodoc:
182
+ def initialize
183
+ @mutex = Mutex . new
184
+ @map = WeakKeyMap . new
185
+ end
186
+
187
+ def []( context )
188
+ @mutex . synchronize do
189
+ @map [ context ] ||= Lease . new
190
+ end
191
+ end
192
+
193
+ def clear
194
+ @mutex . synchronize do
195
+ @map = WeakKeyMap . new
196
+ end
197
+ end
198
+ end
199
+
116
200
include MonitorMixin
117
201
prepend QueryCache ::ConnectionPoolConfiguration
118
202
include ConnectionAdapters ::AbstractPool
@@ -148,9 +232,9 @@ def initialize(pool_config)
148
232
# then that +thread+ does indeed own that +conn+. However, an absence of such
149
233
# mapping does not mean that the +thread+ doesn't own the said connection. In
150
234
# that case +conn.owner+ attr should be consulted.
151
- # Access and modification of <tt>@thread_cached_conns </tt> does not require
235
+ # Access and modification of <tt>@leases </tt> does not require
152
236
# synchronization.
153
- @thread_cached_conns = Concurrent :: Map . new ( initial_capacity : @size )
237
+ @leases = LeaseRegistry . new
154
238
155
239
@connections = [ ]
156
240
@automatic_reconnect = true
@@ -203,14 +287,18 @@ def internal_metadata # :nodoc:
203
287
#
204
288
# #connection can be called any number of times; the connection is
205
289
# held in a cache keyed by a thread.
206
- def connection
207
- @thread_cached_conns [ ActiveSupport ::IsolatedExecutionState . context ] ||= checkout
290
+ def lease_connection
291
+ lease = connection_lease
292
+ lease . sticky = true
293
+ lease . connection ||= checkout
208
294
end
209
295
296
+ alias_method :connection , :lease_connection # TODO: deprecate
297
+
210
298
def pin_connection! ( lock_thread ) # :nodoc:
211
299
raise "There is already a pinned connection" if @pinned_connection
212
300
213
- @pinned_connection = ( @thread_cached_conns [ ActiveSupport :: IsolatedExecutionState . context ] || checkout )
301
+ @pinned_connection = ( connection_lease &. connection || checkout )
214
302
# Any leased connection must be in @connections otherwise
215
303
# some methods like #connected? won't behave correctly
216
304
unless @connections . include? ( @pinned_connection )
@@ -252,7 +340,7 @@ def connection_class # :nodoc:
252
340
# #connection or #with_connection methods. Connections obtained through
253
341
# #checkout will not be detected by #active_connection?
254
342
def active_connection?
255
- @thread_cached_conns [ ActiveSupport :: IsolatedExecutionState . context ]
343
+ connection_lease . connection
256
344
end
257
345
258
346
# Signal that the thread is finished with the current connection.
@@ -262,10 +350,12 @@ def active_connection?
262
350
# This method only works for connections that have been obtained through
263
351
# #connection or #with_connection methods, connections obtained through
264
352
# #checkout will not be automatically released.
265
- def release_connection ( owner_thread = ActiveSupport :: IsolatedExecutionState . context )
266
- if conn = @thread_cached_conns . delete ( owner_thread )
353
+ def release_connection ( existing_lease = nil )
354
+ if conn = connection_lease . release
267
355
checkin conn
356
+ return true
268
357
end
358
+ false
269
359
end
270
360
271
361
# Yields a connection from the connection pool to the block. If no connection
@@ -278,13 +368,14 @@ def release_connection(owner_thread = ActiveSupport::IsolatedExecutionState.cont
278
368
# connection will be properly returned to the pool by the code that checked
279
369
# it out.
280
370
def with_connection
281
- if conn = @thread_cached_conns [ ActiveSupport ::IsolatedExecutionState . context ]
282
- yield conn
371
+ lease = connection_lease
372
+ if lease . connection
373
+ yield lease . connection
283
374
else
284
375
begin
285
- yield connection
376
+ yield lease . connection = checkout
286
377
ensure
287
- release_connection
378
+ release_connection ( lease ) unless lease . sticky
288
379
end
289
380
end
290
381
end
@@ -326,7 +417,7 @@ def disconnect(raise_on_acquisition_timeout = true)
326
417
conn . disconnect!
327
418
end
328
419
@connections = [ ]
329
- @thread_cached_conns . clear
420
+ @leases . clear
330
421
@available . clear
331
422
end
332
423
end
@@ -353,7 +444,7 @@ def discard! # :nodoc:
353
444
@connections . each do |conn |
354
445
conn . discard!
355
446
end
356
- @connections = @available = @thread_cached_conns = nil
447
+ @connections = @available = @leases = nil
357
448
end
358
449
end
359
450
@@ -436,7 +527,7 @@ def checkin(conn)
436
527
437
528
conn . lock . synchronize do
438
529
synchronize do
439
- remove_connection_from_thread_cache conn
530
+ connection_lease . clear ( conn )
440
531
441
532
conn . _run_checkin_callbacks do
442
533
conn . expire
@@ -560,6 +651,10 @@ def schedule_query(future_result) # :nodoc:
560
651
end
561
652
562
653
private
654
+ def connection_lease
655
+ @leases [ ActiveSupport ::IsolatedExecutionState . context ]
656
+ end
657
+
563
658
def build_async_executor
564
659
case ActiveRecord . async_query_executor
565
660
when :multi_thread_pool
@@ -734,17 +829,10 @@ def acquire_connection(checkout_timeout)
734
829
#--
735
830
# if owner_thread param is omitted, this must be called in synchronize block
736
831
def remove_connection_from_thread_cache ( conn , owner_thread = conn . owner )
737
- @thread_cached_conns . delete_pair ( owner_thread , conn )
832
+ @leases [ owner_thread ] . clear ( conn )
738
833
end
739
834
alias_method :release , :remove_connection_from_thread_cache
740
835
741
- def prune_thread_cache
742
- dead_threads = @thread_cached_conns . keys . reject ( &:alive? )
743
- dead_threads . each do |dead_thread |
744
- @thread_cached_conns . delete ( dead_thread )
745
- end
746
- end
747
-
748
836
def new_connection
749
837
connection = db_config . new_connection
750
838
connection . pool = self
@@ -788,6 +876,12 @@ def try_to_checkout_new_connection
788
876
def adopt_connection ( conn )
789
877
conn . pool = self
790
878
@connections << conn
879
+
880
+ # We just created the first connection, it's time to load the schema
881
+ # cache if that wasn't eagerly done before
882
+ if @schema_cache . nil? && ActiveRecord . lazily_load_schema_cache
883
+ schema_cache . load!
884
+ end
791
885
end
792
886
793
887
def checkout_new_connection
0 commit comments