@ 261101.7

261026.11 · 23fe4654f12b86897a15d2e2dcc48f5d4c38df31

--restart leaves another binary's servers alone

The package's units and a build from make install both run --restart and share one registry, so each swapped the other's servers onto itself. The package unit could put a dev server back on a package binary days older, and pages showed old code without saying so.

Each server now records its binary in its registry. --restart with no address moves only the servers and persistent trees its own binary started, and names each one it leaves. -f takes them all. A named address is taken whichever binary runs it, so make run still works. make install-deb passes -f, since it removes the make install link. --list and --kill print each tree's binary.

reading declarations

Changes to restart_test.go. All files

restart_test.go

new file mode 100644@@ -0,0 +1,84 @@+package main++import (+	"net/http"+	"os"+	"path/filepath"+	"syscall"+	"testing"+	"time"+)++// A server records its binary, which outlives it. --restart leaves the+// server and the persistent trees of another binary that still exists,+// and takes those of a binary that is gone.+func TestRestartLeavesOtherBinaries(t *testing.T) {+	ep := testEndpoint(t)+	dir := t.TempDir()+	os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a\n"), 0o644)+	base := "http://" + ep.String()+	reg := filepath.Join(os.Getenv("XDG_STATE_HOME"), progName, endpointName(ep))+	record := filepath.Join(reg, binaryFile)+	exe, _ := os.Executable()++	_, served := start(t, config{dir: dir, ep: ep, persist: true})+	if got := binaryOf(ep); got != exe {+		t.Fatalf("binary record %q, want %q", got, exe)+	}+	if trees, _ := liveTrees(ep); len(trees) != 1 || trees[0].binary != exe {+		t.Errorf("listed trees: %+v", trees)+	}+	if got := otherBinary(ep); got != "" {+		t.Errorf("own binary reads as other: %q", got)+	}++	// /bin/sh stands in for the package's binary.+	os.WriteFile(record, []byte("/bin/sh\n"), 0o600)+	if got := otherBinary(ep); got != "/bin/sh" {+		t.Fatalf("other binary %q, want /bin/sh", got)+	}+	if code := restart(config{restart: true}); code != 0 {+		t.Errorf("restart exit %d", code)+	}+	select {+	case <-served:+		t.Fatal("restart stopped the server of another binary")+	case <-time.After(200 * time.Millisecond):+	}+	if status(t, base+dir+"/a.txt?raw") != http.StatusOK {+		t.Error("the other binary's tree went")+	}++	syscall.Kill(os.Getpid(), syscall.SIGHUP)+	select {+	case <-served:+	case <-time.After(5 * time.Second):+		t.Fatal("server did not stop")+	}+	os.WriteFile(record, []byte("/bin/sh\n"), 0o600)+	if code := restart(config{restart: true}); code != 0 {+		t.Errorf("restart exit %d", code)+	}+	if _, ok := readLive(ep, filepath.Join(dir, persistName(ep))); ok {+		t.Error("restart served the persistent trees of another binary")+	}++	os.WriteFile(record, []byte(filepath.Join(t.TempDir(), "gone")+"\n"), 0o600)+	if got := otherBinary(ep); got != "" {+		t.Errorf("a gone binary reads as other: %q", got)+	}+	os.Remove(record)+	if got := otherBinary(ep); got != "" {+		t.Errorf("no record reads as other: %q", got)+	}+}++func TestForceGoesWithRestart(t *testing.T) {+	if _, err := parseArgs([]string{"-f", "."}); err == nil {+		t.Error("-f without --restart parsed")+	}+	c, err := parseArgs([]string{"--restart", "-f"})+	if err != nil || !c.force {+		t.Errorf("--restart -f: %+v, %v", c, err)+	}+}