Let SIGTERM cancel a running pass and bound nfs-server's clean-up at 10 s, so a hanging command cannot keep the export file past the stop timeout

This commit is contained in:
jochen
2026-10-09 14:43:32 +02:00
parent 87d6a5549c
commit 72a6d062dc
5 changed files with 118 additions and 5 deletions
+29 -4
View File
@@ -56,7 +56,14 @@ func (m *Machine) Pass(ctx context.Context, last *Status) Status {
if _, note := m.Backup(); note != "" {
s.Said = append(s.Said, note)
}
m.setWalk(ctx)
exports, refused, said := m.Judge(c, roster, wants)
m.setWalk(nil)
if ctx.Err() != nil {
// Stopping: write nothing and reload nothing, so Stopped starts at once.
s.Problems = append(s.Problems, "the pass was ended by the process stopping; nothing was written")
return s
}
s.Refused = refused
s.Said = append(s.Said, said...)
@@ -80,7 +87,7 @@ func (m *Machine) Pass(ctx context.Context, last *Status) Status {
}
}
if reload != "" {
if _, err := m.AsRoot("exportfs", "-ra"); err != nil {
if _, err := m.AsRootWithin(ctx, "exportfs", "-ra"); err != nil {
s.ReloadError = err.Error()
} else {
at := m.Now().UTC()
@@ -102,6 +109,10 @@ func (m *Machine) Pass(ctx context.Context, last *Status) Status {
return s
}
// StopTimeout bounds Stopped and the commands it runs: well under the service manager's stop timeout (90 s,
// then SIGKILL), so the export file is gone before the process could be killed (mesh-host #62).
var StopTimeout = 10 * time.Second
// RunExports is the process: a pass now and every Every until the context ends, then Stopped.
func (m *Machine) RunExports(ctx context.Context) error {
last := m.Pass(ctx, nil)
@@ -184,11 +195,25 @@ func (m *Machine) Stopped() {
if _, err := m.Read(ConfigFile); err == nil || !os.IsNotExist(err) {
return
}
ctx, cancel := context.WithTimeout(context.Background(), StopTimeout)
defer cancel()
// The file first: removing it needs no command, so a hanging sudo or exportfs cannot keep it.
if err := m.unexport(); err != nil {
fmt.Fprintln(os.Stderr, "nfs-server:", err)
loud("the module was unassigned and its export file is still there: " + err.Error() +
"; remove " + ExportFile + " and run exportfs -ra as root")
return
}
if _, err := m.AsRoot("exportfs", "-ra"); err != nil {
fmt.Fprintln(os.Stderr, "nfs-server: the export file is gone, and exportfs -ra failed:", err)
fmt.Fprintln(os.Stderr, "nfs-server: unassigned: the export file "+ExportFile+" is removed")
if _, err := m.AsRootWithin(ctx, "exportfs", "-ra"); err != nil {
loud("the module was unassigned and its export file is removed, but the kernel may still export its " +
"shares until exportfs -ra runs as root: " + err.Error())
return
}
fmt.Fprintln(os.Stderr, "nfs-server: unassigned: the kernel let the shares go (exportfs -ra)")
}
// loud says, on the process's log where the service manager's journal keeps it, what an unassignment left
// undone, so a person finds it rather than an export that outlived its module.
func loud(what string) {
fmt.Fprintln(os.Stderr, "nfs-server: UNASSIGNED, NOT CLEAN: "+what)
}