Skip to content

Commit a189655

Browse files
MONGOID-5417 Fix memory leak when call 'with' (#5409)
1 parent f232f5b commit a189655

File tree

2 files changed

+66
-5
lines changed

2 files changed

+66
-5
lines changed

lib/mongoid/persistence_context.rb

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,7 @@ class << self
189189
#
190190
# @return [ Mongoid::PersistenceContext ] The persistence context for the object.
191191
def set(object, options_or_context)
192-
key = "[mongoid][#{object.object_id}]:context"
193-
existing_context = Thread.current[key]
192+
existing_context = get_context(object)
194193
existing_options = if existing_context
195194
existing_context.options
196195
else
@@ -201,7 +200,7 @@ def set(object, options_or_context)
201200
end
202201
new_options = existing_options.merge(options_or_context)
203202
context = PersistenceContext.new(object, new_options)
204-
Thread.current[key] = context
203+
store_context(object, context)
205204
end
206205

207206
# Get the persistence context for a particular class or model instance.
@@ -213,7 +212,7 @@ def set(object, options_or_context)
213212
#
214213
# @return [ Mongoid::PersistenceContext ] The persistence context for the object.
215214
def get(object)
216-
Thread.current["[mongoid][#{object.object_id}]:context"]
215+
get_context(object)
217216
end
218217

219218
# Clear the persistence context for a particular class or model instance.
@@ -232,7 +231,44 @@ def clear(object, cluster = nil, original_context = nil)
232231
end
233232
end
234233
ensure
235-
Thread.current["[mongoid][#{object.object_id}]:context"] = original_context
234+
store_context(object, original_context)
235+
end
236+
237+
private
238+
239+
# Key to store persistence contexts in the thread local storage.
240+
#
241+
# @api private
242+
PERSISTENCE_CONTEXT_KEY = :"[mongoid]:persistence_context"
243+
244+
# Get the persistence context for a given object from the thread local
245+
# storage.
246+
#
247+
# @param [ Object ] object Object to get the persistance context for.
248+
#
249+
# @return [ Mongoid::PersistenceContext | nil ] The persistence context
250+
# for the object if previously stored, otherwise nil.
251+
#
252+
# @api private
253+
def get_context(object)
254+
Thread.current[PERSISTENCE_CONTEXT_KEY] ||= {}
255+
Thread.current[PERSISTENCE_CONTEXT_KEY][object.object_id]
256+
end
257+
258+
# Store persistence context for a given object in the thread local
259+
# storage.
260+
#
261+
# @param [ Object ] object Object to store the persistance context for.
262+
# @param [ Mongoid::PersistenceContext ] context Context to store
263+
#
264+
# @api private
265+
def store_context(object, context)
266+
if context.nil?
267+
Thread.current[PERSISTENCE_CONTEXT_KEY]&.delete(object.object_id)
268+
else
269+
Thread.current[PERSISTENCE_CONTEXT_KEY] ||= {}
270+
Thread.current[PERSISTENCE_CONTEXT_KEY][object.object_id] = context
271+
end
236272
end
237273
end
238274
end

spec/mongoid/clients_spec.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,6 +1042,31 @@
10421042
end
10431043
end
10441044
end
1045+
1046+
context 'when using on different objects' do
1047+
require_mri
1048+
1049+
let(:first_band) do
1050+
Band.create!(name: "The Beatles")
1051+
end
1052+
1053+
let(:second_band) do
1054+
Band.create!(name: 'Led Zeppelin')
1055+
end
1056+
1057+
it 'does not create extra symbols symbols' do
1058+
first_band.with(write: { w: 0, j: false }) do |band|
1059+
band.set(active: false)
1060+
end
1061+
initial_symbols_count = Symbol.all_symbols.size
1062+
second_band.with(write: { w: 0, j: false }) do |band|
1063+
band.set(active: false)
1064+
end
1065+
expect(Symbol.all_symbols.size).to eq(initial_symbols_count)
1066+
end
1067+
1068+
end
1069+
10451070
end
10461071

10471072
context "when overriding the default database" do

0 commit comments

Comments
 (0)