Reload the service manager's units before restarting a service whose files changed, and start the guard before the network as the controller declares it (hq ADR 0100)

This commit is contained in:
2026-09-22 17:38:17 +02:00
parent 3e0e6e6b7e
commit 6eabed63eb
4 changed files with 37 additions and 1 deletions
+16
View File
@@ -688,11 +688,27 @@ func reflected(r *declaration.Service, changed map[string]bool) []string {
return restartedBy(r.RestartOn, changed)
}
// unitReloader is a service manager that caches unit files and must be told to read them again.
type unitReloader interface {
ReloadUnits(ctx context.Context, run system.Runner) error
}
func applyService(ctx context.Context, sys system.System, r *declaration.Service, run Runner,
changed map[string]bool) (Outcome, error) {
out := begin(r)
var changes []string
// A file the service reflects changed, and it may be the unit's own file or a drop-in: the
// service manager reads those again only when told to, and a restart without it runs the unit
// it had already loaded.
if reflects(r, changed) {
if u, ok := sys.(unitReloader); ok {
if err := u.ReloadUnits(ctx, run); err != nil {
return out, fmt.Errorf("reloading the service manager's units for %s: %w", r.Unit, err)
}
}
}
// Boot first. A unit asked to be running and enabled should survive this apply failing
// half way in the more useful direction: enabled-and-stopped comes back at the next boot,
// where running-and-disabled does not.
+12
View File
@@ -1114,6 +1114,18 @@ func TestAServiceIsRestartedWhenWhatItReflectsChanges(t *testing.T) {
if !stopped || !started {
t.Errorf("the file changed and the service was not restarted; commands were %v", commands)
}
reloaded, stop := -1, -1
for i, c := range commands {
if strings.Contains(c, "daemon-reload") && reloaded < 0 {
reloaded = i
}
if strings.Contains(c, "stop thing.service") && stop < 0 {
stop = i
}
}
if reloaded < 0 || reloaded > stop {
t.Errorf("the service was restarted without the unit files being read again first; commands were %v", commands)
}
}
}