Friday, August 1, 2008

Upstart wishlist

There's still quite a few features I would like to see in Upstart, all of which I intend to start implementing as soon as 0.5 is released. Here's a list:

Global Environment


As of current 0.5 head, Upstart has a global upstart.conf file. Also as of current 0.5 head, there are no valid settings which can be set from this file. That's right, it can be blank, it can have white space, it can have comments. The file doesn't have to exist for upstart to work, so its not a big deal, but it seems like there's something we could do with it.

Global environment is one purposing I have for this. The idea is to be able to inject environment variables globally into all jobs. The standard use case for this would be default runlevel, which is better as a system-level setting. There are numerous other examples. Basically anywhere it would be a good idea to make static information available to all upstart jobs, this is relevant.

Explicit Positioning for Environment


If you set a job to start on foo BAR=1 BAZ=2, that job will start on initctl emit foo BAR=1 BAZ=2, initctl emit foo BAZ=2 BAR=1, but not initctl emit foo BAZ=1 BAR=2. Upstart also supports implicit environment matching, so you could specify start on foo 1 2, and you would start on initctl emit foo BAR=1 BAZ=2, initctl emit foo BAZ=1 BAR=2, initctl emit foo FROB=1 BAR=2, but not initctl emit foo BAZ=2 BAR=1. This is horrible and stupid.

The solution here is another use for our global config file. Instead of relying on the order of passage to initctl, we can simply supply upstart with a default ordering of environment variables for a given event. So with a stanza like takes foo BAR BAZ in upstart.conf, the two start on formats would become equivalent. Furthermore, initctl could parse the same config file, allowing you to initctl emit foo 1 2. Convenient.

True Non-Determinism


If you wanted to run one instance of a job for every tty on your computer, whenever the interface was up, you might try this:

start on new-tty and interface-up
instance $TTY

It seems like it should work, but what happens when you get this sequence of events?

  • new-tty TTY=tty3

  • new-tty TTY=tty4

  • interface-up


When upstart sees the first new-tty event it makes note that one half of the and has been satisfied. The second, however, is ignored, so we only get the job running for one TTY. The solution is non-deterministic matching, and while there's a bit of fiddling involved in that, the upshot is you get the behaviour you expect when these things occur.

Groups


Groups are one possibility for runlevel-like functionality in upstart. The idea is basically this: At any given time, there are one or more "groups" active in upstart, as well as one or more groups suppressed. and each job belongs to one or more groups. A job always belongs to a group which is its name, and if no other groups have been specified for it, it belongs to the default group. The user can also specify implications between groups, so if a job is a member of group A, it will, by definition, be a member of group B.

In order for a job to be running, one of the groups of which it is a member must be active, and either none of the groups it is in may be suppressed, or the group for which shares its name must be active. The default group is always active at startup, all other groups are always inactive at startup. If a job tries to run while it has no active groups or one of its groups is suppressed, it will be put into a special waiting state, and as soon as one of its groups becomes active or the suppressed group is unsuppressed, it will start (unless something tells it to stop before this happens). If the group goes away again, the job will return to that waiting state.

This is a very flexible system, lying somewhere in between the previous profiles suggestion and the use of simple flags.

Controlled Starting


The groups idea allows us to be a bit more sane about the start and stop commands. Right now, you can just start a job at any time, even if the dependencies which its states were designed to monitor aren't there.

Taking the groups idea, consider this semantic instead:

  • If a job is blocked due to a group issue and the user runs start, its namesake group is activated

  • If a job has not in any way been signaled to run and the user runs start, the user is asked to try again with --force

  • If a user runs stop the job's namesake group is put into the suppressed state


This both allows upstart to remain in control of dependency solving, lets the user take control if desired, and prevents upstart from undoing a user's explicit act in a job without ceasing to track dependency information for the job.

States


Its a much-belabored idea for people who have been following upstart, but since those people are few, I'll discuss it here:

The idea behind states is to allow someone to say "Be running when X is true" rather than "Start on X, stop on Y." The former allows you to express some things that the latter just doesn't, like keeping a job running when 2 other jobs are both running.

0 comments: