Nested scopes and the Singleton pattern

H

Hal Fulton

Hello all,

This is really two questions.

I've been putting together a nested class structure in pieces
since it is a little large and unwieldy.

Here's a snippet of something I've run across.

By the way, my last change was to implement a simple version of the
Singleton pattern (without using singleton.rb).


# Note: *Reopening* existing classes

class ABC
class XYZ
@@instance = nil
def initialize(foo)
# Do some stuff...
# and then:
def XYZ.new(foo)
@@instance
end
end
end
end


This works fine.

First question: Is this a valid way to implement Singleton, or am I
overlooking something?


Then I thought: Well, let's get rid of the unnecessary verbiage.
(Remember, I'm REOPENING these classes.)

So I did:

# Note: *Reopening* existing classes

class ABC::XYZ
@@instance = nil
def initialize(foo)
# Do some stuff...
# and then:
def ABC::XYZ.new(foo)
@@instance
end
end
end

But it didn't work.

This does work:

# Note: *Reopening* existing classes

class ABC::XYZ
@@instance = nil
def initialize(foo)
# Do some stuff...
# and then:
klass = ABC::XYZ
def klass.new(foo)
@@instance
end
end
end


Second question: Why should these two be different?


Thanks,
Hal Fulton
 
N

nobu.nokada

Hi,

At Thu, 15 Jan 2004 06:40:07 +0900,
Hal said:
By the way, my last change was to implement a simple version of the
Singleton pattern (without using singleton.rb).


# Note: *Reopening* existing classes

class ABC
class XYZ
@@instance = nil
def initialize(foo)
# Do some stuff...
# and then:
def XYZ.new(foo)
@@instance
end
end
end
end


This works fine.

First question: Is this a valid way to implement Singleton, or am I
overlooking something?

Have you considered about race conditions?
So I did:

# Note: *Reopening* existing classes

class ABC::XYZ
@@instance = nil
def initialize(foo)
# Do some stuff...
# and then:
def ABC::XYZ.new(foo)
@@instance
end
end
end

But it didn't work.

def ABC::XYZ means singleton method XYZ of ABC by itself, so
trailing . is superfluous. Try

def (ABC::XYZ).new(foo)
 
H

Hal Fulton

Have you considered about race conditions?

Thank you, Nobu. No, I did not consider race conditions.

I suppose singleton.rb is thread safe?
def ABC::XYZ means singleton method XYZ of ABC by itself, so
trailing . is superfluous. Try

def (ABC::XYZ).new(foo)

Ahh, that is clear. But I could not see it before.


Thanks very much,
Hal
 
N

nobu.nokada

Hi,

At Thu, 15 Jan 2004 13:24:19 +0900,
Hal said:
Thank you, Nobu. No, I did not consider race conditions.

I suppose singleton.rb is thread safe?

Just using Thread.critical. And seems close to your idea
excepting for it uses "instance" method rather than "new".
 
H

Hal Fulton

Hi,

At Thu, 15 Jan 2004 13:24:19 +0900,



Just using Thread.critical. And seems close to your idea
excepting for it uses "instance" method rather than "new".

Actually I like "new" instead of "instance". I guess this
(instance) is to emphasize that there can only be one. But
I prefer simply calling new as usual, which was why I did this.

Hal
 
N

nobu.nokada

Hi,

At Thu, 15 Jan 2004 13:55:44 +0900,

Not accurate, it uses 3-state; before, during and after
creation, but I feel it should use mutex or something.
Actually I like "new" instead of "instance". I guess this
(instance) is to emphasize that there can only be one. But
I prefer simply calling new as usual, which was why I did this.

singleton.rb is for generic use, so it gets rid of overriding
instance methods, I guess.
 
P

Paul Brannan

At Thu, 15 Jan 2004 13:55:44 +0900,


Not accurate, it uses 3-state; before, during and after
creation, but I feel it should use mutex or something.

I think I agree. It took me an hour to understand the Singleton code
last time I read it, and when I look at it again now, I seem to have
forgotten how it works.

It seems like using a Mutex plus Thread.exclusive around the code that
redefines instance() should be sufficient, but perhaps I'm missing
something.

Paul
 
N

nobu.nokada

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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top