From: "Chr. Rippel" Date: 2001-11-25T05:58:38+09:00 Subject: [ruby-talk:26364] Re: OO Singletons and Marshalling (bug?) "Yukihiro Matsumoto" wrote in message > I like this one. Can I merge it to the standard distribution, under > the terms identical to the rest of Ruby? Sure, I don't mind at all if some (or all) could be merged according to Ruby's license;-)) There was bug in my previous code (and in cvs) - threads on the ``allocation waiting loop'' failed to check if the allocation was successful. There is a cosmetic change replacing @__allocating__ with a three way logic of @__instance__ taking on a nil, false or ``the instance'' value. I also made some adjustments to the examples and added something resembling a synopsis. Any comments are very well come ... /Christoph ---------------- $ ruby Singleton.rb true private method `new' called for SomeSingletonClass:Class There is(are) 0 Ups instance(s) boom - allocation in thread #1 aborted initialize called by thread #3 true There is(are) 1 Ups instance(s) true ["persist"] nil ---------------- # 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 - i.e. the first call # of Klass.instance(). # # class OtherKlass # include Singleton # # ... # end # p "#{ObjectSpace.each_object(OtherKlass) {}}" # => 0 # # * This behavior is preserved under inheritance. # # # This achieved by marking # * Klass.new and Klass.allocate - as private and # * Klass.inherited(sub_klass) - modifying to ensure # that the Singleton pattern is properly inherited. # # In addition Klass is provided with the 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 from ``the instance''. Providing costume # _dump(depth) and _load(str) method allows the (partial) resurrection # of a previous state of ``the instance'' - see third example. # module Singleton def Singleton.included (klass) # should this be checked? # raise TypeError.new "..." if klass.type == Module class << klass def inherited(sub_klass) # @__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 @__instance__ = nil def sub_klass.instance unless @__instance__.nil? # is the extra flexiblity having the hook method # _wait() around ever useful? _wait() while false.equal?(@__instance__) # check for instance creation return @__instance__ if @__instance__ end Thread.critical = true unless @__instance__ @__instance__ = false Thread.critical = false begin @__instance__ = new ensure if @__instance__ define_method(:instance) {@__instance__ } else # failed instance creation @__instance__ = nil end end else Thread.critical = false end return @__instance__ end end def _load(str) instance end def _wait sleep(0.05) end private :new, :allocate # hook methods are also marked private private :_load,:_wait end klass.inherited klass end private def _dump(depth) return "" 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 Thread.abort_on_exception = true class Ups < SomeSingletonClass @__threads__= [] @__flip__ = nil @@__index__ = nil def initialize sleep(rand(0.1)/10.0) Thread.current[:index] = @@__index__ end class << self def allocate unless @__flip__ @__flip__ = true raise "boom - allocation in thread ##{@@__index__} aborted" end super() end def instanciate_all 1.upto(5) do |@@__index__| sleep(rand(0.1)/10.0) @__threads__.push Thread.new { begin instance rescue RuntimeError => mes puts mes end } end end def join @__threads__.each do |t| t.join puts "initialize called by thread ##{t[:index]}" if t[:index] end end end end puts "There is(are) #{ObjectSpace.each_object(Ups) {}} Ups instance(s)" # => The is(are) 0 Ups instance(s) Ups.instanciate_all Ups.join # => initialize called by thread # i - where i = 2 ... 5 p Marshal.load(Marshal.dump(Ups.instance)) == Ups.instance # => true puts "There is(are) #{ObjectSpace.each_object(Ups) {}} Ups instance(s)" # => The is(are) 1 Ups instance(s) # 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 ----------------