rspec seems to leak memory (never releases spec instances) #321

Closed
carllerche opened this Issue Feb 21, 2011 · 10 comments

6 participants

@carllerche

I was forced to manually set all ivars in a spec to nil in order to get the GC (jruby) to clean up the references. Not doing this will cause the JVM to exit due to being out of memory.

https://github.com/strobecorp/kirk/blob/a7873fd0a288366cd41de30fb8201c74587ab619/spec/spec_helper.rb#L26

@dchelimsky
RSpec member

Duplicate issue: #312.

@dchelimsky
RSpec member

After more thought I think this is a diff issue. I've pushed a potential fix to the issue-321 branch. Can you point your Gemfile to this branch and give it a whirl?

@jensb

Seems to fix my memory hole issue.

@jensb

Well, almost. RAM usage of the ruby process running my tests still grows, and constantly, and never releases any memory. But it grows from ~120MB to about 300MB, and not to 900MB any more.
But still, all objects that I instantiated during any test, are still available in the "after(:all)" block, even if they are not instance variables but normal local variables. For example, after running a specific model spec I have 108 User instances in ObjectSpace, although there is only one user in the test DB (per fixture) and it is always reused.

@ajh

I'm seeing this issue as well and the issue-321 branch fixes it. Could this branch be merged into master?

This is a big issue with my team where we have a test suite with 5k examples, each of which leaks memory all their instance variables.

In case it helps, I've created a very simple example project that reproduces the issue with rspec-core 2.5.1. Applying the issue-321 fix makes the problem go away. Here's the link: https://github.com/ajh/rspec-core-leak

@kaiwren
require 'spec_helper'
require 'jruby'
JRuby.objectspace=true

class Foo
  def self.instance_count
    count = 0
    ObjectSpace.each_object(Foo) { count+=1 }
    count
  end
end

describe 'count instances' do
  before(:each) { @woot = Foo.new }
  after(:each) { puts Foo.instance_count }
  after(:all) { puts Foo.instance_count }

  20.times do |i|
    it "is a placeholder spec #{i}" { true.should eq(true) }
  end
end

On master, at the end of the test there are 20 instances of Foo. Placing a Foo.instance_count in a spec that runs later in the build also shows a count of 20.

On the issue-321 branch, the latter part is fixed - a call to Foo.instance_count in a spec that runs after this one returns a count of 0. However while the spec is running the instance count still rises to 19 on JRuby, 20 on 1.8.7 and 12 on 1.9.2, so it looks like there is still a leak but it's now restricted to within the scope of a running spec.

@dchelimsky
RSpec member

I just rebased that branch off master, and added another commit that nullifies ivars after each example.

With your example modified to include an explicit GC call, the following prints 20 at the end on master, but 0 at the end on the issue-321 branch:

 class Foo
  def self.instance_count
    count = 0
    ObjectSpace.each_object(Foo) { count+=1 }
    count
  end
end

describe 'count instances' do
  before(:each) { @woot = Foo.new }
  after(:each) { puts Foo.instance_count }
  after(:all) do
    ObjectSpace.garbage_collect
    puts Foo.instance_count
  end

  20.times do |i|
    it("is a placeholder spec #{i}") { true.should eq(true) }
  end
end

@carllerche and @jensb, can you please verify that this resolves your issue? If so, I'll merge it to master before the next release.

@jensb

It gets better and better ... now my specs run in 340s instead of 400s and the ruby process never consumes more than 258MB of RAM (instead of ~300MB as before).

Thank you!

@deepakinseattle

Another +1 for this patch. I noticed a significant improvement in memory consumption after switching to this branch.

@dchelimsky
RSpec member

Address memory leaks.

  • set each example's instance of the group to nil after processing.

This dereferences the example group instance which contains all of the
example's state, thereby releasing each example for garbage collection
after processing.

Note that this does not dereference state initialized in before(:all),
but those instance variables are cleared out in a separate process.

  1. nullify ivars after each example

@dchelimsky dchelimsky closed this Apr 7, 2011
@kaiwren kaiwren pushed a commit that referenced this issue Apr 16, 2011
@dchelimsky dchelimsky Address memory leaks.
- set each example's instance of the group to nil after processing.

This dereferences the example group instance which contains all of the
example's state, thereby releasing each example for garbage collection
after processing.

