[RFC] Prototype for new Upstart state machine

Casey Dahlin cdahlin at redhat.com
Thu Nov 6 22:19:21 GMT 2008


Scott James Remnant wrote:
> On Thu, 2008-11-06 at 10:45 -0500, Casey Dahlin wrote:
>
>   
>> I've written a prototype for a service state machine which will 
>> hopefully behave more simply and cleanly than the present event-driven 
>> upstart. I'm posting it here for general comments. Its written in ruby 
>> and consists of the implementation itself and a test suite. You should 
>> find it fairly easy to poke it in irb if you're curious.
>>
>> git clone git://fedorapeople.org/~sadmac/upstate.git
>>
>>     
> For those of us who are ruby impaired, could you explain it? :)
>
> Scott
>   
Gladly.

The user defines several state classes. Each state class contains
* A rising edge action (optional)
* A falling edge action (optional)
* A series of dependencies
* A series of expected hold parameters, which are key-pattern pairs

A dependency contains
* A reference to a state class
* A series of parameter requirements, which are key-pattern pairs

For any state class there may be one or more states. A state consists of
* A reference to its state class
* Zero or more holds
* A series of dependency-state tuples, where the dependency is one of 
the dependencies outlined in the state's class, and the state is a state 
which satisfies that dependency.
* Parameters, which are key-value pairs, and consist wholly of all the 
parameters of the states in the dependency-state tuples, and any 
parameters of the hold which match the class's expected hold parameters

Holds come in 3 types: Dependency, User, and System
- System holds are always placed when an event occurs. Their parameters 
are the same as the event's parameters
- User holds are placed when a user has explicitly told upstart to do 
something. The parameters are defined by the user
- Dependency holds are placed when a state depends on something. Their 
parameters are identical to the parameters of the depending state.

Two states are considered equal if they have the same parameters. At any 
given time no two equal states may exist. Attempting to create a state 
which is equal to another, or to cause a state to become equal to 
another, will yield the pre-existing state.

A state is defined as "up" if it has at least one hold, and "down" 
otherwise. When a "down" state has a hold placed on it, it will run the 
rising edge action for its state class, passing to it all of its 
parameters. If there is a failure in the rising edge action, then the 
attempt to place the hold will fail and the state will remain down. The 
hold will also not be placed if each of the state's class's expected 
hold parameters. Finally, before a state comes "up" it must place a 
Dependency holds on all the states in its dependency-state tuple list. 
If any of those hold placements fail, the state remains down, and this 
hold placement fails. Hold placement should /never/ fail if a state is 
already up at time of placement.

A state can be forced down by user intervention or some system cases. 
Forcing a state down consists of iterating the list of holds, "clearing" 
each of them, and then discarding them from the state's list of holds. 
For System and User holds, clearing is a no-op. For Dependency holds, 
clearing consists of forcing the dependant state down. Whenever the last 
hold is removed from a state's list of dependencies, it runs its class's 
falling edge action if present (failure is reported as an error, and 
otherwise ignored), then removes any holds it placed on other states 
when coming up.

It should be noted that because two states are judged to be equal or not 
based on their parameters, and parameters are partially taken from the 
first hold placed on a state, a state coming up may in some cases cease 
to be equal to what it was previously, meaning that a downed version of 
its former self can spring up alongside it (and necessarily will, as we 
will see in a moment). A state can also become equal to another state 
when a hold is placed on it. This causes the state to disappear, and it 
is ensured that no rising-edge action takes place when this occurs. 
Finally, a state can become equal to another state when its holds are 
removed. Again, the state disappears in this case, but the falling-edge 
action is still executed. (Implementation note: high level procedural 
languages hate these behaviors).

When the system comes online, there are several state classes, but no 
states. At this point, and every time a state moves from up to down, or 
down to up, depsolving occurs. The procedure is as follows:
1) All states which are presently down cease to exist.
2) For each state class:
A) For each dependency, find all states which meet the dependency, put 
this into a "list of dependency solutions"
B) If any of the dependencies can't be solved, go to the next state class
C) Generate a list of all possible ways to choose exactly one item from 
each list of dependency solutions (aggregate cross product)
D) Find each member of the above for which each state therein is "rooted 
in" that member. A state is "rooted in" a list of states if any 
dependency which /can/ be met by a member of the list /is/, and if all 
of its dependencies are rooted in the same list.
E) For each result from D, attempt to create a new state with the 
current class and dependencies from the given list.

At the start of the program, there are no "up" states, so the only new 
down states which will be created are ones with no dependencies.

Now events:

An event consists of:
* A name
* A key-value list of parameters

Every state class has a series of key-(list of key-pattern) pairs which 
say what events it responds to. If an event occurs which a given state 
class responds to, any down states of that class have a system hold 
placed on them. Failure to place the hold is reported as an error and 
otherwise ignored. The system hold's parameters are taken from the event.

There is a special event called Epsilon or ε, which is automatically 
generated by the state machine at certain times. It has no parameters, 
and is defined as occuring "whenever it would cause something to 
happen." You can think of defining a state class as responding to ε as 
defining it to start automatically, and indeed this is how the 
configuration interface will expose it.

Hope that made things interesting.

--CJD



More information about the upstart-devel mailing list