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 _______________________________________________ systemd-devel mailing list systemd-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/systemd-devel