@@ -13,8 +13,7 @@ def included(base) # :nodoc:
13
13
:truncate_tables , :rollback_to_savepoint , :rollback_db_transaction , :restart_db_transaction ,
14
14
:exec_insert_all
15
15
16
- base . set_callback :checkout , :after , :configure_query_cache!
17
- base . set_callback :checkin , :after , :disable_query_cache!
16
+ base . set_callback :checkin , :after , :unset_query_cache!
18
17
end
19
18
20
19
def dirties_query_cache ( base , *method_names )
@@ -29,60 +28,153 @@ def #{method_name}(...)
29
28
end
30
29
end
31
30
32
- module ConnectionPoolConfiguration
33
- def initialize ( *)
31
+ class Store # :nodoc:
32
+ attr_reader :enabled
33
+ alias_method :enabled? , :enabled
34
+
35
+ def initialize ( max_size )
36
+ @map = { }
37
+ @max_size = max_size
38
+ @enabled = false
39
+ end
40
+
41
+ def enabled = ( enabled )
42
+ clear if @enabled && !enabled
43
+ @enabled = enabled
44
+ end
45
+
46
+ def size
47
+ @map . size
48
+ end
49
+
50
+ def empty?
51
+ @map . empty?
52
+ end
53
+
54
+ def []( key )
55
+ return unless @enabled
56
+
57
+ if entry = @map . delete ( key )
58
+ @map [ key ] = entry
59
+ end
60
+ end
61
+
62
+ def compute_if_absent ( key )
63
+ return yield unless @enabled
64
+
65
+ if entry = @map . delete ( key )
66
+ return @map [ key ] = entry
67
+ end
68
+
69
+ if @max_size && @map . size >= @max_size
70
+ @map . shift # evict the oldest entry
71
+ end
72
+
73
+ @map [ key ] ||= yield
74
+ end
75
+
76
+ def clear
77
+ @map . clear
78
+ self
79
+ end
80
+ end
81
+
82
+ module ConnectionPoolConfiguration # :nodoc:
83
+ def initialize ( ...)
34
84
super
35
- @query_cache_enabled = Concurrent ::Map . new { false }
85
+ @thread_query_caches = Concurrent ::Map . new ( initial_capacity : @size )
86
+ @query_cache_max_size = \
87
+ case query_cache = db_config &.query_cache
88
+ when 0 , false
89
+ nil
90
+ when Integer
91
+ query_cache
92
+ when nil
93
+ DEFAULT_SIZE
94
+ end
95
+ end
96
+
97
+ def checkout ( ...)
98
+ connection = super
99
+ connection . query_cache ||= query_cache
100
+ connection
101
+ end
102
+
103
+ # Disable the query cache within the block.
104
+ def disable_query_cache
105
+ cache = query_cache
106
+ old , cache . enabled = cache . enabled , false
107
+ begin
108
+ yield
109
+ ensure
110
+ cache . enabled = old
111
+ end
112
+ end
113
+
114
+ def enable_query_cache
115
+ cache = query_cache
116
+ old , cache . enabled = cache . enabled , true
117
+ begin
118
+ yield
119
+ ensure
120
+ cache . enabled = old
121
+ end
36
122
end
37
123
38
124
def enable_query_cache!
39
- @query_cache_enabled [ connection_cache_key ( ActiveSupport ::IsolatedExecutionState . context ) ] = true
40
- connection . enable_query_cache! if active_connection?
125
+ query_cache . enabled = true
41
126
end
42
127
43
128
def disable_query_cache!
44
- @query_cache_enabled . delete connection_cache_key ( ActiveSupport ::IsolatedExecutionState . context )
45
- connection . disable_query_cache! if active_connection?
129
+ query_cache . enabled = false
46
130
end
47
131
48
132
def query_cache_enabled
49
- @query_cache_enabled [ connection_cache_key ( ActiveSupport :: IsolatedExecutionState . context ) ]
133
+ query_cache . enabled
50
134
end
135
+
136
+ private
137
+ def prune_thread_cache
138
+ dead_threads = @thread_query_caches . keys . reject ( &:alive? )
139
+ dead_threads . each do |dead_thread |
140
+ @thread_query_caches . delete ( dead_thread )
141
+ end
142
+ end
143
+
144
+ def query_cache
145
+ @thread_query_caches . compute_if_absent ( ActiveSupport ::IsolatedExecutionState . context ) do
146
+ Store . new ( @query_cache_max_size )
147
+ end
148
+ end
51
149
end
52
150
53
- attr_reader :query_cache , :query_cache_enabled
151
+ attr_accessor :query_cache
54
152
55
153
def initialize ( *)
56
154
super
57
- @query_cache = { }
58
- @query_cache_enabled = false
59
- @query_cache_max_size = nil
155
+ @query_cache = nil
156
+ end
157
+
158
+ def query_cache_enabled
159
+ @query_cache &.enabled?
60
160
end
61
161
62
162
# Enable the query cache within the block.
63
- def cache
64
- old , @query_cache_enabled = @query_cache_enabled , true
65
- yield
66
- ensure
67
- @query_cache_enabled = old
68
- clear_query_cache unless @query_cache_enabled
163
+ def cache ( &)
164
+ pool . enable_query_cache ( &)
69
165
end
70
166
71
167
def enable_query_cache!
72
- @query_cache_enabled = true
168
+ pool . enable_query_cache!
73
169
end
74
170
75
- def disable_query_cache!
76
- @query_cache_enabled = false
77
- clear_query_cache
171
+ # Disable the query cache within the block.
172
+ def uncached ( & )
173
+ pool . disable_query_cache ( & )
78
174
end
79
175
80
- # Disable the query cache within the block.
81
- def uncached
82
- old , @query_cache_enabled = @query_cache_enabled , false
83
- yield
84
- ensure
85
- @query_cache_enabled = old
176
+ def disable_query_cache!
177
+ pool . disable_query_cache!
86
178
end
87
179
88
180
# Clears the query cache.
@@ -93,7 +185,7 @@ def uncached
93
185
# undermining the randomness you were expecting.
94
186
def clear_query_cache
95
187
@lock . synchronize do
96
- @query_cache . clear
188
+ @query_cache & .clear
97
189
end
98
190
end
99
191
@@ -102,7 +194,7 @@ def select_all(arel, name = nil, binds = [], preparable: nil, async: false) # :n
102
194
103
195
# If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
104
196
# Such queries should not be cached.
105
- if @query_cache_enabled && !( arel . respond_to? ( :locked ) && arel . locked )
197
+ if @query_cache &. enabled? && !( arel . respond_to? ( :locked ) && arel . locked )
106
198
sql , binds , preparable = to_sql_and_binds ( arel , binds , preparable )
107
199
108
200
if async
@@ -117,42 +209,37 @@ def select_all(arel, name = nil, binds = [], preparable: nil, async: false) # :n
117
209
end
118
210
119
211
private
212
+ def unset_query_cache!
213
+ @query_cache = nil
214
+ end
215
+
120
216
def lookup_sql_cache ( sql , name , binds )
121
217
key = binds . empty? ? sql : [ sql , binds ]
122
- hit = false
123
- result = nil
124
218
219
+ result = nil
125
220
@lock . synchronize do
126
- if ( result = @query_cache . delete ( key ) )
127
- hit = true
128
- @query_cache [ key ] = result
129
- end
221
+ result = @query_cache [ key ]
130
222
end
131
223
132
- if hit
224
+ if result
133
225
ActiveSupport ::Notifications . instrument (
134
226
"sql.active_record" ,
135
227
cache_notification_info ( sql , name , binds )
136
228
)
137
-
138
- result
139
229
end
230
+
231
+ result
140
232
end
141
233
142
234
def cache_sql ( sql , name , binds )
143
235
key = binds . empty? ? sql : [ sql , binds ]
144
236
result = nil
145
- hit = false
237
+ hit = true
146
238
147
239
@lock . synchronize do
148
- if ( result = @query_cache . delete ( key ) )
149
- hit = true
150
- @query_cache [ key ] = result
151
- else
152
- result = @query_cache [ key ] = yield
153
- if @query_cache_max_size && @query_cache . size > @query_cache_max_size
154
- @query_cache . shift
155
- end
240
+ result = @query_cache . compute_if_absent ( key ) do
241
+ hit = false
242
+ yield
156
243
end
157
244
end
158
245
@@ -178,23 +265,6 @@ def cache_notification_info(sql, name, binds)
178
265
cached : true
179
266
}
180
267
end
181
-
182
- def configure_query_cache!
183
- case query_cache = pool . db_config . query_cache
184
- when 0 , false
185
- return
186
- when Integer
187
- @query_cache_max_size = query_cache
188
- when nil
189
- @query_cache_max_size = DEFAULT_SIZE
190
- else
191
- @query_cache_max_size = nil # no limit
192
- end
193
-
194
- if pool . query_cache_enabled
195
- enable_query_cache!
196
- end
197
- end
198
268
end
199
269
end
200
270
end
0 commit comments