Note that this does not dereference state initialized in before(:all),
but those instance variables are cleared out in a separate process.

2. nullify ivars after each example

- Closes #321.
b51be2c
@JoshCheek JoshCheek added a commit to JoshCheek/rspec-core that referenced this issue Apr 28, 2015
@JoshCheek JoshCheek Allows user code to retain references to memoized helpers
Accomplish this by no longer clearing the example's ivars.

Context:

* Originally clearing ivars due to memory leak:
  rspec#321
* Threadsafe memoized helpers caused `__memoized` to stop lazily initializing:
  rspec#321
* This caused it to be permawiped by by the resetting of the example's ivars:
  rspec#1921

However, this patch tests the behaviour of the memory leak,
rather than its mechanics, which shows that it was fixed at some point.
So we simply remove that code.
6b101e1
@JoshCheek JoshCheek added a commit to JoshCheek/rspec-core that referenced this issue Apr 28, 2015
@JoshCheek JoshCheek Allows user code to retain references to memoized helpers
Accomplish this by no longer clearing the example's ivars.

Fixes rspec/rspec-core#1921

Context
-------

* Originally clearing ivars due to memory leak:
  rspec#321
* Threadsafe memoized helpers caused `__memoized` to stop lazily initializing:
  rspec#321
* This caused it to be permawiped by by the resetting of the example's ivars:
  rspec#1921

However, this patch tests the behaviour of the memory leak,
rather than its mechanics, which shows that it was fixed at some point.
So we simply remove that code.
58c2574
@JoshCheek JoshCheek added a commit to JoshCheek/rspec-core that referenced this issue Apr 28, 2015
@JoshCheek JoshCheek Allows user code to retain references to memoized helpers
Accomplish this by no longer clearing the example's ivars.

Fixes rspec/rspec-core#1921

Context
-------

* Originally clearing ivars due to memory leak:
  rspec#321
* Threadsafe memoized helpers caused `__memoized` to stop lazily initializing:
  rspec#321
* This caused it to be permawiped by by the resetting of the example's ivars:
  rspec#1921

However, this patch tests the behaviour of the memory leak,
rather than its mechanics, which shows that it was fixed at some point.
So we simply remove that code.
000214d
@JoshCheek JoshCheek added a commit to JoshCheek/rspec-core that referenced this issue Apr 28, 2015
@JoshCheek JoshCheek Allows user code to retain references to memoized helpers
Accomplish this by no longer clearing the example's ivars.

Fixes rspec/rspec-core#1921

Context
-------

* Originally clearing ivars due to memory leak:
  rspec#321
* Threadsafe memoized helpers caused `__memoized` to stop lazily initializing:
  rspec#321
* This caused it to be permawiped by by the resetting of the example's ivars:
  rspec#1921

However, this patch tests the behaviour of the memory leak,
rather than its mechanics, which shows that it was fixed at some point.
So we simply remove that code.
c41c3b2
@JoshCheek JoshCheek added a commit to JoshCheek/rspec-core that referenced this issue Apr 28, 2015
@JoshCheek JoshCheek Allows user code to retain references to memoized helpers
Accomplish this by no longer clearing the example's ivars.

Fixes rspec/rspec-core#1921

Context
-------

* Originally clearing ivars due to memory leak:
  rspec#321
* Threadsafe memoized helpers caused `__memoized` to stop lazily initializing:
  rspec#321
* This caused it to be permawiped by by the resetting of the example's ivars:
  rspec#1921

However, this patch tests the behaviour of the memory leak,
rather than its mechanics, which shows that it was fixed at some point.
So we simply remove that code.
5bc2165
@JoshCheek JoshCheek added a commit to JoshCheek/rspec-core that referenced this issue Apr 29, 2015
@JoshCheek JoshCheek Allows user code to retain references to memoized helpers
Accomplish this by no longer clearing the example's ivars.

Fixes rspec/rspec-core#1921

Context
-------

* This pull request:
  rspec#1950
* Originally clearing ivars due to memory leak:
  rspec#321
* Threadsafe memoized helpers caused `__memoized` to stop lazily initializing:
  rspec#321
* This caused it to be permawiped by by the resetting of the example's ivars:
  rspec#1921

However, this patch tests the behaviour of the memory leak,
rather than its mechanics, which shows that it was fixed at some point.
So we simply remove that code.
a50b765
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment