Skip to content

Commit 0bc488c

Browse files
committed
core: implement Uphold= dependency type
This is like a really strong version of Wants=, that keeps starting the specified unit if it is ever found inactive. This is an alternative to Restart= inside a unit, acknowledging the fact that whether to keep restarting the unit is sometimes not a property of the unit itself but the state of the system. This implements a part of what systemd#4263 requests. i.e. there's no distinction between "always" and "opportunistic". We just dumbly implement "always" and become active whenever we see no job queued for an inactive unit that is supposed to be upheld.
1 parent 294446d commit 0bc488c

File tree

12 files changed

+180
-7
lines changed

12 files changed

+180
-7
lines changed

man/systemd.unit.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,24 @@
708708
</listitem>
709709
</varlistentry>
710710

711+
<varlistentry>
712+
<term><varname>Upholds=</varname></term>
713+
714+
<listitem><para>Configures dependencies similar to <varname>Wants=</varname>, but as long a this unit
715+
is up, all units listed in <varname>Upholds=</varname> are started whenever found to be inactive or
716+
failed, and no job is queued for them. While a <varname>Wants=</varname> dependency on another unit
717+
has a one-time effect when this units started, a <varname>Upholds=</varname> dependency on it has a
718+
continuous effect, constantly restarting the unit if necessary. This is an alternative to the
719+
<varname>Restart=</varname> setting of service units, to ensure they are kept running whatever
720+
happens.</para>
721+
722+
<para>When <varname>Upholds=b.service</varname> is used on <filename>a.service</filename>, this
723+
dependency will show as <varname>UpheldBy=a.service</varname> in the property listing of
724+
<filename>b.service</filename>. The <varname>UpheldBy=</varname> dependency cannot be specified
725+
directly.</para>
726+
</listitem>
727+
</varlistentry>
728+
711729
<varlistentry>
712730
<term><varname>Conflicts=</varname></term>
713731

