From: "Chr. Rippel" Date: 2002-01-26T20:32:55+09:00 Subject: TypeError verus NoMethodError I wanted to point out an inconistency (IMO as usual) that build in singleton/immutable classes NilClass, TrueClass FalseClass, Symbols and Numeric instances follow the old style Exception scheme of raising a TypeError when attempting to clone or dup - instead of following the new style NoMethodError exception. I also take this as an opportunity to send in a fix for my revisited version of singleton.rb - (I just realized that I removed the wrong dup ...). Hm, maybe we should stick with the old version anyway ... /Christoph PS. A patch is pointless it's just about as long ... ---- # The Singleton module implements the Singleton pattern - i.e. # # class Klass # include Singleton # # ... # end # # * ensures that only one instance of Klass called ``the instance'' # can be created. # # a,b = Klass.instance, Klass.instance # a == b # => true # a.new # NoMethodError - new is private ... # # * ``The instance'' is created at instanciation time, # in other words the first call of Klass.instance(), thus # # class OtherKlass # include Singleton # # ... # end # p "#{ObjectSpace.each_object(OtherKlass) {}}" # => 0 # # * This behavior is preserved under inheritance. # # # This is achieved by marking # * Klass.new and Klass.allocate - as private # * removing #clone and #dup and modifying # * Klass.inherited(sub_klass)- to ensure that the # Singleton pattern is properly inherited. # # In addition Klass is providing the additional class methods # * Klass.instance() - returning ``the instance'' # * Klass._load(str) - returning ``the instance'' # * Klass._wait() - a hook method putting a second (or n-th) # thread calling Klass.instance on a waiting loop if the first call # to Klass.instance is still in progress. # # The sole instance method of Singleton is # * _dump(depth) - returning the empty string. # The default Marshalling strategy is to strip all state information, # i.e. instance variables and taint state etc. from ``the instance''. # Providing custom _dump(depth) and _load(str) method allows # the (partial) resurrection of a previous state of ``the instance''. # Also see the last example. module Singleton private def _dump(depth=-1) '' end class << self undef_method :extend_object private def append_features(mod) unless mod.instance_of? (Class) raise TypeError.new "Inclusion of the OO-Singleton module in module #{mod}" end unless (class << mod; self end) <= (class << Object; self end) raise TypeError.new "Inclusion of the OO-Singleton module in singleton type" end super end def included (klass) klass.class_eval do undef_method :clone undef_method :dup end class << klass private :new, :allocate define_method (:instance, Singleton::FirstInstanceCall) private def _wait sleep(0.1) while false.equal?(@__instance__) end def _load(str) instance end def inherited(sub_klass) super class << sub_klass define_method (:instance, Singleton::FirstInstanceCall) end sub_klass.instance_eval do @__instance__ = nil end end end klass.instance_eval do @__instance__ = nil end end end FirstInstanceCall = proc do # @__instance__ takes on one of the following values # * nil - before and after a failed creation # * false - during creation # * sub_class instance - after a successful creation _wait() unless @__instance__.nil? Thread.critical = true unless @__instance__ @__instance__ = false Thread.critical = false begin @__instance__ = new ensure if @__instance__ def self.instance() @__instance__ end else # failed instance creation @__instance__ = nil end end else Thread.critical = false end @__instance__ end end if __FILE__ == $0 #basic example class SomeSingletonClass include Singleton end a = SomeSingletonClass.instance b = SomeSingletonClass.instance # a and b are same object p a == b # => true begin SomeSingletonClass.new rescue NoMethodError => mes puts mes end # threaded example with exception and customized hook #_wait method Thread.abort_on_exception = false def num_of_instances(mod) "#{ObjectSpace.each_object(mod){}} #{mod} instance" end class Ups < SomeSingletonClass def initialize type.__sleep puts "initialize called by thread ##{Thread.current[:i]}" end class << self def _wait @enter.push Thread.current[:i] sleep 0.02 while false.equal?(@__instance__) @leave.push Thread.current[:i] end def __sleep sleep (rand(0.1)) end def allocate __sleep def self.allocate; __sleep; super() end raise "allocation in thread ##{Thread.current[:i]} aborted" end def instanciate_all @enter = [] @leave = [] 1.upto(9) do |i| Thread.new do begin Thread.current[:i] = i __sleep instance rescue RuntimeError => mes puts mes end end end puts "Before there were #{num_of_instances(Ups)}s" sleep 6 puts "Now there is #{num_of_instances(Ups)}" puts "#{@enter.join "; "} was the order of threads entering the waiting loop" puts "#{@leave.join "; "} was the order of threads leaving the waiting loop" end end end Ups.instanciate_all # results in message like # Before there were 0 Ups instances # boom - allocation in thread #8 aborted # initialize called by thread #3 # Now there is 1 Ups instance # 2; 3; 6; 1; 7; 5; 9; 4 was the order of threads entering the waiting loop # 3; 2; 1; 7; 6; 5; 4; 9 was the order of threads leaving the waiting loop # Customized marshalling class A include Singleton attr_accessor :persist, :die def _dump(depth) # this strips the @die information from the instance Marshal.dump(@persist,depth) end end def A._load(str) instance.persist = Marshal.load(str) instance end a = A.instance a.persist = ["persist"] a.die = "die" stored_state = Marshal.dump(a) # change state a.persist = nil a.die = nil b = Marshal.load(stored_state) p a == b # => true p a.persist # => ["persist"] p a.die # => nil end -----