[systemd-devel] proposal to more precisely articulate the meaning of start and stop dependencies

James Feeney james at nurealm.net
Sat Dec 24 17:12:53 UTC 2016


>From the point of view of a systemd user, not a developer, and at the same time,
while following discussions of proper meaning and operation of systemd
dependencies, it appears to me that there is confusion with respect to the
precise meaning of dependency terms used in the documentation.  Here is offered
a framework which may allow a more clear and precise definition of the various
actions provided by systemd.  It is hoped that this framework will facilitate
communication and understanding when discussing the "correct and proper"
functioning of the various dependency relationships.

I have been told that "Systemd is job manager" and "Systemd is a
dependency-based job queue dispatcher", which I take to mean that, in simplest
terms, systemd provides exactly two actions: starting a job, and stopping a job.
 I would say that systemd is both a declarative language interpreter and a
declarative language made to describe the starting and stopping of jobs using a
set of "start/stop" dependency terms and "ordering" dependency terms.  When and
how systemd accomplishes these things is an implementation detail, to be
abstracted and hidden from the administrator. The framework described here
addresses these start/stop terms.

I will make the argument that, currently, systemd over-complicates the
dependency terminology, partly by unnecessarily "overloading" the terms used,
and partly by providing an incomplete set of terms for describing more basic
actions.  Further, I will argue that this terminology can be clarified by using
a more complete set of basic terms to describe more complex actions.

In systemd parlance, I surmise that a "job" is associated with a "unit", where a
"unit" is the systemd abstraction for all manner of regular and special files,
in the classic Unix sense of "everything is a file".  At the same time, the
state of a unit is not identically the same thing as the state of the "file" it
abstracts or the job with which it is associated.  Generally, systemd
dependencies describe binary starting and stopping relationships between these
units.

We will see that almost all of the terms currently used in the documentation
will describe pairs of starting and stopping relationships, rather than
describing individual starting and stopping relationships.  This makes
discussion of simple starting and stopping relationships almost impossible.

A simple starting/stopping relationship for which there is no agreed-upon
descriptive word makes for very confusing and difficult conceptualization and
communication.  As a consequence, it becomes difficult to articulate whether a
particular starting or stopping function is "correct and proper", or not.

Consider:

Given two units, A and B, the systemd Unit Activation and Deactivation
Dependency Options, may be categorized across two dimensions: forward/reverse
and start/stop.  The terms "forward" and "reverse" refer to the roles of actor
and object in the relationship between the unit file in which the dependency
option is written - "this unit" - and the right-hand-side unit file listed in a
dependency option - "that unit, the listed unit".  The terms "start" and "stop"
refer to the action being propagated from "this unit" to "that unit" in the
"forward" direction or from "that unit" to "this unit" in  the "reverse" direction.

There are three binary variables, and so eight possible combinations here:

Predicate : Consequence

Forward Dependencies -
A Start : B Start	Wants
A Start : B Stop	(Conflicts - not simple)
A Stop : B Start	OnFailure
A Stop : B Stop		(RequiredBy - not simple)
Reverse Dependencies -
B Start : A Start	WantedBy
B Start : A Stop	(Conflicts - not simple)
B Stop : A Start	( not available )
B Stop : A Stop		(several - Requires - not simple)

These can be divided into two sets of four, to allow itemizing each set in two
dimensions, here by selecting like-paired and opposite-paired actions:

Forward Dependencies -
A Start : B Start	Wants
A Stop : B Stop		(RequiredBy - not simple)
Reverse Dependencies -
B Start : A Start	WantedBy
B Stop : A Stop		(several - Requires - not simple)

Forward Dependencies -
A Start : B Stop	(Conflicts - not simple)
A Stop : B Start	OnFailure
Reverse Dependencies -
B Start : A Stop	(Conflicts - not simple)
B Stop : A Start	( not available )

In table form, the documented dependencies can be listed as:

	Unit Activation and Deactivation Dependency Options
Like-paired actions
	Forward - "this unit acts"		Reverse - "that unit acts"
Start	Requires, Wants, (Unit), BindsTo?	PartOf, WantedBy, RequiredBy
Stop	RequiredBy				PartOf, Requires, Requisite,
						BindsTo
Opposite-paired actions
	Forward - "this unit acts"		Reverse - "that unit acts"
Start	Conflicts				Conflicts
Stop	OnFailure				(not available)

In addition to being an extremely useful summary, the purpose of these tables is
to show that almost all of the terms are "complex" in the sense that they refer
to pairs of starting/stopping relationship actions.  The only exceptions appear
to be Wants, WantedBy, and OnFailure, where the actual function of Requisite and
BindsTo are currently not clear.  Further, OnFailure, BindsTo and PartOf are
"irregular" in the sense that OnFailure only responds to a "failed state" rather
than a simple "stop", BindsTo responds only to a "disappearing" unit, and PartOf
partially applies only to a "re-starting" unit.

