Skip to content

Commit 4356701

Browse files
committed
Add <~cond> condition modifier for tightly coupled services
Conditions in Finit are dependencies: if A is asserted, service B is allowed to run. When A goes through FLUX (e.g., upstream reloads), dependents are PAUSED and then simply resumed when the condition is reasserted -- this is the correct behavior for barrier-style deps like <pid/syslogd>. However, some setups have tightly coupled services where dependents must be reloaded/restarted when an upstream service reloads, not just resumed. E.g., the FRR routing stack on Infix OS: netd <pid/mgmtd> ← zebra <!pid/netd> ← {staticd,ripd} <!pid/zebra> When netd reloads (SIGHUP), zebra and its dependents must be restarted to pick up the new configuration. The new '~' condition prefix marks a dependency as flux-sensitive: service <!~pid/netd> name:zebra ... When the upstream condition goes FLUX and returns to ON, the dependent is reloaded (SIGHUP) or restarted (noreload '!') instead of merely resumed. Transitivity follows naturally through the condition chain. Closes #416 Closes #476 Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
1 parent 4bb2c33 commit 4356701

6 files changed

Lines changed: 78 additions & 12 deletions

File tree

doc/conditions.md

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ specified separated by comma. Multiple conditions are logically AND'ed
1414
during evaluation, i.e. all conditions must be satisfied in order for a
1515
service to run.
1616

17-
A special syntax, using a leading `!` in run/task/service conditions,
18-
denote if a:
17+
Two special prefixes can be used inside the angle brackets:
1918

20-
- service does not support `SIGHUP`
21-
- run/task should not block runlevel changes (i.e., bootstrap)
19+
- `!` -- service does not support `SIGHUP` (noreload), or run/task
20+
should not block runlevel changes (i.e., bootstrap)
21+
- `~` -- propagate reload from this dependency, see below
2222

2323
Finit guarantees by default that all run/tasks run (at least) once
2424
per runlevel. For most tasks this is a good default, for example
@@ -53,6 +53,30 @@ moving to the next runlevel after bootstrap, so we set `<!>`:
5353
task [S0123456789] <!sys/pwr/fail> name:pwrfail initctl poweroff -- Power failure, shutting down
5454

5555

56+
Propagating Reload in Dependencies
57+
-----------------------------------
58+
59+
By default, when a service reloads during `initctl reload`, dependent
60+
services are paused (`SIGSTOP`) and simply resumed (`SIGCONT`) when the
61+
condition is reasserted. This is correct for barrier-style dependencies
62+
like `<pid/syslogd>`, where dependents just need syslogd running and do
63+
not care if it reloads its config.
64+
65+
For services that need to react when their upstream reloads, the `~`
66+
prefix propagates the reload from the dependency:
67+
68+
service <pid/svc_a> name:svc_b /sbin/svc_b -- Needs A (barrier)
69+
service <!~pid/svc_b> name:svc_c /sbin/svc_c -- Propagate reload from B
70+
71+
Here, `<~pid/svc_b>` means: propagate a reload of `svc_b` to `svc_c`.
72+
When `svc_b` reloads, `svc_c` will be restarted (because of `!`,
73+
noreload) instead of merely resumed. If `svc_c` supported `SIGHUP`
74+
(no `!` prefix), it would be sent `SIGHUP` instead.
75+
76+
This is similar to systemd's `PropagatesReloadTo=` directive, but
77+
declared on the consumer side rather than the provider side.
78+
79+
5680
Triggering
5781
----------
5882

@@ -281,6 +305,11 @@ This STOP/CONT handling minimizes the number of unnecessary service
281305
restarts that would otherwise occur because a depending service was sent
282306
`SIGHUP` for example.
283307

308+
Services with the `~` prefix are an exception to this rule: when their
309+
conditions return to `on` after being in `flux`, the reload is propagated
310+
-- the service is reloaded (SIGHUP) or restarted (noreload `!`) instead
311+
of simply being resumed.
312+
284313
Therefore, any plugin that supplies Finit with conditions must ensure
285314
that their state is updated after each reconfiguration. This can be
286315
done by binding to the `HOOK_SVC_RECONF` hook. For an example of how

doc/config/services.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ waits for another service, `zebra`, to have created its PID file in
4747
*all* files in `/var/run`, for each file named `*.pid`, or `*/pid`,
4848
Finit opens it and find the matching `NAME:ID` using the PID.
4949

50+
The condition can be prefixed with `!` and/or `~`:
51+
52+
- `<!pid/zebra>` -- `ospfd` does not support `SIGHUP` (noreload)
53+
- `<~pid/zebra>` -- propagate reload from `zebra` to `ospfd`
54+
- `<!~pid/zebra>` -- both: noreload and propagate reload
55+
56+
For details, see the [Finit Conditions](../conditions.md) document.
57+
5058
Some services do not maintain a PID file and rather than patching each
5159
application Finit provides a workaround. A `pid` keyword can be set
5260
to have Finit automatically create (when starting) and later remove

src/conf.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,8 +745,21 @@ void conf_parse_cond(svc_t *svc, char *cond)
745745
return;
746746
}
747747

