From: chr_news@... (chr_news@...) Date: 2001-11-21T08:32:46+09:00 Subject: [ruby-talk:25963] OO Singletons and Marshalling (bug?) Hi, the current (i.e. cvs) implementation of the OO Singleton pattern has no strategy with respect to marshalling - the following script illustrates the problem. --------------- require 'singleton' class A include Singleton end resurrect = Marshal.load(Marshal.dump(A.instance)) p resurrect == A.instance # => false --------------- The attached script fixes this problem - it is a variation of the current cvs strategy and implements the following idea * Ensure that only one instance of the class can be (regularly) created * The ``instance'' is created at ``instanciation time'' - i.e. the first call of #instance * This behavior is preserved under inheritance The script includes a slightly more serious test of a threaded environment. A typical outcome on my machine is --------------- $ ruby Singleton.rb true "initialize() called by thread #2" true "The number of Ups objects is 1" Singleton.rb:85: private method `new' called for Ups:Class (NoMethodError) Christoph --------------- module Singleton private def Singleton.included(klass) class << klass def inherited(sub_klass) @__instance__ = nil def sub_klass.instance if defined?(@__allocating__) _wait() until @__instance__ return @__instance__ end Thread.critical = true unless @__instance__ begin @__allocating__ = true Thread.critical = false @__instance__ = new define_method(:instance) {@__instance__ } ensure remove_instance_variable(:@__allocating__) end else begin Thread.critical = false ensure remove_instance_variable(:@__allocating__) end end return @__instance__ end end def _load(str) instance end def _wait() sleep(0.05) end private :_load,:_wait, :new, :allocate end klass.inherited(klass) end def _dump(depth) return "" end end # simple test class SomeSingletonClass include Singleton end a = SomeSingletonClass.instance b = SomeSingletonClass.instance # a and b are same object p a == b # true # threaded test class Ups < SomeSingletonClass @__threads__= [] @@__index__ = nil def initialize sleep(rand(0.1)/10.0) Thread.current[:self] = self Thread.current[:index] = @@__index__ end end class << Ups def initialize_all 1.upto(4) do |@@__index__| sleep(rand(0.2)/10.0) @__threads__.push Thread.new { Thread.current[:instance] = instance } end end def join @__threads__.each do |t| t.join; p "initialize() called by thread ##{t[:index]}" if t[:self] end end end Ups.initialize_all Ups.join # only one should be found p Marshal.load(Marshal.dump(Ups.instance)) == Ups.instance # => true p "The number of Ups objects is #{ObjectSpace.each_object(Ups) { |obj| }}" Ups.new ---------------