lib/singleton.rb


DEFINITIONS

This source file includes following functions.


   1  # The Singleton module implements the Singleton pattern.
   2  #
   3  # Usage:
   4  #    class Klass
   5  #       include Singleton
   6  #       # ...
   7  #    end
   8  #
   9  # *  this ensures that only one instance of Klass lets call it
  10  #    ``the instance'' can be created.
  11  #
  12  #    a,b  = Klass.instance, Klass.instance
  13  #    a == b   # => true
  14  #    a.new     #  NoMethodError - new is private ...
  15  #
  16  # *  ``The instance'' is created at instanciation time, in other words
  17  #    the first call of Klass.instance(), thus
  18  #
  19  #    class OtherKlass
  20  #        include Singleton
  21  #        # ...
  22  #    end
  23  #    ObjectSpace.each_object(OtherKlass){} # => 0.
  24  #
  25  # *  This behavior is preserved under inheritance and cloning.
  26  #
  27  #
  28  # This is achieved by marking
  29  # *  Klass.new and Klass.allocate - as private
  30  # *  removing #clone and #dup and modifying 
  31  # *  Klass.inherited(sub_klass) and Klass.clone()  - 
  32  #    to ensure that the Singleton pattern is properly
  33  #    inherited and cloned.
  34  #
  35  # In addition Klass is providing the additional class methods
  36  # *  Klass.instance()  -  returning ``the instance''. After a successful
  37  #    self modifying instanciating first call the method body is a simple
  38  #           def Klass.instance()
  39  #               return @__instance__
  40  #           end
  41  # *  Klass._load(str)  -  calls instance()
  42  # *  Klass._instanciate?()  -  returning ``the instance'' or nil
  43  #    This hook method puts a second (or nth) thread calling
  44  #    Klass.instance() on a waiting loop. The return value signifies
  45  #    the successful completion or premature termination of the
  46  #    first, or more generally, current instanciating thread.
  47  #
  48  # The sole instance method of Singleton is
  49  # *  _dump(depth) - returning the empty string.  Marshalling strips
  50  #    by default all state information, e.g. instance variables and taint
  51  #    state, from ``the instance''.  Providing custom _load(str) and
  52  #    _dump(depth) hooks allows the (partially) resurrections of a
  53  #    previous state of ``the instance''.
  54  
  55  module Singleton
  56      private 
  57      #  default marshalling strategy
  58      def _dump(depth=-1) '' end
  59      
  60      class << self
  61          #  extending an object with Singleton is a bad idea
  62          undef_method :extend_object
  63          private
  64          def append_features(mod)
  65              #  This catches ill advisted inclusions of Singleton in
  66              #  singletons types (sounds like an oxymoron) and 
  67              #  helps out people counting on transitive mixins
  68              unless mod.instance_of?(Class)
  69                  raise TypeError, "Inclusion of the OO-Singleton module in module #{mod}"
  70              end 
  71              unless (class << mod; self end) <= (class << Object; self end)
  72                  raise TypeError, "Inclusion of the OO-Singleton module in singleton type"
  73              end
  74              super
  75          end
  76          def included(klass)
  77              #  remove build in copying methods
  78              klass.class_eval do 
  79                define_method(:clone) {raise TypeError, "can't clone singleton #{self.type}"}
  80              end
  81              
  82              #  initialize the ``klass instance variable'' @__instance__ to nil
  83              klass.instance_eval do @__instance__ = nil end
  84              class << klass
  85                  #  a main point of the whole exercise - make
  86                  #  new and allocate private
  87                  private  :new, :allocate
  88                  
  89                  #  declare the self modifying klass#instance method
  90                  define_method(:instance, Singleton::FirstInstanceCall) 
  91                   
  92                  #  simple waiting loop hook - should do in most cases
  93                  #  note the pre/post-conditions of a thread-critical state
  94                  private   
  95                  def _instanciate?()
  96                      while false.equal?(@__instance__)
  97                          Thread.critical = false
  98                          sleep(0.08)  
  99                          Thread.critical = true
 100                      end
 101                      @__instance__
 102                  end
 103                  
 104                  #  default Marshalling strategy
 105                  def _load(str) instance end    
 106                   
 107                 #  ensure that the Singleton pattern is properly inherited   
 108                  def inherited(sub_klass)
 109                      super
 110                      sub_klass.instance_eval do @__instance__ = nil end
 111                      class << sub_klass
 112                          define_method(:instance, Singleton::FirstInstanceCall) 
 113                      end
 114                  end 
 115                  
 116                  public
 117                  #  properly clone the Singleton pattern. Question - Did
 118                  #  you know that duping doesn't copy class methods?
 119                  def clone
 120                      res = super
 121                      res.instance_eval do @__instance__ = nil end
 122                      class << res
 123                          define_method(:instance, Singleton::FirstInstanceCall)
 124                      end
 125                      res
 126                  end
 127              end   #  of << klass
 128          end       #  of included
 129      end           #  of << Singleton
 130      
 131      FirstInstanceCall = proc do
 132          #  @__instance__ takes on one of the following values
 133          #  * nil     -  before and after a failed creation
 134          #  * false  -  during creation
 135          #  * sub_class instance  -  after a successful creation
 136          #  the form makes up for the lack of returns in progs
 137          Thread.critical = true
 138          if  @__instance__.nil?
 139              @__instance__  = false
 140              Thread.critical = false
 141              begin
 142                  @__instance__ = new
 143              ensure
 144                  if @__instance__
 145                      def self.instance() @__instance__ end
 146                  else
 147                      @__instance__ = nil #  failed instance creation
 148                  end
 149              end
 150          elsif  _instanciate?()
 151              Thread.critical = false    
 152          else
 153              @__instance__  = false
 154              Thread.critical = false
 155              begin
 156                  @__instance__ = new
 157              ensure
 158                  if @__instance__
 159                      def self.instance() @__instance__ end
 160                  else
 161                      @__instance__ = nil
 162                  end
 163              end
 164          end
 165          @__instance__ 
 166      end
 167  end
 168  
 169  
 170  
 171  
 172  if __FILE__ == $0
 173  
 174  def num_of_instances(klass)
 175      "#{ObjectSpace.each_object(klass){}} #{klass} instance(s)"
 176  end 
 177  
 178  # The basic and most important example.  The latter examples demonstrate
 179  # advanced features that have no relevance for the general usage
 180  
 181  class SomeSingletonClass
 182      include Singleton
 183  end
 184  puts "There are #{num_of_instances(SomeSingletonClass)}" 
 185  
 186  a = SomeSingletonClass.instance
 187  b = SomeSingletonClass.instance # a and b are same object
 188  puts "basic test is #{a == b}"
 189  
 190  begin
 191      SomeSingletonClass.new
 192  rescue  NoMethodError => mes
 193      puts mes
 194  end
 195  
 196  
 197  
 198  puts "\nThreaded example with exception and customized #_instanciate?() hook"; p
 199  Thread.abort_on_exception = false
 200  
 201  class Ups < SomeSingletonClass
 202      def initialize
 203          type.__sleep
 204          puts "initialize called by thread ##{Thread.current[:i]}"
 205      end
 206      class << self
 207          def _instanciate?
 208              @enter.push Thread.current[:i]
 209              while false.equal?(@__instance__)
 210                  Thread.critical = false
 211                  sleep 0.04 
 212                  Thread.critical = true
 213              end
 214              @leave.push Thread.current[:i]
 215              @__instance__
 216          end
 217          def __sleep
 218              sleep(rand(0.08))
 219          end 
 220          def allocate
 221              __sleep
 222              def self.allocate; __sleep; super() end
 223              raise  "boom - allocation in thread ##{Thread.current[:i]} aborted"
 224          end
 225          def instanciate_all
 226              @enter = []
 227              @leave = []
 228              1.upto(9) do |i|  
 229                  Thread.new do 
 230                      begin
 231                          Thread.current[:i] = i
 232                          __sleep
 233                          instance
 234                      rescue RuntimeError => mes
 235                          puts mes
 236                      end
 237                  end
 238              end
 239              puts "Before there were #{num_of_instances(self)}"
 240              sleep 5
 241              puts "Now there is #{num_of_instances(self)}"
 242              puts "#{@enter.join '; '} was the order of threads entering the waiting loop"
 243              puts "#{@leave.join '; '} was the order of threads leaving the waiting loop"
 244          end
 245      end
 246  end
 247  
 248  Ups.instanciate_all
 249  #  results in message like
 250  #  Before there were 0 Ups instances
 251  #  boom - allocation in thread #8 aborted
 252  #  initialize called by thread #3
 253  #  Now there is 1 Ups instance
 254  #  2; 3; 6; 1; 7; 5; 9; 4 was the order of threads entering the waiting loop
 255  #  3; 2; 1; 7; 6; 5; 4; 9 was the order of threads leaving the waiting loop
 256  
 257  puts "\nLets see if class level cloning really works"
 258  Yup = Ups.clone
 259  def Yup.allocate
 260      __sleep
 261      def self.allocate; __sleep; super() end
 262      raise  "boom - allocation in thread ##{Thread.current[:i]} aborted"
 263  end
 264  Yup.instanciate_all
 265  
 266  
 267  puts "\n","Customized marshalling"
 268  class A
 269      include Singleton
 270      attr_accessor :persist, :die
 271      def _dump(depth)
 272          # this strips the @die information from the instance
 273          Marshal.dump(@persist,depth)
 274      end
 275  end
 276  def A._load(str)
 277      instance.persist = Marshal.load(str)
 278      instance
 279  end
 280  
 281  a = A.instance
 282  a.persist = ["persist"]
 283  a.die = "die"
 284  a.taint
 285  
 286  stored_state = Marshal.dump(a)
 287  # change state
 288  a.persist = nil
 289  a.die = nil
 290  b = Marshal.load(stored_state)
 291  p a == b  #  => true
 292  p a.persist  #  => ["persist"]
 293  p a.die      #  => nil
 294  
 295  puts "\n\nSingleton with overridden default #inherited() hook"
 296  class Up
 297      def Up.inherited(sub_klass)
 298          puts "#{sub_klass} subclasses #{self}"
 299      end
 300  end
 301  
 302  
 303  class Middle < Up
 304      undef_method :dup
 305      include Singleton
 306  end
 307  class Down < Middle; end
 308  
 309  puts  "basic test is #{Down.instance == Down.instance}"  
 310  
 311  
 312  puts "\n","Various exceptions"
 313  
 314  begin
 315      module AModule
 316          include Singleton
 317      end
 318  rescue TypeError => mes
 319      puts mes  #=> Inclusion of the OO-Singleton module in module AModule
 320  end
 321  
 322  begin
 323      class << 'aString'
 324          include Singleton
 325      end
 326  rescue TypeError => mes
 327      puts mes  # => Inclusion of the OO-Singleton module in singleton type
 328  end
 329  
 330  begin
 331      'aString'.extend Singleton
 332  rescue NoMethodError => mes
 333      puts mes  #=> undefined method `extend_object' for Singleton:Module
 334  end
 335  
 336  end