Uploaded image for project: 'Mongoid'
  1. Mongoid
  2. MONGOID-5820

Session object leaks when using transactions

    • Type: Icon: Bug Bug
    • Resolution: Fixed
    • Priority: Icon: Critical - P2 Critical - P2
    • 9.0.3
    • Affects Version/s: 9.0.2
    • Component/s: None
    • None
    • Ruby Drivers
    • Not Needed
    • Hide

      1. What would you like to communicate to the user about this feature?
      2. Would you like the user to see examples of the syntax and/or executable code and its output?
      3. Which versions of the driver/connector does this apply to?

      Show
      1. What would you like to communicate to the user about this feature? 2. Would you like the user to see examples of the syntax and/or executable code and its output? 3. Which versions of the driver/connector does this apply to?

      Copied from PR - https://github.com/mongodb/mongoid/pull/5878

      The issue

      I found the memory leak inside Threaded class, when saving documents within transaction: session object is retained forever inside thread-local variable.

      Explanation

      Mongoid uses thread-local variables to temporarily hold modified_documents until transaction is committed.
      The [mongoid]:modified-documents contains a hash with session as key and Set of documents as value.
      During transaction commit phase, the Mongoid::Threaded::clear_modified_documents is called, which clears documents from the set, however, the hash inside [mongoid]:modified-documents still contains a session object as a key.
      This session object is then persists in memory, even after the session was finished by the Mongoid::Threaded::clear_session call.

      Testing

      I used the following code to reproduce the issue:

      require_relative 'spec_helper'
      require 'memory_profiler'
      
      describe "Mongoid test" do
        let(:model) do
          Class.new do
            include Mongoid::Document
      
            store_in collection: 'tests'
            field :name, type: String
          end
        end
      
        context 'memory leak test' do
          it 'store in transaction' do
            run_memory_profiling do
              model.transaction do
                model.create!(name: 'test')
              end
            end
          end
        end
      
        def run_memory_profiling(&block)
          # Warmup
          yield
      
          report = MemoryProfiler.report do
            2000.times(&block)
            GC.start(full_mark: true, immediate_sweep: true)
          end
          p "Total retained: #{report.total_retained_memsize / 1024} KB (#{report.total_retained} objects). "
        end
      end
      

      Initial results:

      Total retained: 3430 KB (36053 objects).
      Report shows a number of retained objects, which corresponds to 2000 iterations of test.

      Results after modification:

      Total retained: 6 KB (61 objects).

            Assignee:
            dmitry.rybakov@mongodb.com Dmitry Rybakov
            Reporter:
            dmitry.rybakov@mongodb.com Dmitry Rybakov
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated:
              Resolved: