From 2f732984a624ab8762c8eb909fcaf926a7895d3c Mon Sep 17 00:00:00 2001 From: mrigangha Date: Thu, 7 May 2026 12:37:38 +0000 Subject: [PATCH] Fixed the format --- pkg/pid/pidfile.go | 12 +-- pkg/pid/pidfile_test.go | 167 ++++++++++++++++++++-------------------- 2 files changed, 90 insertions(+), 89 deletions(-) diff --git a/pkg/pid/pidfile.go b/pkg/pid/pidfile.go index 2b78c0338..8dca41c49 100644 --- a/pkg/pid/pidfile.go +++ b/pkg/pid/pidfile.go @@ -6,11 +6,13 @@ import ( "encoding/json" "errors" "fmt" + "net" "net/http" "os" "path/filepath" "sync" "time" + "strconv" "github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/logger" @@ -50,7 +52,7 @@ func generateToken() string { // then verifies the reported PID matches expectedPID to confirm the process // is actually a picoclaw gateway and not a foreign service on the same port. func isGatewayAlive(host string, port int, expectedPID int) bool { - url := fmt.Sprintf("http://%s:%d/health", host, port) + url := "http://" + net.JoinHostPort(host, strconv.Itoa(port)) + "/health" client := &http.Client{Timeout: 2 * time.Second} resp, err := client.Get(url) if err != nil { @@ -58,18 +60,18 @@ func isGatewayAlive(host string, port int, expectedPID int) bool { return false } defer resp.Body.Close() - + if resp.StatusCode != http.StatusOK { return false } - + var body struct { PID int `json:"pid"` } if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { return false } - + // Only treat as alive if the reported PID matches the PID file. // This prevents an unrelated service on the same port from being // mistaken for a running gateway. @@ -94,7 +96,7 @@ func WritePidFile(homePath, host string, port int) (*PidFileData, error) { // PID file on a shared volume, the host's PID 1 (init) would // pass the isProcessRunning check, blocking new gateway starts. // Treat recorded PID 1 as always stale. - if data.PID != 1 && isProcessRunning(data.PID) && isGatewayAlive(data.Host, data.Port, data.PID){ + if data.PID != 1 && isProcessRunning(data.PID) && isGatewayAlive(data.Host, data.Port, data.PID) { return nil, fmt.Errorf("gateway is already running (PID: %d, version: %s)", data.PID, data.Version) } logger.Warnf("not running (PID: %d) so will remove the pid file: %s", data.PID, pidPath) diff --git a/pkg/pid/pidfile_test.go b/pkg/pid/pidfile_test.go index c8fac1aa5..2cb602943 100644 --- a/pkg/pid/pidfile_test.go +++ b/pkg/pid/pidfile_test.go @@ -1,14 +1,14 @@ package pid import ( - "encoding/json" - "net" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "strconv" - "testing" + "encoding/json" + "net" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strconv" + "testing" ) // tmpDir returns a clean temporary directory for a test. @@ -55,98 +55,97 @@ func TestPidFilePath(t *testing.T) { } } -// verifies that an unrelated service on the recorded port is not mistaken for the gateway. +// verifies that an unrelated service on the recorded port is not mistaken for the gateway. func TestWritePidFileHealthPIDMismatch(t *testing.T) { - mux := http.NewServeMux() - mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - // Return PID 99999 — does not match the PID file entry - json.NewEncoder(w).Encode(map[string]any{"pid": 99999, "status": "ok"}) - }) - srv := httptest.NewServer(mux) - defer srv.Close() + mux := http.NewServeMux() + mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + // Return PID 99999 — does not match the PID file entry + json.NewEncoder(w).Encode(map[string]any{"pid": 99999, "status": "ok"}) + }) + srv := httptest.NewServer(mux) + defer srv.Close() - host, portStr, _ := net.SplitHostPort(srv.Listener.Addr().String()) - port, _ := strconv.Atoi(portStr) + host, portStr, _ := net.SplitHostPort(srv.Listener.Addr().String()) + port, _ := strconv.Atoi(portStr) - dir := tmpDir(t) - foreign := PidFileData{ - PID: os.Getpid(), // real running PID so it reaches isGatewayAlive - Token: "deadbeef12345678deadbeef12345678", - Port: port, - Host: host, - } - raw, _ := json.MarshalIndent(foreign, "", " ") - os.WriteFile(filepath.Join(dir, pidFileName), raw, 0o600) + dir := tmpDir(t) + foreign := PidFileData{ + PID: os.Getpid(), // real running PID so it reaches isGatewayAlive + Token: "deadbeef12345678deadbeef12345678", + Port: port, + Host: host, + } + raw, _ := json.MarshalIndent(foreign, "", " ") + os.WriteFile(filepath.Join(dir, pidFileName), raw, 0o600) - // Should succeed — health PID (99999) != PID file PID (os.Getpid()) = not our gateway - data, err := WritePidFile(dir, "127.0.0.1", 18790) - if err != nil { - t.Fatalf("WritePidFile should treat health PID mismatch as stale, got error: %v", err) - } - if data.PID != os.Getpid() { - t.Errorf("PID = %d, want %d", data.PID, os.Getpid()) - } + // Should succeed — health PID (99999) != PID file PID (os.Getpid()) = not our gateway + data, err := WritePidFile(dir, "127.0.0.1", 18790) + if err != nil { + t.Fatalf("WritePidFile should treat health PID mismatch as stale, got error: %v", err) + } + if data.PID != os.Getpid() { + t.Errorf("PID = %d, want %d", data.PID, os.Getpid()) + } } -// verifies that isGatewayAlive uses the host from the PID file instead of hardcoding localhost. +// verifies that isGatewayAlive uses the host from the PID file instead of hardcoding localhost. func TestWritePidFileNonLocalhostHost(t *testing.T) { - if !isProcessRunning(os.Getppid()) { - t.Skip("skipping: parent process not running in this environment") - } + if !isProcessRunning(os.Getppid()) { + t.Skip("skipping: parent process not running in this environment") + } - mux := http.NewServeMux() - mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - // Return parent PID — matches the PID file entry - json.NewEncoder(w).Encode(map[string]any{"pid": os.Getppid(), "status": "ok"}) - }) - srv := httptest.NewServer(mux) - defer srv.Close() + mux := http.NewServeMux() + mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + // Return parent PID — matches the PID file entry + json.NewEncoder(w).Encode(map[string]any{"pid": os.Getppid(), "status": "ok"}) + }) + srv := httptest.NewServer(mux) + defer srv.Close() - host, portStr, _ := net.SplitHostPort(srv.Listener.Addr().String()) - port, _ := strconv.Atoi(portStr) + host, portStr, _ := net.SplitHostPort(srv.Listener.Addr().String()) + port, _ := strconv.Atoi(portStr) - dir := tmpDir(t) - foreign := PidFileData{ - PID: os.Getppid(), // parent PID — real, running, but not us - Token: "deadbeef12345678deadbeef12345678", - Port: port, - Host: host, - } - raw, _ := json.MarshalIndent(foreign, "", " ") - os.WriteFile(filepath.Join(dir, pidFileName), raw, 0o600) + dir := tmpDir(t) + foreign := PidFileData{ + PID: os.Getppid(), // parent PID — real, running, but not us + Token: "deadbeef12345678deadbeef12345678", + Port: port, + Host: host, + } + raw, _ := json.MarshalIndent(foreign, "", " ") + os.WriteFile(filepath.Join(dir, pidFileName), raw, 0o600) - // Should block — PID exists, health responds with matching PID on non-localhost host - _, err := WritePidFile(dir, "127.0.0.1", 18790) - if err == nil { - t.Fatal("WritePidFile should block startup when gateway is genuinely alive on non-localhost host") - } + // Should block — PID exists, health responds with matching PID on non-localhost host + _, err := WritePidFile(dir, "127.0.0.1", 18790) + if err == nil { + t.Fatal("WritePidFile should block startup when gateway is genuinely alive on non-localhost host") + } } - -//verifies that a foreign process reusing a crashed gateway's PID is treated as stale. +// verifies that a foreign process reusing a crashed gateway's PID is treated as stale. func TestWritePidFileForeignPIDReuse(t *testing.T) { - dir := tmpDir(t) + dir := tmpDir(t) - // PID 1 (init/systemd) is always running but won't respond on port 19999 - foreign := PidFileData{ - PID: 1, - Token: "deadbeef12345678deadbeef12345678", - Port: 19999, // nothing listening here - Host: "127.0.0.1", - } - raw, _ := json.MarshalIndent(foreign, "", " ") - os.WriteFile(filepath.Join(dir, pidFileName), raw, 0o600) + // PID 1 (init/systemd) is always running but won't respond on port 19999 + foreign := PidFileData{ + PID: 1, + Token: "deadbeef12345678deadbeef12345678", + Port: 19999, // nothing listening here + Host: "127.0.0.1", + } + raw, _ := json.MarshalIndent(foreign, "", " ") + os.WriteFile(filepath.Join(dir, pidFileName), raw, 0o600) - // Should succeed — foreign PID reuse should be treated as stale - data, err := WritePidFile(dir, "127.0.0.1", 18790) - if err != nil { - t.Fatalf("WritePidFile should treat foreign PID as stale, got error: %v", err) - } - if data.PID != os.Getpid() { - t.Errorf("PID = %d, want %d", data.PID, os.Getpid()) - } + // Should succeed — foreign PID reuse should be treated as stale + data, err := WritePidFile(dir, "127.0.0.1", 18790) + if err != nil { + t.Fatalf("WritePidFile should treat foreign PID as stale, got error: %v", err) + } + if data.PID != os.Getpid() { + t.Errorf("PID = %d, want %d", data.PID, os.Getpid()) + } } // TestWritePidFile creates a PID file and verifies its contents.