Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 316 lines (268 sloc) 7.326 kb
511dc44 initial import
Laurent Sansonetti authored
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 instantiation time, in other
17 # words 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 #
29 # This is achieved by marking
30 # * Klass.new and Klass.allocate - as private
31 #
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.
36 #
37 # * Klass.instance() - returning ``the instance''. After a
38 # successful self modifying (normally the first) call the
39 # method body is a simple:
40 #
41 # def Klass.instance()
42 # return @singleton__instance__
43 # end
44 #
45 # * Klass._load(str) - calling Klass.instance()
46 #
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".
52 #
53 #
54 # The instance method of Singleton are
55 # * clone and dup - raising TypeErrors to prevent cloning or duping
56 #
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''.
62
63 require 'thread'
64
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
75
76 # default marshalling strategy
77 def _dump(depth = -1)
78 ''
79 end
80
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
87
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
100
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
119
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
139 end
140
141
142 if __FILE__ == $0
143
144 def num_of_instances(klass)
145 "#{ObjectSpace.each_object(klass){}} #{klass} instance(s)"
146 end
147
148 # The basic and most important example.
149
150 class SomeSingletonClass
151 include Singleton
152 end
153 puts "There are #{num_of_instances(SomeSingletonClass)}"
154
155 a = SomeSingletonClass.instance
156 b = SomeSingletonClass.instance # a and b are same object
157 puts "basic test is #{a == b}"
158
159 begin
160 SomeSingletonClass.new
161 rescue NoMethodError => mes
162 puts mes
163 end
164
165
166
167 puts "\nThreaded example with exception and customized #_instantiate?() hook"; p
168 Thread.abort_on_exception = false
169
170 class Ups < SomeSingletonClass
171 def initialize
172 self.class.__sleep
173 puts "initialize called by thread ##{Thread.current[:i]}"
174 end
175 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
225 end
226
227
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
236
237
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
250 end
251 Yup.instantiate_all
252
253
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
262 end
263
264 def A._load(str)
265 instance.persist = Marshal.load(str)
266 instance
267 end
268
269 a = A.instance
270 a.persist = ["persist"]
271 a.die = "die"
272 a.taint
273
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
282
283
284 puts "\n\nSingleton with overridden default #inherited() hook"
285 class Up
286 end
287 def Up.inherited(sub_klass)
288 puts "#{sub_klass} subclasses #{self}"
289 end
290
291
292 class Middle < Up
293 include Singleton
294 end
295
296 class Down < Middle; end
297
298 puts "and basic \"Down test\" is #{Down.instance == Down.instance}\n
299 Various exceptions"
300
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
307 end
308
309 begin
310 'aString'.extend Singleton
311 rescue NoMethodError => mes
312 puts mes #=> undefined method `extend_object' for Singleton:Module
313 end
314
315 end
Something went wrong with that request. Please try again.