Hi,
At Fri, 16 Jan 2004 07:46:39 +0900,
Christoph said:
You probably always need a 3-state if you want a self
modifying first instance call (at least in some implicit way).
Here is a mutexy version of singleton.rb I wrote some time
ago - it is probably more robust then the current code but equally
obscure - sorry ...
Due to the line ending codes, your patch contains whole files.
Following is a bit modified version.
Index: lib/singleton.rb
===================================================================
RCS file: /cvs/ruby/src/ruby/lib/singleton.rb,v
retrieving revision 1.21
diff -u -2 -p -d -r1.21 singleton.rb
--- lib/singleton.rb 22 Aug 2003 08:09:58 -0000 1.21
+++ lib/singleton.rb 16 Jan 2004 01:26:06 -0000
@@ -12,5 +12,5 @@
# a,b = Klass.instance, Klass.instance
# a == b # => true
-# a.new # NoMethodError - new is private ...
+# Klass.new # NoMethodError - new is private ...
#
# * ``The instance'' is created at instanciation time, in other
@@ -31,7 +31,5 @@
#
# Providing (or modifying) the class methods
-# * Klass.inherited(sub_klass) and Klass.clone() -
-# to ensure that the Singleton pattern is properly
-# inherited and cloned.
+# * Klass.inherited(sub_klass) and Klass.initialize_copy()
#
# * Klass.instance() - returning ``the instance''. After a
@@ -45,20 +43,12 @@
# * Klass._load(str) - calling Klass.instance()
#
-# * Klass._instanciate?() - returning ``the instance'' or
-# nil. This hook method puts a second (or nth) thread calling
-# Klass.instance() on a waiting loop. The return value
-# signifies the successful completion or premature termination
-# of the first, or more generally, current "instanciation thread".
-#
#
# The instance method of Singleton are
-# * clone and dup - raising TypeErrors to prevent cloning or duping
+# * clone and dup - raising TypeErrors to prevent cloning
#
-# * _dump(depth) - returning the empty string. Marshalling strips
-# by default all state information, e.g. instance variables and
-# taint state, from ``the instance''. Providing custom _load(str)
-# and _dump(depth) hooks allows the (partially) resurrections of
-# a previous state of ``the instance''.
-
+# * _dump(depth) - returning the empty string, in other words
+# marshalling strips all state information. Providing custom
+# _load(str) and _dump(depth) hooks allows the (partially)
+# resurrections of a previous state of ``the instance''.
@@ -82,60 +72,61 @@ end
class << Singleton
# Method body of first instance call.
- FirstInstanceCall = proc do
- # @__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
- # the form makes up for the lack of returns in progs
- Thread.critical = true
- if @__instance__.nil?
- @__instance__ = false
- Thread.critical = false
- begin
+ FirstInstanceCall = proc do ||
+ critical, Thread.critical = Thread.critical, true
+ begin
+ if instanciating = @__instance__.nil?
+ @__instance__ = false
+ Thread.critical = critical
@__instance__ = new
- ensure
- if @__instance__
- class <<self
- remove_method :instance
- def instance; @__instance__ end
- end
- else
- @__instance__ = nil # failed instance creation
- end
+ elsif !@__instance__
+ @__instanciating_queue__ << Thread.current
+ Thread.stop
+ Thread.critical = true
+ retry
end
- elsif _instanciate?()
- Thread.critical = false
- else
- @__instance__ = false
- Thread.critical = false
- begin
- @__instance__ = new
- ensure
+ @__instance__
+ ensure
+ if instanciating
+ Thread.critical= true
if @__instance__
- class <<self
+ class << self
remove_method :instance
def instance; @__instance__ end
end
+ remove_instance_variable(@__instanciating_queue__).each do |thr|
+ thr.wakeup
+ end
else
@__instance__ = nil
+ if thr = @__instanciating_queue__.shift
+ thr.wakeup
+ end
end
end
+ Thread.critical = critical
end
- @__instance__
end
- module SingletonClassMethods
- # properly clone the Singleton pattern - did you know
- # that duping doesn't copy class methods?
- def clone
- Singleton.__init__(super)
+ module SingletonClassMethods
+ def self.extended(klass)
+ klass.instance_eval {
+ @__instance__ = nil
+ @__instanciating_queue__ = []
+ }
+ class << klass
+ define_method
instance, FirstInstanceCall)
+ end
end
private
- # ensure that the Singleton pattern is properly inherited
+ def initialize_copy(orig)
+ super
+ SingletonClassMethods.extended(self)
+ end
+
def inherited(sub_klass)
super
- Singleton.__init__(sub_klass)
+ SingletonClassMethods.extended(sub_klass)
end
@@ -143,23 +134,6 @@ class << Singleton
instance
end
-
- # waiting-loop hook
- def _instanciate?()
- while false.equal?(@__instance__)
- Thread.critical = false
- sleep(0.08) # timeout
- Thread.critical = true
- end
- @__instance__
- end
end
- def __init__(klass)
- klass.instance_eval { @__instance__ = nil }
- class << klass
- define_method
instance,FirstInstanceCall)
- end
- klass
- end
private
@@ -177,7 +151,7 @@ class << Singleton
def included(klass)
super
- klass.private_class_method :new, :allocate
+ klass.private_class_method :new,:allocate
klass.extend SingletonClassMethods
- Singleton.__init__(klass)
+ SingletonClassMethods.extended(klass)
end
end
@@ -186,9 +160,4 @@ end
if __FILE__ == $0
-
-def num_of_instances(klass)
- "#{ObjectSpace.each_object(klass){}} #{klass} instance(s)"
-end
-
# The basic and most important example.
@@ -196,5 +165,7 @@ class SomeSingletonClass
include Singleton
end
-puts "There are #{num_of_instances(SomeSingletonClass)}"
+
+num = ObjectSpace.each_object(SomeSingletonClass) {}
+puts "There are #{num} of SomeSingletonClass instances"
a = SomeSingletonClass.instance
@@ -209,90 +180,81 @@ end
+puts "\nThreaded example with exception"
+$stdout.sync= true
-puts "\nThreaded example with exception and customized #_instanciate?() hook"; p
-Thread.abort_on_exception = false
+class Foo < SomeSingletonClass
+ @attempts = 0
-class Ups < SomeSingletonClass
def initialize
- self.class.__sleep
- puts "initialize called by thread ##{Thread.current[:i]}"
- end
-end
-
-class << Ups
- def _instanciate?
- @enter.push Thread.current[:i]
- while false.equal?(@__instance__)
- Thread.critical = false
- sleep 0.08
- Thread.critical = true
+ @valid = false
+ sleep(rand(0.1))
+ if self.class.attempts < 3
+ raise "boom - initialize failed for thread ##{Thread.current[:i]}"
+ else
+ @valid = true
+ puts "yes! - initialize succceeded for thread ##{Thread.current[:i]}"
end
- @leave.push Thread.current[:i]
- @__instance__
+ ensure
+ self.class.attempts+= 1
end
- def __sleep
- sleep(rand(0.08))
- end
+ def valid?
+ @valid
+ end
+end
- def new
- begin
- __sleep
- raise "boom - thread ##{Thread.current[:i]} failed to create instance"
- ensure
- # simple flip-flop
- class << self
- remove_method :new
- end
- end
- end
+class << Foo
+ attr_accessor :attempts
def instanciate_all
- @enter = []
- @leave = []
- 1.upto(9) {|i|
- Thread.new {
+ Thread.current.priority = 101
+ thrs= Array.new(101) {|i|
+ curr= Thread.new {
begin
+ sleep(rand(0.1))
Thread.current[:i] = i
- __sleep
instance
- rescue RuntimeError => mes
+ rescue => mes
puts mes
end
}
+ curr.priority = rand(101)
+ curr
}
- puts "Before there were #{num_of_instances(self)}"
+ puts "Before there existed #{num} valid #{self} instance(s)"
sleep 3
- puts "Now there is #{num_of_instances(self)}"
- puts "#{@enter.join '; '} was the order of threads entering the waiting loop"
- puts "#{@leave.join '; '} was the order of threads leaving the waiting loop"
+ thrs.each {|t| t.join }
+ puts "Now there exist(s) #{num} valid #{self} instance(s)"
end
-end
+
+ def num
+ cnt = 0
+ ObjectSpace.each_object(self) {|o| cnt+=1 if o.valid? }
+ cnt
+ end
+
+ private
+
+ def initialize_copy(orig)
+ super
+ @attempts = 0
+ end
+end
-Ups.instanciate_all
-# results in message like
-# Before there were 0 Ups instance(s)
-# boom - thread #6 failed to create instance
-# initialize called by thread #3
-# Now there is 1 Ups instance(s)
-# 3; 2; 1; 8; 4; 7; 5 was the order of threads entering the waiting loop
-# 3; 2; 1; 7; 4; 8; 5 was the order of threads leaving the waiting loop
+Foo.instanciate_all
+# results in something like:
+# Before there were 0 valid Foo instance(s)
+# boom - initialize failed for thread #78
+# boom - initialize failed for thread #93
+# boom - initialize failed for thread #8
+# yes - initialize succceeded for thread #27
+# Now there are 1 valid Foo instance(s)
+# Now there are 1 valid Foo instance(s)
puts "\nLets see if class level cloning really works"
-Yup = Ups.clone
-def Yup.new
- begin
- __sleep
- raise "boom - thread ##{Thread.current[:i]} failed to create instance"
- ensure
- # simple flip-flop
- class << self
- remove_method :new
- end
- end
-end
-Yup.instanciate_all
+Baz = Foo.clone
+Baz.instanciate_all