Nested scopes and the Singleton pattern

Discussion in 'Ruby' started by Hal Fulton, Jan 14, 2004.

  1. Hal Fulton

    Hal Fulton Guest

    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
     
    Hal Fulton, Jan 14, 2004
    #1
    1. Advertising

  2. Hal Fulton

    Guest

    Hi,

    At Thu, 15 Jan 2004 06:40:07 +0900,
    Hal Fulton wrote:
    > 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)

    --
    Nobu Nakada
     
    , Jan 15, 2004
    #2
    1. Advertising

  3. Hal Fulton

    Hal Fulton Guest

    wrote:

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

    >
    > 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
     
    Hal Fulton, Jan 15, 2004
    #3
  4. Hal Fulton

    Guest

    Hi,

    At Thu, 15 Jan 2004 13:24:19 +0900,
    Hal Fulton wrote:
    > >>First question: Is this a valid way to implement Singleton, or am I
    > >>overlooking something?

    > >
    > > Have you considered about race conditions?

    >
    > 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".

    --
    Nobu Nakada
     
    , Jan 15, 2004
    #4
  5. Hal Fulton

    Hal Fulton Guest

    wrote:
    > Hi,
    >
    > At Thu, 15 Jan 2004 13:24:19 +0900,
    > Hal Fulton wrote:
    >
    >>>>First question: Is this a valid way to implement Singleton, or am I
    >>>>overlooking something?

    >>
    >> >
    >> > Have you considered about race conditions?

    >>
    >>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".


    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
     
    Hal Fulton, Jan 15, 2004
    #5
  6. Hal Fulton

    Guest

    Hi,

    At Thu, 15 Jan 2004 13:55:44 +0900,
    Hal Fulton wrote:
    > >>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".


    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.

    --
    Nobu Nakada
     
    , Jan 15, 2004
    #6
  7. Hal Fulton

    Paul Brannan Guest

    On Thu, Jan 15, 2004 at 06:39:13PM +0900, wrote:
    > At Thu, 15 Jan 2004 13:55:44 +0900,
    > Hal Fulton wrote:
    > > > Just using Thread.critical. And seems close to your idea
    > > > excepting for it uses "instance" method rather than "new".

    >
    > 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
     
    Paul Brannan, Jan 15, 2004
    #7
  8. Hal Fulton

    Guest

    Hi,

    At Fri, 16 Jan 2004 07:46:39 +0900,
    Christoph wrote:
    > > > > Just using Thread.critical. And seems close to your idea
    > > > > excepting for it uses "instance" method rather than "new".

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

    >
    > 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




    --
    Nobu Nakada
     
    , Jan 16, 2004
    #8
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Miki Tebeka

    Question about nested scopes

    Miki Tebeka, Oct 8, 2003, in forum: Python
    Replies:
    3
    Views:
    306
    Miki Tebeka
    Oct 9, 2003
  2. Fernando Rodriguez
    Replies:
    2
    Views:
    293
    Alexander Schmolck
    Nov 21, 2003
  3. Dave Benjamin

    Nested scopes and class variables

    Dave Benjamin, Jan 31, 2005, in forum: Python
    Replies:
    7
    Views:
    406
    Steven Bethard
    Feb 3, 2005
  4. Tim N. van der Leeuw

    Nested scopes, and augmented assignment

    Tim N. van der Leeuw, Jul 4, 2006, in forum: Python
    Replies:
    39
    Views:
    810
    Piet van Oostrum
    Jul 10, 2006
  5. Wilhelm
    Replies:
    1
    Views:
    185
Loading...

Share This Page