src/basic/unit-def.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,10 +265,12 @@ static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = {
265265
[UNIT_WANTS] = "Wants",
266266
[UNIT_BINDS_TO] = "BindsTo",
267267
[UNIT_PART_OF] = "PartOf",
268+
[UNIT_UPHOLDS] = "Upholds",
268269
[UNIT_REQUIRED_BY] = "RequiredBy",
269270
[UNIT_REQUISITE_OF] = "RequisiteOf",
270271
[UNIT_WANTED_BY] = "WantedBy",
271272
[UNIT_BOUND_BY] = "BoundBy",
273+
[UNIT_UPHELD_BY] = "UpheldBy",
272274
[UNIT_CONSISTS_OF] = "ConsistsOf",
273275
[UNIT_CONFLICTS] = "Conflicts",
274276
[UNIT_CONFLICTED_BY] = "ConflictedBy",

src/basic/unit-def.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,13 +211,15 @@ typedef enum UnitDependency {
211211
UNIT_WANTS,
212212
UNIT_BINDS_TO,
213213
UNIT_PART_OF,
214+
UNIT_UPHOLDS,
214215

215216
/* Inverse of the above */
216217
UNIT_REQUIRED_BY, /* inverse of 'requires' is 'required_by' */
217218
UNIT_REQUISITE_OF, /* inverse of 'requisite' is 'requisite_of' */
218219
UNIT_WANTED_BY, /* inverse of 'wants' */
219220
UNIT_BOUND_BY, /* inverse of 'binds_to' */
220221
UNIT_CONSISTS_OF, /* inverse of 'part_of' */
222+
UNIT_UPHELD_BY, /* inverse of 'uphold' */
221223

222224
/* Negative dependencies */
223225
UNIT_CONFLICTS, /* inverse of 'conflicts' is 'conflicted_by' */

src/core/dbus-unit.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2304,6 +2304,7 @@ static int bus_unit_set_transient_property(
23042304
UNIT_WANTS,
23052305
UNIT_BINDS_TO,
23062306
UNIT_PART_OF,
2307+
UNIT_UPHOLDS,
23072308
UNIT_CONFLICTS,
23082309
UNIT_BEFORE,
23092310
UNIT_AFTER,

src/core/load-fragment-gperf.gperf.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ Unit.Requisite, config_parse_unit_deps,
261261
Unit.Wants, config_parse_unit_deps, UNIT_WANTS, 0
262262
Unit.BindsTo, config_parse_unit_deps, UNIT_BINDS_TO, 0
263263
Unit.BindTo, config_parse_unit_deps, UNIT_BINDS_TO, 0
264+
Unit.Upholds, config_parse_unit_deps, UNIT_UPHOLDS, 0
264265
Unit.Conflicts, config_parse_unit_deps, UNIT_CONFLICTS, 0
265266
Unit.Before, config_parse_unit_deps, UNIT_BEFORE, 0
266267
Unit.After, config_parse_unit_deps, UNIT_AFTER, 0

src/core/manager.c

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1295,7 +1295,7 @@ static unsigned manager_dispatch_stop_when_unneeded_queue(Manager *m) {
12951295
/* If stopping a unit fails continuously we might enter a stop loop here, hence stop acting on the
12961296
* service being unnecessary after a while. */
12971297

1298-
if (!ratelimit_below(&u->auto_stop_ratelimit)) {
1298+
if (!ratelimit_below(&u->auto_start_stop_ratelimit)) {
12991299
log_unit_warning(u, "Unit not needed anymore, but not stopping since we tried this too often recently.");
13001300
continue;
13011301
}
@@ -1309,6 +1309,44 @@ static unsigned manager_dispatch_stop_when_unneeded_queue(Manager *m) {
13091309
return n;
13101310
}
13111311

1312+
static unsigned manager_dispatch_start_when_upheld_queue(Manager *m) {
1313+
unsigned n = 0;
1314+
Unit *u;
1315+
int r;
1316+
1317+
assert(m);
1318+
1319+
while ((u = m->start_when_upheld_queue)) {
1320+
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1321+
Unit *culprit = NULL;
1322+
1323+
assert(u->in_start_when_upheld_queue);
1324+
LIST_REMOVE(start_when_upheld_queue, m->start_when_upheld_queue, u);
1325+
u->in_start_when_upheld_queue = false;
1326+
1327+
n++;
1328+
1329+
if (!unit_is_upheld_by_active(u, &culprit))
1330+
continue;
1331+
1332+
log_unit_debug(u, "Unit is started because upheld by active unit %s.", culprit->id);
1333+
1334+
/* If stopping a unit fails continuously we might enter a stop loop here, hence stop acting on the
1335+
* service being unnecessary after a while. */
1336+
1337+
if (!ratelimit_below(&u->auto_start_stop_ratelimit)) {
1338+
log_unit_warning(u, "Unit needs to be started because active unit %s upholds it, but not starting since we tried this too often recently.", culprit->id);
1339+
continue;
1340+
}
1341+
1342+
r = manager_add_job(u->manager, JOB_START, u, JOB_FAIL, NULL, &error, NULL);
1343+
if (r < 0)
1344+
log_unit_warning_errno(u, r, "Failed to enqueue start job, ignoring: %s", bus_error_message(&error, r));
1345+
}
1346+
1347+
return n;
1348+
}
1349+
13121350
static void manager_clear_jobs_and_units(Manager *m) {
13131351
Unit *u;
13141352

@@ -1327,6 +1365,7 @@ static void manager_clear_jobs_and_units(Manager *m) {
13271365
assert(!m->gc_unit_queue);
13281366
assert(!m->gc_job_queue);
13291367
assert(!m->stop_when_unneeded_queue);
1368+
assert(!m->start_when_upheld_queue);
13301369

13311370
assert(hashmap_isempty(m->jobs));
13321371
assert(hashmap_isempty(m->units));
@@ -2954,6 +2993,9 @@ int manager_loop(Manager *m) {
29542993
if (manager_dispatch_cgroup_realize_queue(m) > 0)
29552994
continue;
29562995

2996+
if (manager_dispatch_start_when_upheld_queue(m) > 0)
2997+
continue;
2998+
29572999
if (manager_dispatch_stop_when_unneeded_queue(m) > 0)
29583000
continue;
29593001

src/core/manager.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ struct Manager {
187187
/* Units that might be subject to StopWhenUnneeded= clean-up */
188188
LIST_HEAD(Unit, stop_when_unneeded_queue);
189189

190+
/* Units which are upheld by another other which we might need to act on */
191+
LIST_HEAD(Unit, start_when_upheld_queue);
192+
190193
sd_event *event;
191194

192195
/* This maps PIDs we care about to units that are interested in. We allow multiple units to he interested in

src/core/unit-dependency-atom.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ static const UnitDependencyAtom atom_map[_UNIT_DEPENDENCY_MAX] = {
3535

3636
[UNIT_PART_OF] = UNIT_ATOM_ADD_DEFAULT_TARGET_DEPENDENCY_QUEUE,
3737

38+
[UNIT_UPHOLDS] = UNIT_ATOM_PULL_IN_START_IGNORED |
39+
UNIT_ATOM_RETROACTIVE_START_REPLACE |
40+
UNIT_ATOM_ADD_START_WHEN_UPHELD_QUEUE |
41+
UNIT_ATOM_ADD_STOP_WHEN_UNNEEDED_QUEUE |
42+
UNIT_ATOM_ADD_DEFAULT_TARGET_DEPENDENCY_QUEUE,
43+
3844
[UNIT_REQUIRED_BY] = UNIT_ATOM_PROPAGATE_STOP |
3945
UNIT_ATOM_PROPAGATE_RESTART |
4046
UNIT_ATOM_PROPAGATE_START_FAILURE |
@@ -58,6 +64,10 @@ static const UnitDependencyAtom atom_map[_UNIT_DEPENDENCY_MAX] = {
5864
UNIT_ATOM_PINS_STOP_WHEN_UNNEEDED |
5965
UNIT_ATOM_DEFAULT_TARGET_DEPENDENCIES,
6066

67+
[UNIT_UPHELD_BY] = UNIT_ATOM_START_STEADILY |
68+
UNIT_ATOM_DEFAULT_TARGET_DEPENDENCIES |
69+
UNIT_ATOM_PINS_STOP_WHEN_UNNEEDED,
70+
6171
[UNIT_CONSISTS_OF] = UNIT_ATOM_PROPAGATE_STOP |
6272
UNIT_ATOM_PROPAGATE_RESTART,
6373

@@ -140,6 +150,14 @@ UnitDependency unit_dependency_from_unique_atom(UnitDependencyAtom atom) {
140150
case UNIT_ATOM_CANNOT_BE_ACTIVE_WITHOUT:
141151
return UNIT_BINDS_TO;
142152

153+
case UNIT_ATOM_PULL_IN_START_IGNORED |
154+
UNIT_ATOM_RETROACTIVE_START_REPLACE |
155+
UNIT_ATOM_ADD_START_WHEN_UPHELD_QUEUE |
156+
UNIT_ATOM_ADD_STOP_WHEN_UNNEEDED_QUEUE |
157+
UNIT_ATOM_ADD_DEFAULT_TARGET_DEPENDENCY_QUEUE:
158+
case UNIT_ATOM_ADD_START_WHEN_UPHELD_QUEUE:
159+
return UNIT_UPHOLDS;
160+
143161
case UNIT_ATOM_PROPAGATE_STOP |
144162
UNIT_ATOM_PROPAGATE_RESTART |
145163
UNIT_ATOM_PROPAGATE_START_FAILURE |
@@ -157,6 +175,12 @@ UnitDependency unit_dependency_from_unique_atom(UnitDependencyAtom atom) {
157175
UNIT_ATOM_DEFAULT_TARGET_DEPENDENCIES:
158176
return UNIT_BOUND_BY;
159177

178+
case UNIT_ATOM_START_STEADILY |
179+
UNIT_ATOM_DEFAULT_TARGET_DEPENDENCIES |
180+
UNIT_ATOM_PINS_STOP_WHEN_UNNEEDED:
181+
case UNIT_ATOM_START_STEADILY:
182+
return UNIT_UPHELD_BY;
183+
160184
case UNIT_ATOM_PULL_IN_STOP |
161185
UNIT_ATOM_RETROACTIVE_STOP_ON_START:
162186
case UNIT_ATOM_PULL_IN_STOP:

src/core/unit-dependency-atom.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ typedef enum UnitDependencyAtom {
3232
/* Stop our unit if the other unit happens to inactive */
3333
UNIT_ATOM_CANNOT_BE_ACTIVE_WITHOUT = UINT64_C(1) << 7,
3434

35+
/* Start this unit whenever we find it inactive and the other unit active */
36+
UNIT_ATOM_START_STEADILY = UINT64_C(1) << 9,
37+
/* Whenever our unit becomes active, add other unit to start_when_upheld_queue */
38+
UNIT_ATOM_ADD_START_WHEN_UPHELD_QUEUE = UINT64_C(1) << 10,
39+
3540
/* If our unit unexpectedly becomes active, retroactively start the other unit too, in "replace" job
3641
* mode */
3742
UNIT_ATOM_RETROACTIVE_START_REPLACE = UINT64_C(1) << 11,

src/core/unit.c

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ Unit* unit_new(Manager *m, size_t size) {
122122
u->last_section_private = -1;
123123

124124
u->start_ratelimit = (RateLimit) { m->default_start_limit_interval, m->default_start_limit_burst };
125-
u->auto_stop_ratelimit = (RateLimit) { 10 * USEC_PER_SEC, 16 };
125+
u->auto_start_stop_ratelimit = (RateLimit) { 10 * USEC_PER_SEC, 16 };
126126

127127
for (CGroupIOAccountingMetric i = 0; i < _CGROUP_IO_ACCOUNTING_METRIC_MAX; i++)
128128
u->io_accounting_last[i] = UINT64_MAX;
@@ -507,6 +507,22 @@ void unit_submit_to_stop_when_unneeded_queue(Unit *u) {
507507
u->in_stop_when_unneeded_queue = true;
508508
}
509509

510+
void unit_submit_to_start_when_upheld_queue(Unit *u) {
511+
assert(u);
512+
513+
if (u->in_start_when_upheld_queue)
514+
return;
515+
516+
if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(u)))
517+
return;
518+
519+
if (!unit_has_dependency(u, UNIT_ATOM_START_STEADILY, NULL))
520+
return;
521+
522+
LIST_PREPEND(start_when_upheld_queue, u->manager->start_when_upheld_queue, u);
523+
u->in_start_when_upheld_queue = true;
524+
}
525+
510526
static void unit_clear_dependencies(Unit *u) {
511527
assert(u);
512528

@@ -719,6 +735,9 @@ Unit* unit_free(Unit *u) {
719735
if (u->in_stop_when_unneeded_queue)
720736
LIST_REMOVE(stop_when_unneeded_queue, u->manager->stop_when_unneeded_queue, u);
721737

738+
if (u->in_start_when_upheld_queue)
739+
LIST_REMOVE(start_when_upheld_queue, u->manager->start_when_upheld_queue, u);
740+
722741
safe_close(u->ip_accounting_ingress_map_fd);
723742
safe_close(u->ip_accounting_egress_map_fd);
724743

@@ -2011,6 +2030,36 @@ bool unit_is_unneeded(Unit *u) {
20112030
return true;
20122031
}
20132032

2033+
bool unit_is_upheld_by_active(Unit *u, Unit **ret_culprit) {
2034+
Unit *other;
2035+
2036+
assert(u);
2037+
2038+
/* Checks if the unit needs to be started because it currently is not running, but some other unit
2039+
* that is active declared an Uphold= dependencies on it */
2040+
2041+
if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(u)) || u->job) {
2042+
if (ret_culprit)
2043+
*ret_culprit = NULL;
2044+
return false;
2045+
}
2046+
2047+
UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_START_STEADILY) {
2048+
if (other->job)
2049+
continue;
2050+
2051+
if (UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(other))) {
2052+
if (ret_culprit)
2053+
*ret_culprit = other;
2054+
return true;
2055+
}
2056+
}
2057+
2058+
if (ret_culprit)
2059+
*ret_culprit = NULL;
2060+
return false;
2061+
}
2062+
20142063
static void check_unneeded_dependencies(Unit *u) {
20152064
Unit *other;
20162065
assert(u);
@@ -2021,6 +2070,16 @@ static void check_unneeded_dependencies(Unit *u) {
20212070
unit_submit_to_stop_when_unneeded_queue(other);
20222071
}
20232072

2073+
static void check_uphold_dependencies(Unit *u) {
2074+
Unit *other;
2075+
assert(u);
2076+
2077+
/* Add all units this unit depends on to the queue that processes Uphold= behaviour. */
2078+
2079+
UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_ADD_START_WHEN_UPHELD_QUEUE)
2080+
unit_submit_to_start_when_upheld_queue(other);
2081+
}
2082+
20242083
static void unit_check_binds_to(Unit *u) {
20252084
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
20262085
bool stop = false;
@@ -2056,7 +2115,7 @@ static void unit_check_binds_to(Unit *u) {
20562115
/* If stopping a unit fails continuously we might enter a stop
20572116
* loop here, hence stop acting on the service being
20582117
* unnecessary after a while. */
2059-
if (!ratelimit_below(&u->auto_stop_ratelimit)) {
2118+
if (!ratelimit_below(&u->auto_start_stop_ratelimit)) {
20602119
log_unit_warning(u, "Unit is bound to inactive unit %s, but not stopping since we tried this too often recently.", other->id);
20612120
return;
20622121
}
@@ -2595,10 +2654,14 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag
25952654
retroactively_stop_dependencies(u);
25962655
}
25972656

2598-
/* stop unneeded units regardless if going down was expected or not */
2657+
/* Stop unneeded units regardless if going down was expected or not */
25992658
if (UNIT_IS_INACTIVE_OR_FAILED(ns))
26002659
check_unneeded_dependencies(u);
26012660

2661+
/* Start uphold units regardless if going up was expected or not */
2662+
if (UNIT_IS_ACTIVE_OR_RELOADING(ns))
2663+
check_uphold_dependencies(u);
2664+
26022665
if (ns != os && ns == UNIT_FAILED) {
26032666
log_unit_debug(u, "Unit entered failed state.");
26042667

@@ -2634,6 +2697,9 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag
26342697
/* Maybe we finished startup and are now ready for being stopped because unneeded? */
26352698
unit_submit_to_stop_when_unneeded_queue(u);
26362699

2700+
/* Maybe someone wants us to remain up? */
2701+
unit_submit_to_start_when_upheld_queue(u);
2702+
26372703
/* Maybe we finished startup, but something we needed has vanished? Let's die then. (This happens when
26382704
* something BindsTo= to a Type=oneshot unit, as these units go directly from starting to inactive,
26392705
* without ever entering started.) */
@@ -2901,11 +2967,13 @@ int unit_add_dependency(
29012967
[UNIT_REQUISITE] = UNIT_REQUISITE_OF,
29022968
[UNIT_BINDS_TO] = UNIT_BOUND_BY,
29032969
[UNIT_PART_OF] = UNIT_CONSISTS_OF,
2970+
[UNIT_UPHOLDS] = UNIT_UPHELD_BY,
29042971
[UNIT_REQUIRED_BY] = UNIT_REQUIRES,
29052972
[UNIT_REQUISITE_OF] = UNIT_REQUISITE,
29062973
[UNIT_WANTED_BY] = UNIT_WANTS,
29072974
[UNIT_BOUND_BY] = UNIT_BINDS_TO,
29082975
[UNIT_CONSISTS_OF] = UNIT_PART_OF,
2976+
[UNIT_UPHELD_BY] = UNIT_UPHOLDS,
29092977
[UNIT_CONFLICTS] = UNIT_CONFLICTED_BY,
29102978
[UNIT_CONFLICTED_BY] = UNIT_CONFLICTS,
29112979
[UNIT_BEFORE] = UNIT_AFTER,

0 commit comments

Comments
 (0)