Tag Archive for 'memory leak'

Ruby/Rails y sus cositas…

Llevo un buen rato perdiendo el tiempo por culpa de un “behaviour” de ruby/rails, pero después de mucho batallar por fin he encontrado lo que estaba pasando, y lo he solucionado.

Escribo esto para ver si san google me indexa el blog y desde aqui puedo ayudar un poco a los pobres desarrolladores web que se enfrenten al mismo problema que yo… Memory Leaks!!

Pero esta vez de los gordos, nada de mariconadas como el GetText 1.90 y su Mb por request perdido….

El tema tiene relación con activerecord, una asociación has_many :through junto con otra has_and_belongs_to_many y un callback. Vayamos a por el ejemplo práctico:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class User < ActiveRecord::Base
  has_many :groups_users
  has_many :groups, :through => :groups_users
  has_and_belongs_to_many :discussions
end
 
class Group < ActiveRecord::Base
  has_many :groups_users
  has_many :users, :through => :groups_users
  has_one :discussion
  before_create :create_discussion
end
 
class GroupsUser < ActiveRecord::Base
  belongs_to :group
  belongs_to :user
  before_create :add_owner_to_discussion
 
  def add_user_to_discussion
    group.discussion.users << user
  end
 
end
 
class Discussion < ActiveRecord::Base
  belongs_to :group
  has_and_belongs_to_many :users
end

El tema está claro, un usuario puede pertenecer a n grupos, cada grupo tiene una discusión, y los usuarios pertenecen a estas discusiones.

Vayamos a la consola, primero creamos 3000 usuarios

1
>> 3000.times {|x| User.create }

Vale, ahora creemos un grupo

1
>> g = Group.create

Y como paso final…. le asignamos a este grupo todos los usuarios

1
>> User.all.each {|u| g.users << u}

PAM! Si lo has ejecutado de petará la maquina por exceso de memoria, en mi caso, 2 Gb de SWAP ocupadas en mi precioso MacBook Pro.

¿Cuál es el problema?

El memory leak está en el callback definido en GroupsUser. Al parecer en una asociación has_and_belongs_to_many, el método << devuelve el proxy (en nuestro caso users) y este proxy se queda en el limbo de ruby al finalizar la llamada al callback, provocando que para cada llamada al callback el proceso pierda bastante memoria (calculo que entre 500 kb y 1 Mb). ¿Cómo solucionarlo? Fácil, asigna una variable y despues nullificala para borrar de la memoria el proxy, en nuestro ejemplo:

1
2
3
4
  def add_user_to_discussion
    x = group.discussion.users << user
    x = nil
  end

That’s it, problema resuelto. Increible pero cierto. Atentos a vuestro código. ;)