748+
/*
749+
* The '~' prefix means a reload of the upstream service is
750+
* propagated to this service -- it will be reloaded (SIGHUP)
751+
* or restarted (noreload), not just paused and resumed.
752+
* Syntax: <!~pid/foo,~pid/bar> or <~pid/foo>
753+
*
754+
* Strip '~' from each condition and set the service-level
755+
* flag. This allows '~' on any condition in the list.
756+
*/
748757
svc->cond[0] = 0;
749758
for (i = 0, c = strtok(ptr, ","); c; c = strtok(NULL, ","), i++) {
759+
if (c[0] == '~') {
760+
c++;
761+
svc->flux_reload = 1;
762+
}
750763
devmon_add_cond(c);
751764
if (i)
752765
strlcat(svc->cond, ",", sizeof(svc->cond));

src/service.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3146,6 +3146,20 @@ int service_step(svc_t *svc)
31463146
switch (cond) {
31473147
case COND_ON:
31483148
kill(svc->pid, SIGCONT);
3149+
3150+
/*
3151+
* Propagate reload (~): upstream reloaded, so
3152+
* we must also reload/restart, not just resume.
3153+
*/
3154+
if (svc->flux_reload && !svc_is_changed(svc)) {
3155+
svc_set_state(svc, SVC_RUNNING_STATE);
3156+
if (svc_is_noreload(svc))
3157+
service_stop(svc);
3158+
else
3159+
service_reload(svc);
3160+
break;
3161+
}
3162+
31493163
svc_set_state(svc, SVC_RUNNING_STATE);
31503164
/* Reassert condition if we go from waiting and no change */
31513165
if (!svc_is_changed(svc)) {

src/svc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ typedef struct svc {
146146
int starting; /* ... waiting for pidfile to be re-asserted */
147147
int runlevels;
148148
int sighup; /* This service supports SIGHUP :) */
149+
int flux_reload; /* Propagate reload from dependency, '~' prefix */
149150
int forking; /* This is a service/sysv daemon that forks, wait for it ... */
150151
svc_block_t block; /* Reason that this service is currently stopped */
151152
char cond[MAX_COND_LEN];

test/dep-chain-reload.sh

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,18 @@
44
# Four services in a chain: A → B → C → D, all using notify:pid.
55
# B and C are placed in separate sub-config files so we can use
66
# 'initctl touch' on them independently. B supports SIGHUP (reload),
7-
# while C and D use the '!' condition prefix (noreload), matching
8-
# a common pattern in real-world setups (e.g., FRR daemons on Infix).
7+
# while C and D use '!' (noreload) and '~' (propagate reload),
8+
# matching a common pattern in real-world setups (e.g., FRR routing
9+
# daemons on Infix OS).
910
#
10-
# Chain: A ← B <pid/A> ← C <!pid/B> ← D <!pid/C>
11+
# Chain: A ← B <pid/A> ← C <!~pid/B> ← D <!~pid/C>
1112
#
1213
# Test 1 - touch C + reload (the "Infix" scenario):
1314
# C is in the middle of the chain. After 'initctl touch svc_c.conf'
1415
# + 'initctl reload':
1516
# - A and B should be unaffected (same PID)
1617
# - C is stopped/started (config was touched, noreload)
17-
# - D must be restarted (reverse dep of C)
18+
# - D must be restarted (reload propagated from C)
1819
#
1920
# Test 2 - crash (kill -9):
2021
# When B is killed with SIGKILL the crash path (RUNNING → HALTED)
@@ -26,7 +27,7 @@
2627
# svc_b.conf' + 'initctl reload':
2728
# - A should be unaffected (same PID)
2829
# - B is SIGHUP'd (same PID, config was touched)
29-
# - C and D must be restarted (transitive reverse deps)
30+
# - C and D must be restarted (reload propagated, '~' prefix)
3031

3132
set -eu
3233

@@ -47,13 +48,13 @@ test_setup()
4748
{
4849
run "cat >> $FINIT_CONF" <<EOF
4950
service log:stdout notify:pid name:svc_a serv -np -i svc_a -- Chain root
50-
service log:stdout notify:pid <!pid/svc_c> name:svc_d serv -np -i svc_d -- Needs C
51+
service log:stdout notify:pid <!~pid/svc_c> name:svc_d serv -np -i svc_d -- Needs C
5152
EOF
5253
run "cat >> $FINIT_RCSD/svc_b.conf" <<EOF
53-
service log:stdout notify:pid <pid/svc_a> name:svc_b serv -np -i svc_b -- Needs A
54+
service log:stdout notify:pid <pid/svc_a> name:svc_b serv -np -i svc_b -- Needs A
5455
EOF
5556
run "cat >> $FINIT_RCSD/svc_c.conf" <<EOF
56-
service log:stdout notify:pid <!pid/svc_b> name:svc_c serv -np -i svc_c -- Needs B
57+
service log:stdout notify:pid <!~pid/svc_b> name:svc_c serv -np -i svc_c -- Needs B
5758
EOF
5859
}
5960

0 commit comments

Comments
 (0)