summaryrefslogtreecommitdiff
path: root/lib/singleton.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/singleton.rb')
-rw-r--r--lib/singleton.rb387
1 files changed, 210 insertions, 177 deletions
diff --git a/lib/singleton.rb b/lib/singleton.rb
index 404eaf7101..74aec8903c 100644
--- a/lib/singleton.rb
+++ b/lib/singleton.rb
@@ -1,207 +1,240 @@
-# The Singleton module implements the Singleton pattern - i.e.
+# frozen_string_literal: true
+
+# The Singleton module implements the Singleton pattern.
+#
+# == Usage
+#
+# To use Singleton, include the module in your class.
+#
+# class Klass
+# include Singleton
+# # ...
+# end
#
-# class Klass
-# include Singleton
-# # ...
-# end
+# This ensures that only one instance of Klass can be created.
#
-# * ensures that only one instance of Klass called ``the instance''
-# can be created.
+# a,b = Klass.instance, Klass.instance
#
-# a,b = Klass.instance, Klass.instance
-# a == b # => true
-# a.new # NoMethodError - new is private ...
+# a == b
+# # => true
#
-# * ``The instance'' is created at instanciation time - i.e. the first call
-# of Klass.instance().
+# Klass.new
+# # => NoMethodError - new is private ...
#
-# class OtherKlass
+# The instance is created at upon the first call of Klass.instance().
+#
+# class OtherKlass
# include Singleton
# # ...
-# end
-# p "#{ObjectSpace.each_object(OtherKlass) {}}" # => 0
+# end
+#
+# ObjectSpace.each_object(OtherKlass){}
+# # => 0
+#
+# OtherKlass.instance
+# ObjectSpace.each_object(OtherKlass){}
+# # => 1
+#
#
-# * This behavior is preserved under inheritance.
+# This behavior is preserved under inheritance and cloning.
#
+# == Implementation
#
-# 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 above is achieved by:
#
-# 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.
+# * Making Klass.new and Klass.allocate private.
#
-# 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.
+# * Overriding Klass.inherited(sub_klass) and Klass.clone() to ensure that the
+# Singleton properties are kept when inherited and cloned.
+#
+# * Providing the Klass.instance() method that returns the same object each
+# time it is called.
+#
+# * Overriding Klass._load(str) to call Klass.instance().
+#
+# * Overriding Klass#clone and Klass#dup to raise TypeErrors to prevent
+# cloning or duping.
+#
+# == Singleton and Marshal
+#
+# By default Singleton's #_dump(depth) returns the empty string. Marshalling by
+# default will strip state information, e.g. instance variables from the instance.
+# Classes using Singleton can provide custom _load(str) and _dump(depth) methods
+# to retain some of the previous state of the instance.
+#
+# require 'singleton'
+#
+# class Example
+# include Singleton
+# attr_accessor :keep, :strip
+# def _dump(depth)
+# # this strips the @strip information from the instance
+# Marshal.dump(@keep, depth)
+# end
+#
+# def self._load(str)
+# instance.keep = Marshal.load(str)
+# instance
+# end
+# end
+#
+# a = Example.instance
+# a.keep = "keep this"
+# a.strip = "get rid of this"
+#
+# stored_state = Marshal.dump(a)
+#
+# a.keep = nil
+# a.strip = nil
+# b = Marshal.load(stored_state)
+# p a == b # => true
+# p a.keep # => "keep this"
+# p a.strip # => nil
#
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
+ # The version string
+ VERSION = "0.3.0"
+
+ module SingletonInstanceMethods # :nodoc:
+ # Raises a TypeError to prevent cloning.
+ def clone
+ raise TypeError, "can't clone instance of singleton #{self.class}"
+ end
+
+ # Raises a TypeError to prevent duping.
+ def dup
+ raise TypeError, "can't dup instance of singleton #{self.class}"
+ end
+
+ # By default, do not retain any state when marshalling.
+ def _dump(depth = -1)
+ ''
end
- klass.inherited klass
end
- private
- def _dump(depth)
- return ""
+ include SingletonInstanceMethods
+
+ module SingletonClassMethods # :nodoc:
+
+ def clone # :nodoc:
+ Singleton.__init__(super)
+ end
+
+ # By default calls instance(). Override to retain singleton state.
+ def _load(str)
+ instance
+ end
+
+ def instance # :nodoc:
+ @singleton__instance__ || @singleton__mutex__.synchronize { @singleton__instance__ ||= new }
+ end
+
+ private
+
+ def inherited(sub_klass)
+ super
+ Singleton.__init__(sub_klass)
+ end
+
+ def set_instance(val)
+ @singleton__instance__ = val
+ end
+
+ def set_mutex(val)
+ @singleton__mutex__ = val
+ end
+ end
+
+ def self.module_with_class_methods # :nodoc:
+ SingletonClassMethods
end
-end
-if __FILE__ == $0
+ module SingletonClassProperties # :nodoc:
-#basic example
-class SomeSingletonClass
- include Singleton
-end
-a = SomeSingletonClass.instance
-b = SomeSingletonClass.instance # a and b are same object
-p a == b # => true
-begin
- SomeSingletonClass.new
-rescue NoMethodError => mes
- puts mes
-end
+ def self.included(c)
+ # extending an object with Singleton is a bad idea
+ c.undef_method :extend_object
+ end
-# threaded example with exception and customized hook #_wait method
-Thread.abort_on_exception = false
-def num_of_instances(mod)
- "#{ObjectSpace.each_object(mod){}} #{mod} instance"
-end
-
-class Ups < SomeSingletonClass
- def initialize
- type.__sleep
- puts "initialize called by thread ##{Thread.current[:i]}"
- end
- class << self
- def _wait
- @enter.push Thread.current[:i]
- sleep 0.02 while false.equal?(@__instance__)
- @leave.push Thread.current[:i]
- end
- def __sleep
- sleep (rand(0.1))
- end
- def allocate
- __sleep
- def self.allocate; __sleep; super() end
- raise "allocation in thread ##{Thread.current[:i]} aborted"
- end
- def instanciate_all
- @enter = []
- @leave = []
- 1.upto(9) do |i|
- Thread.new do
- begin
- Thread.current[:i] = i
- __sleep
- instance
- rescue RuntimeError => mes
- puts mes
- end
- end
- end
- puts "Before there were #{num_of_instances(Ups)}s"
- sleep 3
- puts "Now there is #{num_of_instances(Ups)}"
- 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
+ def self.extended(c)
+ # extending an object with Singleton is a bad idea
+ c.singleton_class.send(:undef_method, :extend_object)
end
-end
-Ups.instanciate_all
-# results in message like
-# Before there were 0 Ups instances
-# boom - allocation in thread #8 aborted
-# initialize called by thread #3
-# Now there is 1 Ups instance
-# 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
+ def __init__(klass) # :nodoc:
+ klass.instance_eval {
+ set_instance(nil)
+ set_mutex(Thread::Mutex.new)
+ }
+ klass
+ end
+ private
-# Customized marshalling
-class A
- include Singleton
- attr_accessor :persist, :die
- def _dump(depth)
- # this strips the @die information from the instance
- Marshal.dump(@persist,depth)
+ def append_features(mod)
+ # help out people counting on transitive mixins
+ unless mod.instance_of?(Class)
+ raise TypeError, "Inclusion of the OO-Singleton module in module #{mod}"
+ end
+ super
end
+
+ def included(klass)
+ super
+ klass.private_class_method :new, :allocate
+ klass.extend module_with_class_methods
+ Singleton.__init__(klass)
+ end
+ end
+ extend SingletonClassProperties
+
+ ##
+ # :singleton-method: _load
+ # By default calls instance(). Override to retain singleton state.
+
+ ##
+ # :singleton-method: instance
+ # Returns the singleton instance.
end
-def A._load(str)
- instance.persist = Marshal.load(str)
- instance
-end
-a = A.instance
-a.persist = ["persist"]
-a.die = "die"
+if defined?(Ractor)
+ module RactorLocalSingleton # :nodoc:
+ include Singleton::SingletonInstanceMethods
+
+ module RactorLocalSingletonClassMethods # :nodoc:
+ include Singleton::SingletonClassMethods
+ def instance
+ set_mutex(Thread::Mutex.new) if Ractor.current[mutex_key].nil?
+ return Ractor.current[instance_key] if Ractor.current[instance_key]
+ Ractor.current[mutex_key].synchronize {
+ return Ractor.current[instance_key] if Ractor.current[instance_key]
+ set_instance(new())
+ }
+ Ractor.current[instance_key]
+ end
+
+ private
+
+ def instance_key
+ :"__RactorLocalSingleton_instance_with_class_id_#{object_id}__"
+ end
+
+ def mutex_key
+ :"__RactorLocalSingleton_mutex_with_class_id_#{object_id}__"
+ end
-stored_state = Marshal.dump(a)
-# change state
-a.persist = nil
-a.die = nil
-b = Marshal.load(stored_state)
-p a == b # => true
-p a.persist # => ["persist"]
-p a.die # => nil
+ def set_instance(val)
+ Ractor.current[instance_key] = val
+ end
+ def set_mutex(val)
+ Ractor.current[mutex_key] = val
+ end
+ end
+
+ def self.module_with_class_methods
+ RactorLocalSingletonClassMethods
+ end
+
+ extend Singleton::SingletonClassProperties
+ end
end