Upstart 0.5: Relationships
Even the relatively simple System V rc scripts recognise that there are relationships between services, and that in many cases one or more others must be started before a particular service can itself be started: it allows for such relationships to be expressed by using a directory of numbered scripts that are run in series by the sysv rc script.
Tackling this problem in some way is arguably one of the main reasons that each of the alternate init daemons exists. Even launchd acknowledges the problem, even if its solution is to tell service developers that they should spin or sleep while dependencies aren’t available.
The Competition
The way in which the other leading init replacements tackle the relationship problem is through dependencies. This is not that surprising, since the concept is shared (and effectively mirrored) by both the dynamic link loader and the package manager; both things that a service maintainer knows well.
To illustrate how dependencies work, since I use that term precisely to mean only this behaviour, we’ll use one of the chains of the well known Network Manager service.
- Network Manager depends on HAL
- HAL depends on D-Bus
When A depends on B, B is required for A to function properly. Any attempt to start A must first start B.
This works well for the link loader, when we load an executable we also need to load and map the shared objects it links to.
It also works well for the package manager, when we install Network Manager it means we also need to install HAL and D-Bus for it to function.
However for an init daemon, it’s not normally ideal: the only reason that D-Bus and HAL will be running is because Network Manager depends on them. If we were to stop Network Manager, we would also stop HAL and D-Bus.
This obviously isn’t what we want, HAL and D-Bus are both essential services in their own right. Thus we end up with a target or goal set of services that must be started anyway, within this group the dependency relationships are only effective for ordering of them. Ironically, it is very rare indeed for a service to not be a target and so all of the complex ability of the dependency-based daemon is lost; the only reason to generate the dependency tree at runtime at all is to allow for parallel starts.
Upside Down Dependencies
Thus one of the first things that service maintainers have to get used to about Upstart is that its service relationships are upside down from the way that they might expect. Upstart assumes that if a service is installed, not disabled, and the required services, tasks or hardware is available then the service should be running.
In the dependency-based model, starting Network Manager would first start HAL which would first start D-Bus.
In the Upstart (event-based) model, D-Bus is started fulfilling HAL’s requirements so HAL is started, fulfilling Network Manager’s requirements (once a network card is available?) so Network Manager is then started.
Upstart has no notion of targets or goals, it simply ensures that all services that can and should be running are; and ensures that services are stopped when it is no longer the right time for them to be running.
Relationships through Events
The way in which relationships between services are defined is by having services react to each other’s events. To continue with our example, HAL would therefore have the following in its job definition:
start on started dbus
stop on stopping dbus
The first line means that when the dbus service is fully up and running (recall from previous posts that this event can be delayed as necessary), HAL will itself be started.
The second line is a little more interesting. Events in Upstart will block until the jobs they affect complete, and the stopping event is emitted before the dbus job is actually stopped and blocks it from doing so. Put more simply, HAL will be fully stopped before D-Bus is stopped.
Thus we have the simplest kind of Upstart relationship. Starting D-Bus will start HAL immediately afterwards, and stopping D-Bus will stop HAL first.
The portmap problem
Most maintainers at this point will be feeling quite smug and about to hit the comments button because they’ve thought of an example service that actually is a dependency, and should not be running if nothing needs it.
Remember that I said they were rare, not non-existant.
One such example is portmap, another is often something like tomcat. There are a few, but they’re certainly not the common case.
Happily one of the elegant things about Upstart’s design is that it does still support this model where it’s needed. In order for portmap to be started when we start an nfs-server, we simply write the following in portmap’s job definition:
start on starting nfs-server
stop on stopped nfs-server
Compare to the example for D-Bus/HAL and you’ll notice that it’s the events that have changed.
Remember that the starting event, like the stopping event we used in the previous example, blocks the job until jobs affected by the event are completed. Thus this first line means that when we start nfs-server, it will not be started until portmap is started.
And the second line is pretty much the mirror of the first in the previous example, once the nfs-server is stopped, we stop portmap as well since it’s no longer needed.
It may seem a little odd that the rules go in portmap, and not nfs-server, but it makes logical sense. It means that for an admin to work out why portmap is getting started, they just need to read the portmap definition and not hunt around the system to see what else might be doing it.
Also in many of the cases, such requirements are actually conditional. Apache doesn’t need to require tomcat, it’s only a requirement if it’s installed. Thus it makes more sense for tomcat to add itself to Apache’s environment rather than Apache to look for tomcat.






Vadim P.:
Sounds powerful. Does this system keep the same/better speed though?
1 May 2008, 3:30 amJames Henstridge:
If we assumed that SUN-RPC was still popular and people were writing new daemons that depended on portmap, wouldn’t this require updating the portmap package every time a new daemon was added to the repository?
Perhaps an equivalent of the “provides:” package management directive would make sense here, so portmap could say “start on starting portmap-user” and have nfs-server say that it is a “portmap-user”?
1 May 2008, 6:28 amRudd-O:
James, you stole the words from my mouth. It seems this problem requires a dependency injection pattern.
1 May 2008, 7:02 amFrej Soya:
Nice style of blog posts.
I’m probably wrong but, doesn’t this conflict? (seems obvious though, so i’m probably missing something…)
service A has:
start on starting B (A before B)
service B has
start on starting A (B before A)
Does upstart help with this? I’m not saying it should. It should be easy though, just keep the graph of services as a DAG.
Minor nitpick - Semantic difference on start[ed|ing] is a bit hard read. I know it is the current signal/event names. But explicitly denoting the difference of starting as “before”, and started as “after”? Or maybe something else
start after dbus vs. start on starting dbus.
start before nfs-server vs. start on started nfs-server.
There might be other problems with this though, like not matching event names.
Make it easy to write/select the correct configuration - it would be nice. Very un*nix, but still nice
1 May 2008, 10:18 amScott James Remnant:
Right, you’d need to keep the portmap job definition up to date for everything that started it. In the tomcat case, that’s actually just fine since you’d need to update the tomcat package for each web server it works with anyway — but the portmap case it’s a little pessimal.
Fortunately Upstart can do ordinary provision/requirement relationships, I just didn’t want to document them in the blog because you should try to never use this kind of thing.
Remember that the job name is just one parameter to the “starting” event, there are others and you can add any additional ones you want with the “export” variable.
So our nfs-server job could add a parameter to its starting event:
That the portmap job could pick up and match:
1 May 2008, 12:10 pmScott James Remnant:
@Frej: such relationships are entirely supported and unproblematic with Upstart. If you were to “start A”, then B would be started first. If you were to “start B”, then A would be started first.
There’s no cyclic issues or graph resolution because there’s no cycle or graph. A is started, which emits the starting A event, which starts B. B starting emits the starting B event, but that has no effect on A since it’s already started. B finishes starting, unblocking A, and allowing it to finish starting.
This is one of the elegancies of the event-based model.
Of course, there is one way to loop it, job A would have:
and job B would have:
Such a definition would create two jobs that flip-flopped between each other, but that’s a perfectly valid service model — one of “failover”
1 May 2008, 12:14 pm