As I understand, currently, systemd literally manages these dependencies *as*
pairs of relations.  The obvious conjecture then: A more robust and easier to
maintain design would make use of simple "A:B" relationships, which could be
used to build more complex "paired" functions.  Would you agree?

Consider the set of four "like-paired" actions.  The combination of four things
taken two at a time allows for six possible paired combinations:

f Forward
r Reverse
a Starts
o Stops

fafo		(Twin)
fara		(Mutual)
faro	Requires
fora	RequiredBy
foro		(Tied)
raro	PartOf - irregular

Similarly, for "opposite paired" actions:

fafo
fara
faro
fora
foro	Conflicts
raro

Certainly, it is more manageable to provide eight basic functions, and then use
these to build-up twelve *additional* functions, than to create an *incomplete*
set of complex functions, and then try to manage the additional combinations of
those, as well as try to develop basic functions from complex functions, yes?

More importantly, systemd would not have to address these paired functions at
all, not as "first order objects" or as "first class functions", when the
user/administrator could just as well combine a basic set of eight dependency
relations themselves, when writing a unit file.

Presumably, in the Unix tradition, it is not the purpose of systemd to impose
"policy" or restrict the kinds of actions that an administrator may be allowed
to implement, by providing some "crippled" or "restricted" set of systemd
dependency options.  In addition to the simplification afforded by use of a
"basic set" of dependency options, providing a *complete basic set* of options
provides the administrator with more power and flexibility.

Note that, if considering pairs of start/stop relations from the entire set of
eight basic relations, the total number of combinations, eight things taken two
at a time, is twenty-eight.  Choosing the division of like-paired actions and
opposite-paired actions, discussing two group of four things taken two at a
time, is a hopefully useful and practical simplification.

Some of this basic set of eight dependency relations seems to already exist in
the form of: Wants, WantedBy, Requisite (possibly), and OnFailure (irregular).
Four additional terms would have to be chosen to complete the set, but such
prosaic terminology as is used in systemd is not a requirement.  Still, these
might be named simply: Stops (or Terminates), ConflictsB, ConflictsA, and Versus.

*If* a basic set of eight dependency relations are accepted - even, if only for
the sake of discussion - there is then the question of "proper time" when
considering the "activation state" of units, and the state of the actual
"special files" these units have abstracted.  The "proper time" is the sampling
instant at which unit states and system states are examined, and the start/stop
and ordering dependency logic evaluated, to determine what start and stop
actions systemd will execute in response to some event.

Consider - what is the activation state of an individual unit which must execute
a service when that service has net yet been executed?  There are four phases to
that activation, where each phase might be considered as a sampling instant or
"proper time" for discussion.  First phase, the unit is inactive.  Second phase,
the unit is "queued for starting or stopping", but the service has not yet been
executed.  Third phase, the unit is active and the service has been executed.
The result is not yet known and a timer has been started, DefaultTimeoutStartSec
or DefaultTimeoutStopSec.  Fourth phase, the timer has been stopped or the timer
has expired, and the result of activating the unit is known.

The third and fourth phase are not useful as the proper time since systemd is
concerned with the dependencies between units.  The result state of a unit
activation exists at some time in the future and will generate its own event.
Even if, for instance, a process immediately fails to execute, that is still a
separate event in the future.

The first phase is an attractive proper time since it encompasses what is known
from evaluating the previous event.  But it fails to include the effect of
mutual unit dependencies, where for instance, the starting of one unit may
depend upon the starting of a second unit which also depends upon the first unit.

The second phase is most useful as the proper time for discussing the state of
units, though this would seem to "presume the conclusion", when evaluating
dependencies.  For instance, how are circular dependencies to be evaluated?

As a user, I am "on the outside looking in".  I have not deciphered the systemd
code base.  Some people obviously know how this evaluation process is done in
systemd.  Still, for the purpose of discussion, it seems to me that the
evaluation must occur in steps, following a dependency tree, first evaluating
start dependencies, and then, using the projected start states, evaluating stop
dependencies, before finally queuing start and stop jobs.

Supposing that that evaluation of a dependency tree is what happens in practice,
then, to communicate precisely and avoid confusion, it will be necessary to
articulate that tree evaluation process when discussing the starting and
stopping of units.  It seems to me that some of the discussion about the meaning
of particular dependencies has revolved around confusion over the sequence of
steps used in, effectively, evaluating the dependency tree.  There is no systemd
"design document" describing this evaluation process that I have discovered.
Instead, the design seems to develop in an "ad hoc" manner, "buried" in the
code.  Perhaps someone would be willing to "extract" and formalize the systemd
design document, with respect to the priority of steps taken in evaluating the
dependency tree?  Is there some single systemd program file in which these rules
are collected?

