You've probably read somewhere that Symbols are never GC'ed in Ruby.
And if you've seen this you'll also know that a Symbol
takes at least around 60 bytes.
But did you keep that in mind the last time you were doing some meta-programming?
Very often, you need to come up with a unique name, and write things like
name = "__prefix_#{rand(1000000)}"
or even, in order to achieve thread-safety,
name = "__prefix_#{rand(1000000)}_#{Thread.current.object_id.abs}"
Then, you would often define a method with that name, as in
class Object
def unsuspect(*args, &block)
obj = self
class << obj; self end.module_eval do
name = "__unsuspect_#{rand(10000000)}_#{Thread.current.object_id.abs}"
begin
define_method(name, &block)
obj.send(name, *args)
ensure
remove_method name
end
end
end
end
Do you recognize that snippet? It's a simple Object#instance_exec implementation
(I've made a better one, but I'll write some more on this later):
"foo".unsuspect(" bar"){|x| self + x}
Now, did you know that the innocent looking #unsuspect method will bring your
long-running processes down to their knees?
When you do
define_method(name)
Ruby converts name into a Symbol. Which will never be released. So #unsuspect is leaking memory at the rate
of ~60 bytes (at least) per call. The amount of unreclaimed memory will be around 60 bytes times
the number of unique names that will be generated. In the above example, it'd be one million times the
total number of threads you will run over the life of the program
(not even simultaneously!).
Some figures
Let's see a small example: if you perform 1000 calls to #unsuspect per thread, and create new threads
at the rate of 200 an hour, you'd be leaking as much as about
Read more...
Read: Symbols, meta-programming and leaks. On harakiri and DoSing Rails?