summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCasey Dahlin <cdahlin@redhat.com>2008-10-01 16:36:14 -0400
committerCasey Dahlin <cdahlin@redhat.com>2008-10-01 16:36:14 -0400
commit2a728145218ba350ed95c7c1d93641e14884a0c2 (patch)
tree53b317a8733734852a7b09feea6af8b9e8ab74bf
downloadupstate-2a728145218ba350ed95c7c1d93641e14884a0c2.tar.gz
upstate-2a728145218ba350ed95c7c1d93641e14884a0c2.tar.xz
upstate-2a728145218ba350ed95c7c1d93641e14884a0c2.zip
Initial commit
-rw-r--r--.gitignore1
-rw-r--r--state.rb304
2 files changed, 305 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a01ee28
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.*.swp
diff --git a/state.rb b/state.rb
new file mode 100644
index 0000000..be6b7c0
--- /dev/null
+++ b/state.rb
@@ -0,0 +1,304 @@
+=begin rdoc
+UpState - An Upstart state machine prototype
+
+Author: Casey Dahlin <cjdahlin@ncsu.edu>
+=end
+require 'set'
+
+module UpState
+
+# Occurs when the state machine becomes inconsistent
+class ConsistencyFault < StandardError; end
+
+=begin rdoc
+An instance of State exists whenever a state can become true without having to
+toggle any other states, or whenever a state _is_ true.
+=end
+class State
+ attr :params # Parameters to this state
+ attr :holds # Holds that are had on this state
+ attr :active # Are we active?
+ class << self
+ attr :depends # What states does this state depend on?
+ attr :caused_by # What events can cause this state?
+ attr :rising_edge # What do we do as we're coming up?
+ attr :falling_edge # What do we do as we're going down?
+ protected
+ attr_writer :depends
+ attr_writer :caused_by
+ attr_writer :rising_edge
+ attr_writer :falling_edge
+ end
+
+ @@states = Set.new # Set of activatable states
+ @@state_types = Set.new # Set of state types
+
+ # Create a new state. +deps+ is a list of State objects, not patterns
+ def initialize(deps)
+ raise NoMethodError if self.class == State
+
+ @holds = Set.new
+ @deps = deps
+ @params = {}
+ @active = false
+
+ return if @@states.include? self
+ @@states.add self
+ hold(:system) if self.class.caused_by.include? Event::Epsilon
+ end
+
+ # Returns true if this state should be raised if +event+ is occuring
+ def respond_to?(event)
+ return false if @active
+ @caused_by.each do |x|
+ return true if x === event
+ end
+ return false
+ end
+
+ # Make sure our deps are satisfied, and remove ourselves from the list of
+ # states if they aren't
+ def check_deps
+ @deps.each do |dep|
+ next if dep.active
+ raise ConsistencyFault if self.active
+ @@states.delete self
+ self.methods.each{ |x| undef x }
+ self.freeze
+ end
+ end
+
+ # Hold this state. Holding a state informs the system that you would like it
+ # to come up or remain up. +hold+ can be an object of type Hold, an object of
+ # type State to establish that said state depends on this one, or one of
+ # <tt>:user</tt> or <tt>:system</tt> to establish that the user or system
+ # has an interest in keeping this service running
+ def hold(hold)
+ hold = Hold::Dep.new(hold) if hold.is_a? State
+ hold = Hold::User.new if hold == :user
+ hold = Hold::System.new if hold == :system
+ raise TypeError unless hold.is_a? Hold
+ @holds.add hold
+ rise if @holds.size == 1
+ end
+
+ # Release a hold on this state. Arguments are the same as for +add_hold+.
+ def release(hold)
+ hold = Hold::Dep.new(hold) if hold.is_a? State
+ hold = Hold::User.new if hold == :user
+ hold = Hold::System.new if hold == :system
+ raise TypeError unless hold.is_a? Hold
+ @holds.remove hold
+ drop if @holds.size == 0
+ end
+
+ # Set this state to untrue
+ def drop
+ return unless @active
+ break_holds if @holds.size > 0
+ self.class.falling_edge.call(@params)
+ becomes_defunct
+ end
+
+ # Set this state to untrue without running any falling edge code
+ def becomes_defunct
+ return unless @active
+ break_holds if @holds.size > 0
+ @active = false
+ self.class.depends.each{ |x| x.remove_hold(x) }
+ State.gc
+ end
+
+ # Determine if two State objects are equivalent
+ def ==(other)
+ other.is_a? self.class and other.params == @params
+ end
+
+ # Match this State object to patterns or other State objects
+ def ===(other)
+ return other === self if other.is_a? StatePattern
+ return self == other
+ end
+
+ # Create a new type of state. +name+ is capitalized and postfixed with "State"
+ # to create the new state class name. +depends+ is a series of StatePattern
+ # Objects
+ def State.new_type(name, caused_by, depends)
+ name = name.capitalize + "State"
+ raise NameError, "State already exists" if self.const_defined? name
+ newtype = const_set(name, Class.new(State))
+ newtype.depends = depends
+ newtype.caused_by = caused_by
+ newtype.rising_edge = Proc.new{}
+ newtype.falling_edge = Proc.new{}
+ newtype.instance_eval do
+ undef :new_type, :process_event, :gc, :epsilate
+ end
+ @@state_types.add newtype
+ newtype
+ end
+
+ # Handle the occurance of an event
+ def State.process_event(event)
+ raise TypeError unless event.is_a? Event
+ count = 0
+ @@states.select{ |x| x.active }.each do
+ next unless x.respond_to? event
+ x.hold(:system)
+ count += 1
+ end
+ count
+ end
+
+ # Remove inactive states whose deps are no longer satisfied
+ def State.gc
+ @@states.each{ |x| x.check_deps }
+ nil
+ end
+
+private
+ # Set this state to true
+ def rise
+ return if @active
+ self.class.depends.each{ |x| x.hold(self) }
+ self.class.rising_edge.call(@params)
+ @active = true
+ end
+
+ # Inform other states that they may no longer depend on this one
+ def break_holds
+ @holds.each{ |x| x.clear }
+ @holds = Set.new
+ end
+end
+
+=begin rdoc
+A Hold is placed on a State when something has an interest in that thing being
+true. States drop automatically when they cease to have any holds on them.
+=end
+class Hold
+ def initialize #:nodoc:
+ raise NoMethodError if self.class == Hold
+ end
+
+ # Inform interested parties that this hold can no longer be maintained, and
+ # that the held state is going to drop.
+ def clear; end
+
+ # The non-dep holds have no interesting attributes and may be compared
+ # solely by type
+ def ==(other) #:nodoc:
+ self.class == other.class
+ end
+
+ # A hold placed when the system has an interest in something being running
+ class System < Hold; end
+ # A hold placed when the user has asked for something to be running
+ class User < Hold; end
+
+ # A hold place when a state is depended on by another state.
+ class Dep < Hold
+ # +dependent+ must be an object of type State
+ def initialize(dependent)
+ raise TypeError unless dependent.is_a? State
+ @dependent = dependent
+ end
+
+ # Kill the dependent state so that the depended state can drop
+ def clear
+ @dependent.drop
+ end
+
+ # Two Dep holds are equal if they have the same dependent
+ def ==(other)
+ return other == @dependent if other.is_a? State
+ return other.dependent == @dependent if other.is_a? Dep
+ return false
+ end
+
+ protected
+ attr :dependent # State which depends on what we hold
+ end
+end
+
+=begin rdoc
+A StatePattern is a notion of a state which might exist. It will contain the
+type of the state and perhaps some sort of patterns which certain parameters
+must conform to, but it does not necessarily contain enough info to define a
+state in its entirety.
+=end
+class StatePattern
+ # A state pattern is created from a class of state to match, and a hash of
+ # parameters. The parameters in the hash can be string values to match or
+ # Regex objects
+ def initialize(stateclass, params = {})
+ raise TypeError unless State < stateclass
+ raise TypeError unless params.is_a? Hash
+ @stateclass = stateclass
+ @params = params
+ end
+
+ # Two state patterns are equal if any given set would be either matched
+ # by both or rejected by both
+ def ==(other)
+ return false unless other.is_a? StatePattern
+ return(other.stateclass == @stateclass and other.params == params)
+ end
+
+ # Tests for equality between state patterns or matching between a state
+ # pattern and a state
+ def ===(other)
+ return(self.match(other) or self == other)
+ end
+
+ # Determines if a state matches this pattern
+ def match(other)
+ return false unless other.is_a? @stateclass
+ return false if (@params.keys - other.params.keys).size > 0
+ @params.each do |x,y|
+ return false unless y === other.params[x]
+ end
+ return true
+ end
+
+protected
+ attr :stateclass # What class of state do we match?
+ attr :params # What parameters do we expect?
+end
+
+=begin rdoc
+An event indicates something happening to the system that may cause a state
+change
+=end
+class Event
+ attr :name # What happened?
+ attr :params # Where/when/why/how/to what did it happen?
+
+ # Params should be a series of specific properties of this event in hash form
+ def initialize(name, params = {})
+ @name = name
+ @params = params
+ end
+
+ # Epsilon is defined as "An event which occurs whenever its occuring would
+ # cause change in the system"
+ Epsilon = self.new("ε")
+
+ # Two Events are equal if their params are equal and their name is the same
+ def ==(other)
+ other.is_a? Event and \
+ @params == other.params and \
+ @name == other.name
+ end
+
+ # Two Events match if any common params match and their name is the same
+ def ===(other)
+ return false unless other.is_a? Event and @name == other.name
+ @params.each do |x, y|
+ return false unless y === other.params[x]
+ end
+ return true
+ end
+end
+
+end # module UpState