coderrr

December 23, 2008

Singleton recursion bug

Filed under: bug, patch, ruby — Tags: , , — coderrr @ 8:56 pm

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.

No Comments Yet »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a comment

Blog at WordPress.com.