summaryrefslogtreecommitdiff
path: root/lib/singleton.rb
diff options
context:
space:
mode:
authormatz <matz@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2002-01-29 07:16:09 +0000
committermatz <matz@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2002-01-29 07:16:09 +0000
commit86298123d748030ec9dcbb64ba3bf86816de9de7 (patch)
tree5071f9d4670df7f8a7ed10355c9bef1a544aa32c /lib/singleton.rb
parente9d17f8a226bcfcf234def5d0ba77fb77a48a534 (diff)
* file.c (rb_stat_rdev_major): added. [new]
* file.c (rb_stat_rdev_minor): added. [new] * file.c (rb_stat_inspect): print mode in octal. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@2027 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/singleton.rb')
-rw-r--r--lib/singleton.rb327
1 files changed, 228 insertions, 99 deletions
diff --git a/lib/singleton.rb b/lib/singleton.rb
index 5a9b271..2b5a3a3 100644
--- a/lib/singleton.rb
+++ b/lib/singleton.rb
@@ -1,130 +1,203 @@
-# The Singleton module implements the Singleton pattern - i.e.
+# The Singleton module implements the Singleton pattern.
#
-# class Klass
-# include Singleton
-# # ...
-# end
+# Usage:
+# class Klass
+# include Singleton
+# # ...
+# end
#
-# * ensures that only one instance of Klass called ``the instance''
-# can be created.
+# * this ensures that only one instance of Klass lets call it
+# ``the instance'' can be created.
#
# a,b = Klass.instance, Klass.instance
# a == b # => true
# a.new # NoMethodError - new is private ...
#
-# * ``The instance'' is created at instanciation time - i.e. the first call
-# of Klass.instance().
+# * ``The instance'' is created at instanciation time, in other words
+# the first call of Klass.instance(), thus
#
# class OtherKlass
# include Singleton
# # ...
# end
-# p "#{ObjectSpace.each_object(OtherKlass) {}}" # => 0
+# ObjectSpace.each_object(OtherKlass){} # => 0.
#
-# * This behavior is preserved under inheritance.
+# * This behavior is preserved under inheritance and cloning.
#
#
-# This achieved by marking
-# * Klass.new and Klass.allocate - as private and modifying
-# * Klass.inherited(sub_klass) - to ensure
-# that the Singleton pattern is properly inherited.
+# This is achieved by marking
+# * Klass.new and Klass.allocate - as private
+# * removing #clone and #dup and modifying
+# * Klass.inherited(sub_klass) and Klass.clone() -
+# to ensure that the Singleton pattern is properly
+# inherited and cloned.
#
-# In addition Klass is provided with the class methods
-# * Klass.instance() - returning ``the instance''
-# * Klass._load(str) - returning ``the instance''
-# * Klass._wait() - a hook method putting a second (or n-th)
-# thread calling Klass.instance on a waiting loop if the first call
-# to Klass.instance is still in progress.
+# In addition Klass is providing the additional class methods
+# * Klass.instance() - returning ``the instance''. After a successful
+# self modifying instanciating first call the method body is a simple
+# def Klass.instance()
+# return @__instance__
+# end
+# * Klass._load(str) - calls 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 instanciating thread.
#
# The sole instance method of Singleton is
-# * _dump(depth) - returning the empty string
-# The default Marshalling strategy is to strip all state information - i.e.
-# instance variables from ``the instance''. Providing custom
-# _dump(depth) and _load(str) method allows the (partial) resurrection
-# of a previous state of ``the instance'' - see third example.
-#
+# * _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''.
module Singleton
- def Singleton.included (klass)
- # should this be checked?
- # raise TypeError.new "..." if klass.type == Module
- klass.module_eval {
- undef_method :clone
- undef_method :dup
- }
- class << klass
- def inherited(sub_klass)
- # @__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
- sub_klass.instance_eval { @__instance__ = nil }
- def sub_klass.instance
- unless @__instance__.nil?
- # is the extra flexiblity having the hook method
- # _wait() around ever useful?
- _wait()
- # check for instance creation
- return @__instance__ if @__instance__
- end
- Thread.critical = true
- unless @__instance__
- @__instance__ = false
- Thread.critical = false
- begin
- @__instance__ = new
- ensure
- if @__instance__
- define_method(:instance) {@__instance__ }
- else
- # failed instance creation
- @__instance__ = nil
- end
- end
- else
- Thread.critical = false
- end
- return @__instance__
- end
- end
- def _load(str)
- instance
- end
- def _wait
- sleep(0.05) while false.equal?(@__instance__)
- end
- private :new, :allocate
- # hook methods are also marked private
- private :_load,:_wait
+ private
+ # default marshalling strategy
+ def _dump(depth=-1) '' end
+
+ class << self
+ # extending an object with Singleton is a bad idea
+ undef_method :extend_object
+ private
+ def append_features(mod)
+ # This catches ill advisted inclusions of Singleton in
+ # singletons types (sounds like an oxymoron) and
+ # helps out people counting on transitive mixins
+ unless mod.instance_of? (Class)
+ raise TypeError.new "Inclusion of the OO-Singleton module in module #{mod}"
+ end
+ unless (class << mod; self end) <= (class << Object; self end)
+ raise TypeError.new "Inclusion of the OO-Singleton module in singleton type"
+ end
+ super
+ end
+ def included (klass)
+ # remove build in copying methods
+ klass.class_eval do
+ undef_method(:clone) rescue nil
+ undef_method(:dup) rescue nil
+ end
+
+ # initialize the ``klass instance variable'' @__instance__ to nil
+ klass.instance_eval do @__instance__ = nil end
+ class << klass
+ # a main point of the whole exercise - make
+ # new and allocate private
+ private :new, :allocate
+
+ # declare the self modifying klass#instance method
+ define_method (:instance, Singleton::FirstInstanceCall)
+
+ # simple waiting loop hook - should do in most cases
+ # note the pre/post-conditions of a thread-critical state
+ private
+ def _instanciate?()
+ while false.equal?(@__instance__)
+ Thread.critical = false
+ sleep(0.08)
+ Thread.critical = true
+ end
+ @__instance__
+ end
+
+ # default Marshalling strategy
+ def _load(str) instance end
+
+ # ensure that the Singleton pattern is properly inherited
+ def inherited(sub_klass)
+ super
+ sub_klass.instance_eval do @__instance__ = nil end
+ class << sub_klass
+ define_method (:instance, Singleton::FirstInstanceCall)
+ end
+ end
+
+ public
+ # properly clone the Singleton pattern. Question - Did
+ # you know that duping doesn't copy class methods?
+ def clone
+ res = super
+ res.instance_eval do @__instance__ = nil end
+ class << res
+ define_method (:instance, Singleton::FirstInstanceCall)
+ end
+ res
+ end
+ end # of << klass
+ end # of included
+ end # of << Singleton
+
+ 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
+ @__instance__ = new
+ ensure
+ if @__instance__
+ def self.instance() @__instance__ end
+ else
+ @__instance__ = nil # failed instance creation
+ end
+ end
+ elsif _instanciate?()
+ Thread.critical = false
+ else
+ @__instance__ = false
+ Thread.critical = false
+ begin
+ @__instance__ = new
+ ensure
+ if @__instance__
+ def self.instance() @__instance__ end
+ else
+ @__instance__ = nil
+ end
+ end
+ end
+ @__instance__
end
- klass.inherited klass
- end
- private
- def _dump(depth)
- return ""
- end
end
+
+
+
if __FILE__ == $0
-#basic example
+def num_of_instances(klass)
+ "#{ObjectSpace.each_object(klass){}} #{klass} instance(s)"
+end
+
+# The basic and most important example. The latter examples demonstrate
+# advanced features that have no relevance for the general usage
+
class SomeSingletonClass
include Singleton
end
+puts "There are #{num_of_instances(SomeSingletonClass)}"
+
a = SomeSingletonClass.instance
b = SomeSingletonClass.instance # a and b are same object
-p a == b # => true
+puts "basic test is #{a == b}"
+
begin
SomeSingletonClass.new
rescue NoMethodError => mes
puts mes
end
-# threaded example with exception and customized hook #_wait method
+
+
+puts "\nThreaded example with exception and customized #_instanciate?() hook"; p
Thread.abort_on_exception = false
-def num_of_instances(mod)
- "#{ObjectSpace.each_object(mod){}} #{mod} instance"
-end
class Ups < SomeSingletonClass
def initialize
@@ -132,18 +205,23 @@ class Ups < SomeSingletonClass
puts "initialize called by thread ##{Thread.current[:i]}"
end
class << self
- def _wait
+ def _instanciate?
@enter.push Thread.current[:i]
- sleep 0.02 while false.equal?(@__instance__)
+ while false.equal?(@__instance__)
+ Thread.critical = false
+ sleep 0.04
+ Thread.critical = true
+ end
@leave.push Thread.current[:i]
+ @__instance__
end
def __sleep
- sleep (rand(0.1))
+ sleep (rand(0.08))
end
def allocate
__sleep
def self.allocate; __sleep; super() end
- raise "allocation in thread ##{Thread.current[:i]} aborted"
+ raise "boom - allocation in thread ##{Thread.current[:i]} aborted"
end
def instanciate_all
@enter = []
@@ -159,9 +237,9 @@ class Ups < SomeSingletonClass
end
end
end
- puts "Before there were #{num_of_instances(Ups)}s"
- sleep 3
- puts "Now there is #{num_of_instances(Ups)}"
+ puts "Before there were #{num_of_instances(self)}"
+ sleep 5
+ 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"
end
@@ -177,8 +255,17 @@ Ups.instanciate_all
# 2; 3; 6; 1; 7; 5; 9; 4 was the order of threads entering the waiting loop
# 3; 2; 1; 7; 6; 5; 4; 9 was the order of threads leaving the waiting loop
+puts "\nLets see if class level cloning really works"
+Yup = Ups.clone
+def Yup.allocate
+ __sleep
+ def self.allocate; __sleep; super() end
+ raise "boom - allocation in thread ##{Thread.current[:i]} aborted"
+end
+Yup.instanciate_all
+
-# Customized marshalling
+puts "\n","Customized marshalling"
class A
include Singleton
attr_accessor :persist, :die
@@ -195,6 +282,7 @@ end
a = A.instance
a.persist = ["persist"]
a.die = "die"
+a.taint
stored_state = Marshal.dump(a)
# change state
@@ -203,6 +291,47 @@ a.die = nil
b = Marshal.load(stored_state)
p a == b # => true
p a.persist # => ["persist"]
-p a.die # => nil
+p a.die # => nil
+
+puts "\n\nSingleton with overridden default #inherited() hook"
+class Up
+ def Up.inherited(sub_klass)
+ puts "#{sub_klass} subclasses #{self}"
+ end
+end
+
+
+class Middle < Up
+ undef_method :dup
+ include Singleton
+end
+class Down < Middle; end
+
+puts "basic test is #{Down.instance == Down.instance}"
+
+
+puts "\n","Various exceptions"
+
+begin
+ module AModule
+ include Singleton
+ end
+rescue TypeError => mes
+ puts mes #=> Inclusion of the OO-Singleton module in module AModule
+end
+
+begin
+ class << 'aString'
+ include Singleton
+ end
+rescue TypeError => mes
+ puts mes # => Inclusion of the OO-Singleton module in singleton type
+end
+
+begin
+ 'aString'.extend Singleton
+rescue NoMethodError => mes
+ puts mes #=> undefined method `extend_object' for Singleton:Module
+end
end