diff --git a/doc/conditions.md b/doc/conditions.md index 93df4169..aac0eadf 100644 --- a/doc/conditions.md +++ b/doc/conditions.md @@ -14,11 +14,11 @@ specified separated by comma. Multiple conditions are logically AND'ed during evaluation, i.e. all conditions must be satisfied in order for a service to run. -A special syntax, using a leading `!` in run/task/service conditions, -denote if a: +Two special prefixes can be used inside the angle brackets: - - service does not support `SIGHUP` - - run/task should not block runlevel changes (i.e., bootstrap) + - `!` -- service does not support `SIGHUP` (noreload), or run/task + should not block runlevel changes (i.e., bootstrap) + - `~` -- propagate reload from this dependency, see below Finit guarantees by default that all run/tasks run (at least) once 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 ``: task [S0123456789] name:pwrfail initctl poweroff -- Power failure, shutting down +Propagating Reload in Dependencies +----------------------------------- + +By default, when a service reloads during `initctl reload`, dependent +services are paused (`SIGSTOP`) and simply resumed (`SIGCONT`) when the +condition is reasserted. This is correct for barrier-style dependencies +like ``, where dependents just need syslogd running and do +not care if it reloads its config. + +For services that need to react when their upstream reloads, the `~` +prefix propagates the reload from the dependency: + + service name:svc_b /sbin/svc_b -- Needs A (barrier) + service name:svc_c /sbin/svc_c -- Propagate reload from B + +Here, `<~pid/svc_b>` means: propagate a reload of `svc_b` to `svc_c`. +When `svc_b` reloads, `svc_c` will be restarted (because of `!`, +noreload) instead of merely resumed. If `svc_c` supported `SIGHUP` +(no `!` prefix), it would be sent `SIGHUP` instead. + +This is similar to systemd's `PropagatesReloadTo=` directive, but +declared on the consumer side rather than the provider side. + + Triggering ---------- @@ -281,6 +305,11 @@ This STOP/CONT handling minimizes the number of unnecessary service restarts that would otherwise occur because a depending service was sent `SIGHUP` for example. +Services with the `~` prefix are an exception to this rule: when their +conditions return to `on` after being in `flux`, the reload is propagated +-- the service is reloaded (SIGHUP) or restarted (noreload `!`) instead +of simply being resumed. + Therefore, any plugin that supplies Finit with conditions must ensure that their state is updated after each reconfiguration. This can be done by binding to the `HOOK_SVC_RECONF` hook. For an example of how diff --git a/doc/config/services.md b/doc/config/services.md index 38ba0be4..a315b372 100644 --- a/doc/config/services.md +++ b/doc/config/services.md @@ -47,6 +47,14 @@ waits for another service, `zebra`, to have created its PID file in *all* files in `/var/run`, for each file named `*.pid`, or `*/pid`, Finit opens it and find the matching `NAME:ID` using the PID. +The condition can be prefixed with `!` and/or `~`: + + - `` -- `ospfd` does not support `SIGHUP` (noreload) + - `<~pid/zebra>` -- propagate reload from `zebra` to `ospfd` + - `` -- both: noreload and propagate reload + +For details, see the [Finit Conditions](../conditions.md) document. + Some services do not maintain a PID file and rather than patching each application Finit provides a workaround. A `pid` keyword can be set to have Finit automatically create (when starting) and later remove diff --git a/src/conf.c b/src/conf.c index b4393e65..d0abb61d 100644 --- a/src/conf.c +++ b/src/conf.c @@ -745,8 +745,21 @@ void conf_parse_cond(svc_t *svc, char *cond) return; } + /* + * The '~' prefix means a reload of the upstream service is + * propagated to this service -- it will be reloaded (SIGHUP) + * or restarted (noreload), not just paused and resumed. + * Syntax: or <~pid/foo> + * + * Strip '~' from each condition and set the service-level + * flag. This allows '~' on any condition in the list. + */ svc->cond[0] = 0; for (i = 0, c = strtok(ptr, ","); c; c = strtok(NULL, ","), i++) { + if (c[0] == '~') { + c++; + svc->flux_reload = 1; + } devmon_add_cond(c); if (i) strlcat(svc->cond, ",", sizeof(svc->cond)); diff --git a/src/service.c b/src/service.c index eefae25b..09c9823f 100644 --- a/src/service.c +++ b/src/service.c @@ -3147,6 +3147,19 @@ restart: case COND_ON: kill(svc->pid, SIGCONT); svc_set_state(svc, SVC_RUNNING_STATE); + + /* + * Propagate reload (~): upstream reloaded, so + * we must also reload/restart, not just resume. + */ + if (svc->flux_reload && !svc_is_changed(svc)) { + if (svc_is_noreload(svc)) + service_stop(svc); + else + service_reload(svc); + break; + } + /* Reassert condition if we go from waiting and no change */ if (!svc_is_changed(svc)) { if (svc->notify == SVC_NOTIFY_PID) { diff --git a/src/svc.h b/src/svc.h index d433a515..f21a8ee6 100644 --- a/src/svc.h +++ b/src/svc.h @@ -146,6 +146,7 @@ typedef struct svc { int starting; /* ... waiting for pidfile to be re-asserted */ int runlevels; int sighup; /* This service supports SIGHUP :) */ + int flux_reload; /* Propagate reload from dependency, '~' prefix */ int forking; /* This is a service/sysv daemon that forks, wait for it ... */ svc_block_t block; /* Reason that this service is currently stopped */ char cond[MAX_COND_LEN]; diff --git a/test/dep-chain-reload.sh b/test/dep-chain-reload.sh index b5cfea49..dd67ac19 100755 --- a/test/dep-chain-reload.sh +++ b/test/dep-chain-reload.sh @@ -4,17 +4,18 @@ # Four services in a chain: A → B → C → D, all using notify:pid. # B and C are placed in separate sub-config files so we can use # 'initctl touch' on them independently. B supports SIGHUP (reload), -# while C and D use the '!' condition prefix (noreload), matching -# a common pattern in real-world setups (e.g., FRR daemons on Infix). +# while C and D use '!' (noreload) and '~' (propagate reload), +# matching a common pattern in real-world setups (e.g., FRR routing +# daemons on Infix OS). # -# Chain: A ← B ← C ← D +# Chain: A ← B ← C ← D # # Test 1 - touch C + reload (the "Infix" scenario): # C is in the middle of the chain. After 'initctl touch svc_c.conf' # + 'initctl reload': # - A and B should be unaffected (same PID) # - C is stopped/started (config was touched, noreload) -# - D must be restarted (reverse dep of C) +# - D must be restarted (reload propagated from C) # # Test 2 - crash (kill -9): # When B is killed with SIGKILL the crash path (RUNNING → HALTED) @@ -26,7 +27,7 @@ # svc_b.conf' + 'initctl reload': # - A should be unaffected (same PID) # - B is SIGHUP'd (same PID, config was touched) -# - C and D must be restarted (transitive reverse deps) +# - C and D must be restarted (reload propagated, '~' prefix) set -eu @@ -47,13 +48,13 @@ test_setup() { run "cat >> $FINIT_CONF" < name:svc_d serv -np -i svc_d -- Needs C +service log:stdout notify:pid name:svc_d serv -np -i svc_d -- Needs C EOF run "cat >> $FINIT_RCSD/svc_b.conf" < name:svc_b serv -np -i svc_b -- Needs A +service log:stdout notify:pid name:svc_b serv -np -i svc_b -- Needs A EOF run "cat >> $FINIT_RCSD/svc_c.conf" < name:svc_c serv -np -i svc_c -- Needs B +service log:stdout notify:pid name:svc_c serv -np -i svc_c -- Needs B EOF }