To clarify the meaning of the terms "Forward" and "Reverse" with respect to
"Start" and "Stop", the following definitions are quoted from the man pages at
systemd.unit and systemd.timer:


Dependency	Man page Definition

	Forward Start

Requires	If this unit gets activated, the units listed here will be activated as
well.
	[ X Starts Y and X StoppedBy Y ]

[ Note that when a "Requires" and a "Before" dependency list the same timer
unit, this unit will fail to start when it is restarted, even though this unit
is able to start from an "inactive" state.  This unit will show an error on
restart "Job for foo.service canceled" even though there is no warning or error
on the initial start. ]
	
Wants		Units listed in this option will be started if the configuring unit is.
	[ X Starts Y ]

[ Note that "Wants" will fail to start a listed timer unit when this unit is
restarted, and the timer unit's	associated service unit will also not be started
when this unit is restarted. ]

BindsTo		Configures requirement dependencies, very similar in style to Requires=
 [ "If this unit gets activated, the units listed here will be activated as
well."  This is meaningless when the listed unit is a device special file. ]
	[ X Starts Y and X StopppedBy Disappearing Y ]

OnFailure	... units that are activated when this unit enters the "failed" state.
	[ X OnFailure Starts Y ]

Unit		The unit to activate when this timer elapses.  [ Only in a .timer unit file. ]
	[ X Starts Y ]


	Reverse Start

PartOf		When systemd ... restarts the units listed here, the action is
propagated to this unit.
	[ X OnlyReStartedBy Y and X StoppedBy Y ]

[ Note that, when this unit is normally started from a timer unit file,
"PartOf" interferes with the order in which this unit is restarted, such that
the listed units will force this unit to start *immediately* when any listed
unit is restarted, whether or not this unit would otherwise have been started
during an *initial* start of the units listed here.  The timer function is lost. ]

WantedBy	The primary result is that the current unit will be started when the
listed unit is started.
	[ X StartedBy Y ]

RequiredBy	The primary result is that the current unit will be started when the
listed unit is started.
	[ X StartedBy Y and X Stops Y ]


	Reverse Stop

PartOf		When systemd stops ... the units listed here, the action is propagated
to this unit.
	[ X OnlyReStartedBy Y and X StoppedBy Y ]

Requires	If one of the other units gets deactivated or its activation fails,
this unit will be deactivated.
	[ X Starts Y and X StoppedBy Y ]

Requisite	Similar to Requires=. [ If one of the other units gets deactivated or
its activation fails, this unit will be deactivated. ]  However, if the units
listed here are not started already, they will not be started and the
transaction will fail immediately.  [ Not currently working as documented. ]
	[ X StoppedBy Y ]

BindsTo		declares that this unit is stopped when any of the units listed
suddenly disappears.
	[ X Starts Y and X StopppedBy Disappearing Y ]

Conflicts	If a unit has a Conflicts= setting on another unit, starting the
former will stop the latter and vice versa.
	[ X Stops Y and X StoppedBy Starting Y ]


	Forward Stop

RequiredBy	See the description of ... Requires= in the [Unit] section for
details.  [ but with roles reversed ] [ If this unit gets deactivated or its
activation fails, the other units will be deactivated. ]
	[ X StartedBy Y and X Stops Y ]

Conflicts	If a unit has a Conflicts= setting on another unit, starting the
former will stop the latter ...
	[ X Stops Y and X StoppedBy Starting Y ]


For instance, a proposed simple Forward Stop:

Stops	If this unit gets deactivated, the units listed here will be deactivated
as well.
	[ X Stops Y ]
[ Accepts a space-separated list of unit names.  Note that a "Stops" dependency
does not influence the order in which services are started, stopped, or
restarted.  This has to be configured independently with the After= or Before=
options.  When systemd restarts this unit, a "Stops" dependency will not
interfere with the stop and start ordering of the listed units.]

"Stops=" becomes important when working with a *related group* of unit files.
Currently, systemd restricts passing unit names with "enable" template files,
allowing only two parameters to be passed, to resolve template unit names.
Either unit A declares "A Stops B", or unit B declares "B Stopped By A".  For
instance, "PartOf" can be used as a kind of "reverse reverse" stop, but then the
unit file must "know" the name of the unit which is to initiate the stopping of
"this" unit, which would imply possibly undesired customization of this unit
file.  It is at times preferable to write template unit file A with knowledge
about the name of template unit B, instead of writing unit file B with knowledge
about the name of template unit A.  In that case, RequiredBy can be used.

Other than this, of course, PartOf and RequiredBy are not "simple", in the sense
that they impose additional functions, other than just "B Stops A" or "A Stops
B".  It is often easier to "think" in terms of simple functions.


James



More information about the systemd-devel mailing list