What would you expect the following code to do?
require 'singleton'
class X
include Singleton
def initialize
X.instance
end
end
X.instance
Stack overflow right? Wrong! In Ruby 1.8 it’ll hang forever because of the way thread-safety is handled with a while loop.
def _instantiate?()
while false.equal?(@__instance__)
Thread.critical = false
sleep(0.08) # timeout
Thread.critical = true
end
@__instance__
end
Basically the first call to X.instance sets @__instance__ to false while it calls the initialize method to make sure other threads don’t call it at the same time. When initialize calls X.instance again it enters that while loop and sits there forever. This can make debugging a little tricky in a complex app where you might accidentally put a recursive call to instance 5 methods down the line and wonder why the hell your app isn’t doing anything. So watch out for this if you use Singleton.
If you wanna be really safe you can use my patched version which checks for recursive calls and raises an error. Or you can just throw this monkey patch somewhere in your app which prints a warning after it’s hanged for 5 seconds:
require 'singleton'
require 'timeout'
class << Singleton
module SingletonClassMethods
private
def _instantiate?
start_time = Time.now
while false.equal?(@__instance__)
Thread.critical = false
sleep(0.08) # timeout
Thread.critical = true
if Time.now - start_time > 5
@__once__ ||= ! $stderr.puts("Possible recursive call to instance in initialize", caller)
end
end
@__instance__
end
end
end
In Ruby 1.9 Singleton is rewritten using a Mutex so you’ll get a nice deadlock message instead of the hang. In JRuby they use a Mutex too, but you won’t get any message about the deadlock. So effectively it will act the same as 1.8. My monkey patch won’t work for JRuby but the full patched version which I linked to on github will.
