Fix up Rubinius specific library specs.
[rbx.git] / lib / singleton.rb
blobacacfc48c77c4ed00c2e24df83148dfb23e41327
1 # The Singleton module implements the Singleton pattern.
3 # Usage:
4 #    class Klass
5 #       include Singleton
6 #       # ...
7 #    end
9 # *  this ensures that only one instance of Klass lets call it
10 #    ``the instance'' can be created.
12 #    a,b  = Klass.instance, Klass.instance
13 #    a == b   # => true
14 #    a.new    #  NoMethodError - new is private ...
16 # *  ``The instance'' is created at instantiation time, in other
17 #    words the first call of Klass.instance(), thus
19 #      class OtherKlass
20 #        include Singleton
21 #        # ...
22 #      end
23 #      ObjectSpace.each_object(OtherKlass){} # => 0.
25 # *  This behavior is preserved under inheritance and cloning.
29 # This is achieved by marking
30 # *  Klass.new and Klass.allocate - as private
32 # Providing (or modifying) the class methods
33 # *  Klass.inherited(sub_klass) and Klass.clone()  - 
34 #    to ensure that the Singleton pattern is properly
35 #    inherited and cloned.
37 # *  Klass.instance()  -  returning ``the instance''. After a
38 #    successful self modifying (normally the first) call the
39 #    method body is a simple:
41 #       def Klass.instance()
42 #         return @singleton__instance__
43 #       end
45 # *  Klass._load(str)  -  calling Klass.instance()
47 # *  Klass._instantiate?()  -  returning ``the instance'' or
48 #    nil. This hook method puts a second (or nth) thread calling
49 #    Klass.instance() on a waiting loop. The return value
50 #    signifies the successful completion or premature termination
51 #    of the first, or more generally, current "instantiation thread".
54 # The instance method of Singleton are
55 # * clone and dup - raising TypeErrors to prevent cloning or duping
57 # *  _dump(depth) - returning the empty string.  Marshalling strips
58 #    by default all state information, e.g. instance variables and
59 #    taint state, from ``the instance''.  Providing custom _load(str)
60 #    and _dump(depth) hooks allows the (partially) resurrections of
61 #    a previous state of ``the instance''.
63 require 'thread'
65 module Singleton
66   #  disable build-in copying methods
67   def clone
68     raise TypeError, "can't clone instance of singleton #{self.class}"
69   end
70   def dup
71     raise TypeError, "can't dup instance of singleton #{self.class}"
72   end
73   
74   private 
76   #  default marshalling strategy
77   def _dump(depth = -1) 
78     ''
79   end
81   module SingletonClassMethods  
82     # properly clone the Singleton pattern - did you know
83     # that duping doesn't copy class methods?  
84     def clone
85       Singleton.__init__(super)
86     end
88     private
89     
90     #  ensure that the Singleton pattern is properly inherited   
91     def inherited(sub_klass)
92       super
93       Singleton.__init__(sub_klass)
94     end
95     
96     def _load(str) 
97       instance 
98     end
99   end
101   class << Singleton
102     def __init__(klass)
103       klass.instance_eval {
104         @singleton__instance__ = nil
105         @singleton__mutex__ = Mutex.new
106       }
107       def klass.instance
108         return @singleton__instance__ if @singleton__instance__
109         @singleton__mutex__.synchronize {
110           return @singleton__instance__ if @singleton__instance__
111           @singleton__instance__ = new()
112         }
113         @singleton__instance__
114       end
115       klass
116     end
117     
118     private
120     #  extending an object with Singleton is a bad idea
121     undef_method :extend_object
122     
123     def append_features(mod)
124       #  help out people counting on transitive mixins
125       unless mod.instance_of?(Class)
126         raise TypeError, "Inclusion of the OO-Singleton module in module #{mod}"
127       end
128       super
129     end
130     
131     def included(klass)
132       super
133       klass.private_class_method  :new, :allocate
134       klass.extend SingletonClassMethods
135       Singleton.__init__(klass)
136     end
137   end
138   
142 if __FILE__ == $0
144 def num_of_instances(klass)
145     "#{ObjectSpace.each_object(klass){}} #{klass} instance(s)"
146 end 
148 # The basic and most important example.
150 class SomeSingletonClass
151   include Singleton
153 puts "There are #{num_of_instances(SomeSingletonClass)}" 
155 a = SomeSingletonClass.instance
156 b = SomeSingletonClass.instance # a and b are same object
157 puts "basic test is #{a == b}"
159 begin
160   SomeSingletonClass.new
161 rescue  NoMethodError => mes
162   puts mes
167 puts "\nThreaded example with exception and customized #_instantiate?() hook"; p
168 Thread.abort_on_exception = false
170 class Ups < SomeSingletonClass
171   def initialize
172     self.class.__sleep
173     puts "initialize called by thread ##{Thread.current[:i]}"
174   end
176   
177 class << Ups
178   def _instantiate?
179     @enter.push Thread.current[:i]
180     while false.equal?(@singleton__instance__)
181       @singleton__mutex__.unlock
182       sleep 0.08 
183       @singleton__mutex__.lock
184     end
185     @leave.push Thread.current[:i]
186     @singleton__instance__
187   end
188   
189   def __sleep
190     sleep(rand(0.08))
191   end
192   
193   def new
194     begin
195       __sleep
196       raise  "boom - thread ##{Thread.current[:i]} failed to create instance"
197     ensure
198       # simple flip-flop
199       class << self
200         remove_method :new
201       end
202     end
203   end
204   
205   def instantiate_all
206     @enter = []
207     @leave = []
208     1.upto(9) {|i|  
209       Thread.new { 
210         begin
211           Thread.current[:i] = i
212           __sleep
213           instance
214         rescue RuntimeError => mes
215           puts mes
216         end
217       }
218     }
219     puts "Before there were #{num_of_instances(self)}"
220     sleep 3
221     puts "Now there is #{num_of_instances(self)}"
222     puts "#{@enter.join '; '} was the order of threads entering the waiting loop"
223     puts "#{@leave.join '; '} was the order of threads leaving the waiting loop"
224   end
228 Ups.instantiate_all
229 # results in message like
230 # Before there were 0 Ups instance(s)
231 # boom - thread #6 failed to create instance
232 # initialize called by thread #3
233 # Now there is 1 Ups instance(s)
234 # 3; 2; 1; 8; 4; 7; 5 was the order of threads entering the waiting loop
235 # 3; 2; 1; 7; 4; 8; 5 was the order of threads leaving the waiting loop
238 puts "\nLets see if class level cloning really works"
239 Yup = Ups.clone
240 def Yup.new
241   begin
242     __sleep
243     raise  "boom - thread ##{Thread.current[:i]} failed to create instance"
244   ensure
245     # simple flip-flop
246     class << self
247       remove_method :new
248     end
249   end
251 Yup.instantiate_all
254 puts "\n\n","Customized marshalling"
255 class A
256   include Singleton
257   attr_accessor :persist, :die
258   def _dump(depth)
259     # this strips the @die information from the instance
260     Marshal.dump(@persist,depth)
261   end
264 def A._load(str)
265   instance.persist = Marshal.load(str)
266   instance
269 a = A.instance
270 a.persist = ["persist"]
271 a.die = "die"
272 a.taint
274 stored_state = Marshal.dump(a)
275 # change state
276 a.persist = nil
277 a.die = nil
278 b = Marshal.load(stored_state)
279 p a == b  #  => true
280 p a.persist  #  => ["persist"]
281 p a.die      #  => nil
284 puts "\n\nSingleton with overridden default #inherited() hook"
285 class Up
287 def Up.inherited(sub_klass)
288   puts "#{sub_klass} subclasses #{self}"
292 class Middle < Up
293   include Singleton
296 class Down < Middle; end
298 puts  "and basic \"Down test\" is #{Down.instance == Down.instance}\n
299 Various exceptions"  
301 begin
302   module AModule
303     include Singleton
304   end
305 rescue TypeError => mes
306   puts mes  #=> Inclusion of the OO-Singleton module in module AModule
309 begin
310   'aString'.extend Singleton
311 rescue NoMethodError => mes
312   puts mes  #=> undefined method `extend_object' for Singleton:Module