4

I have a memory leak in a Rails 4.2.6 application. A controller allocates a large GaragesPresenter object as an instance variable, which should be de-referenced and garbage collected after the request completes. However, I see that this never happens.

def show
  @garage = GaragesPresenter.new(@garage, view_context)
  respond_to do |format|
    format.html
  end
end

I see that a reference to the GaragesPresenter instance is being held by the GaragesController instance, and an instance to that is being held by the GaragesController class. This is true long after the request has completed and GC.start has been called. Why is the GaragesController class holding a reference to the instance?

I know this because I set up a heap dump with:

require 'objspace'
...
GC.start
file = File.open("/tmp/dumpfile", 'w')
ObjectSpace.dump_all(output: file)

And in the resulting file I see the following three objects:

The following object is a GaragesPresenter, which is very large:

{"address":"0x7fd077217e20", "type":"OBJECT", "class":"0x7fd074a04618", "ivars":7, "references":["0x7fd0772bf940", "0x7fd077711480", "0x7fd077748188", "0x7fd077772898", "0x7fd07720c778", "0x7fd0771ef8d0", "0x7fd0771ef8d0"], "file":"/Users/dyoung/workspace/commutyble/site-app/app/controllers/garages_controller.rb", "line":19, "method":"new", "generation":35, "memsize":56, "flags":{"wb_protected":true, "old":true, "marked":true}}

A reference to the above object is being held by a GaragesController instance (expected, as the show method allocates the presenter as an instance variable):

{"address":"0x7fd0727559f0", "type":"OBJECT", "class":"0x7fd0727865a0", "ivars":22, "references":["0x7fd0727558b0", "0x7fd072755888", "0x7fd072755838", "0x7fd0732400e0", "0x7fd072754a50", "0x7fd0734c5658", "0x7fd07704e878", "0x7fd0732ab020", "0x7fd072785ee8", "0x7fd077217e20", "0x7fd0771ffe10", "0x7fd07720cde0", "0x7fd0732a82d0"], "file":"/Users/dyoung/.rvm/gems/ruby-2.1.0/gems/actionpack-4.2.6/lib/action_controller/metal.rb", "line":237, "method":"new", "generation":35, "memsize":176, "flags":{"wb_protected":true, "old":true, "marked":true}}

A reference to the above GaragesController instance is being held by the GaragesController class, presumably preventing garabage collection. Why??

{"address":"0x7fd0727865a0", "type":"CLASS", "class":"0x7fd0726a7260", "name":"GaragesController", "references":["0x7fd0727559f0", "0x7fd0726a72b0"], "file":"/Users/dyoung/.rvm/gems/ruby-2.1.0/gems/activesupport-4.2.6/lib/active_support/callbacks.rb", "line":435, "method":"instance_exec", "generation":35, "memsize":672, "flags":{"wb_protected":true, "old":true, "marked":true}}

davidgyoung
  • 63,876
  • 14
  • 121
  • 204
  • 1
    Seems `[WeakRef](ruby-doc.org/stdlib-1.9.3/libdoc/weakref/rdoc/WeakRef.html)` can help – oklas Feb 07 '17 at 21:39

2 Answers2

3

You need to use WeakRef

Weak Reference class that allows a referenced object to be garbage-collected. A WeakRef may be used exactly like the object it references.

foo = Object.new

foo = WeakRef.new(foo) # Creates a weak reference to orig

ObjectSpace.garbage_collect

p foo.to_s       # should raise exception (recycled)

The use case is there where two object reference is used. First is master and second is weak. Your object will not be garbage collected until first master link is in use. Use master link (generic variable) in the object which live same or more time than we need referenced object. And inside of referenced object use weak link.

It is corresponding practice for this case. In another languages with garbage collector too, in perl for example. The C++ libraries have too much solutions for memory management strategies. Garbage collector can not remove garbage (objects) when they in use. If object refer to another and another refer to first this means both in use. So it is not garbage - it is useful for "garbage collector opinion". But really it is garbage - and it is memory leak.

Graph of objects-references must not have loops or cycles. If we need reference that forms loop or cycle in objects-references graph we need to use at least one quasi cutting weak reference in each.

oklas
  • 7,935
  • 2
  • 26
  • 42
  • Thanks for the tip. But I don't want my `@garage` object to be garbage collected until I am done using it. It seems that if I use `WeakRef` then there is a chance it could be garbage collected before the controller method completes. Am I wrong? – davidgyoung Feb 07 '17 at 21:45
  • Always will be fine. I did some improvement of answer. Use master link in that place where it not be garbage collected. – oklas Feb 07 '17 at 21:50
  • Yes, it seems like that may work with the improvement. But this seems to have the same effect of me simply setting `@garage = nil` at the end of my method. While either of these may be a reasonable workaround, my understanding is that I shouldn't *need* to do this in Rails. I'd like to first understand what I am doing wrong, if anything, that is causing this unexpected behavior. – davidgyoung Feb 07 '17 at 21:55
  • It is not workaround. It is only way. I'll try to open the full. – oklas Feb 08 '17 at 07:26
3

GaragesPresenter holds a reference to view_context

@garage = GaragesPresenter.new(@garage, view_context)

view_context returns an instance of of a view class which holds a reference to self, which is the invoking controller:

# File actionview/lib/action_view/rendering.rb, line 71
def view_context
  view_context_class.new(view_renderer, view_assigns, self)
end
fylooi
  • 3,840
  • 14
  • 24
  • Thanks. This should not prevent garbage collection though, right? I think it's OK for the presenter to hold a reference to the controller instance because both objects should only exist for the lifetime of the request. Both objects should be eligible for garbage collection after the request ends. – davidgyoung Feb 08 '17 at 12:35
  • It shouldn't, unless something is holding on to either the controller or presenter, in which case both will keep existing. I'd take a closer look at whether any constants are getting mutated. – fylooi Feb 08 '17 at 18:41
  • Yes, in my case, I have evidence that the something is holding a reference to the controller. What do you mean by "whether any constants are getting mutated"? – davidgyoung Feb 08 '17 at 20:29
  • 1
    Constants don't get garbage cleaned, so if you have an array or hash in a constant, the contents of that array / hash will never go out of scope. This may not be obvious, eg. `values = DEFAULT_VALUES; values << some_object`. – fylooi Feb 09 '17 at 04:18