Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 314 lines (267 sloc) 7.271 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 #
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
12 # a,b = Klass.instance, Klass.instance
13 # a == b # => true
14 # Klass.new # NoMethodError - new is private ...
511dc44 initial import
Laurent Sansonetti authored
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
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
33 # * Klass.inherited(sub_klass) and Klass.clone() -
511dc44 initial import
Laurent Sansonetti authored
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 # default marshalling strategy
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
75 def _dump(depth = -1)
511dc44 initial import
Laurent Sansonetti authored
76 ''
77 end
78
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
79 module SingletonClassMethods
511dc44 initial import
Laurent Sansonetti authored
80 # properly clone the Singleton pattern - did you know
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
81 # that duping doesn't copy class methods?
511dc44 initial import
Laurent Sansonetti authored
82 def clone
83 Singleton.__init__(super)
84 end
85
26d0e1f merge with ruby trunk r16762 + better/faster objc-like dispatcher
Laurent Sansonetti authored
86 def _load(str)
87 instance
88 end
89
511dc44 initial import
Laurent Sansonetti authored
90 private
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
91
92 # ensure that the Singleton pattern is properly inherited
511dc44 initial import
Laurent Sansonetti authored
93 def inherited(sub_klass)
94 super
95 Singleton.__init__(sub_klass)
96 end
97 end
98
99 class << Singleton
100 def __init__(klass)
101 klass.instance_eval {
102 @singleton__instance__ = nil
103 @singleton__mutex__ = Mutex.new
104 }
105 def klass.instance
106 return @singleton__instance__ if @singleton__instance__
107 @singleton__mutex__.synchronize {
108 return @singleton__instance__ if @singleton__instance__
109 @singleton__instance__ = new()
110 }
111 @singleton__instance__
112 end
113 klass
114 end
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
115
511dc44 initial import
Laurent Sansonetti authored
116 private
117
118 # extending an object with Singleton is a bad idea
119 undef_method :extend_object
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
120
511dc44 initial import
Laurent Sansonetti authored
121 def append_features(mod)
122 # help out people counting on transitive mixins
123 unless mod.instance_of?(Class)
124 raise TypeError, "Inclusion of the OO-Singleton module in module #{mod}"
125 end
126 super
127 end
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
128
511dc44 initial import
Laurent Sansonetti authored
129 def included(klass)
130 super
131 klass.private_class_method :new, :allocate
132 klass.extend SingletonClassMethods
133 Singleton.__init__(klass)
134 end
135 end
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
136
511dc44 initial import
Laurent Sansonetti authored
137 end
138
139
140 if __FILE__ == $0
141
142 def num_of_instances(klass)
143 "#{ObjectSpace.each_object(klass){}} #{klass} instance(s)"
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
144 end
511dc44 initial import
Laurent Sansonetti authored
145
146 # The basic and most important example.
147
148 class SomeSingletonClass
149 include Singleton
150 end
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
151 puts "There are #{num_of_instances(SomeSingletonClass)}"
511dc44 initial import
Laurent Sansonetti authored
152
153 a = SomeSingletonClass.instance
154 b = SomeSingletonClass.instance # a and b are same object
155 puts "basic test is #{a == b}"
156
157 begin
158 SomeSingletonClass.new
159 rescue NoMethodError => mes
160 puts mes
161 end
162
163
164
165 puts "\nThreaded example with exception and customized #_instantiate?() hook"; p
166 Thread.abort_on_exception = false
167
168 class Ups < SomeSingletonClass
169 def initialize
170 self.class.__sleep
171 puts "initialize called by thread ##{Thread.current[:i]}"
172 end
173 end
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
174
511dc44 initial import
Laurent Sansonetti authored
175 class << Ups
176 def _instantiate?
177 @enter.push Thread.current[:i]
178 while false.equal?(@singleton__instance__)
179 @singleton__mutex__.unlock
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
180 sleep 0.08
511dc44 initial import
Laurent Sansonetti authored
181 @singleton__mutex__.lock
182 end
183 @leave.push Thread.current[:i]
184 @singleton__instance__
185 end
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
186
511dc44 initial import
Laurent Sansonetti authored
187 def __sleep
188 sleep(rand(0.08))
189 end
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
190
511dc44 initial import
Laurent Sansonetti authored
191 def new
192 begin
193 __sleep
194 raise "boom - thread ##{Thread.current[:i]} failed to create instance"
195 ensure
196 # simple flip-flop
197 class << self
198 remove_method :new
199 end
200 end
201 end
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
202
511dc44 initial import
Laurent Sansonetti authored
203 def instantiate_all
204 @enter = []
205 @leave = []
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
206 1.upto(9) {|i|
207 Thread.new {
511dc44 initial import
Laurent Sansonetti authored
208 begin
209 Thread.current[:i] = i
210 __sleep
211 instance
212 rescue RuntimeError => mes
213 puts mes
214 end
215 }
216 }
217 puts "Before there were #{num_of_instances(self)}"
218 sleep 3
219 puts "Now there is #{num_of_instances(self)}"
220 puts "#{@enter.join '; '} was the order of threads entering the waiting loop"
221 puts "#{@leave.join '; '} was the order of threads leaving the waiting loop"
222 end
223 end
224
225
226 Ups.instantiate_all
227 # results in message like
228 # Before there were 0 Ups instance(s)
229 # boom - thread #6 failed to create instance
230 # initialize called by thread #3
231 # Now there is 1 Ups instance(s)
232 # 3; 2; 1; 8; 4; 7; 5 was the order of threads entering the waiting loop
233 # 3; 2; 1; 7; 4; 8; 5 was the order of threads leaving the waiting loop
234
235
236 puts "\nLets see if class level cloning really works"
237 Yup = Ups.clone
238 def Yup.new
239 begin
240 __sleep
241 raise "boom - thread ##{Thread.current[:i]} failed to create instance"
242 ensure
243 # simple flip-flop
244 class << self
245 remove_method :new
246 end
247 end
248 end
249 Yup.instantiate_all
250
251
252 puts "\n\n","Customized marshalling"
253 class A
254 include Singleton
255 attr_accessor :persist, :die
256 def _dump(depth)
257 # this strips the @die information from the instance
258 Marshal.dump(@persist,depth)
259 end
260 end
261
262 def A._load(str)
263 instance.persist = Marshal.load(str)
264 instance
265 end
266
267 a = A.instance
268 a.persist = ["persist"]
269 a.die = "die"
270 a.taint
271
272 stored_state = Marshal.dump(a)
273 # change state
274 a.persist = nil
275 a.die = nil
276 b = Marshal.load(stored_state)
277 p a == b # => true
278 p a.persist # => ["persist"]
279 p a.die # => nil
280
281
282 puts "\n\nSingleton with overridden default #inherited() hook"
283 class Up
284 end
285 def Up.inherited(sub_klass)
286 puts "#{sub_klass} subclasses #{self}"
287 end
288
289
290 class Middle < Up
291 include Singleton
292 end
293
294 class Down < Middle; end
295
296 puts "and basic \"Down test\" is #{Down.instance == Down.instance}\n
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
297 Various exceptions"
511dc44 initial import
Laurent Sansonetti authored
298
299 begin
300 module AModule
301 include Singleton
302 end
303 rescue TypeError => mes
304 puts mes #=> Inclusion of the OO-Singleton module in module AModule
305 end
306
307 begin
308 'aString'.extend Singleton
309 rescue NoMethodError => mes
310 puts mes #=> undefined method `extend_object' for Singleton:Module
311 end
312
313 end
Something went wrong with that request. Please try again.