Refactor: separate process/lock/PID concerns into process.go (bd-d33c)

This commit is contained in:
Steve Yegge
2025-11-01 23:46:01 -07:00
parent 8fccf0df27
commit 7032d7d8ec
3 changed files with 70 additions and 29 deletions

File diff suppressed because one or more lines are too long

View File

@@ -7,7 +7,6 @@ import (
"os"
"os/signal"
"path/filepath"
"strconv"
"strings"
"time"
@@ -136,7 +135,7 @@ func (d *Daemon) runGlobalDaemon() error {
d.log.log("Error: cannot get global beads directory: %v", err)
return err
}
d.cfg.SocketPath = filepath.Join(globalDir, "bd.sock")
d.cfg.SocketPath = getSocketPath(globalDir)
ctx, cancel := context.WithCancel(context.Background())
d.cancel = cancel
@@ -193,18 +192,8 @@ func (d *Daemon) setupLock() (io.Closer, error) {
return nil, err
}
myPID := os.Getpid()
// #nosec G304 - controlled path from config
if data, err := os.ReadFile(d.cfg.PIDFile); err == nil {
if pid, err := strconv.Atoi(strings.TrimSpace(string(data))); err == nil && pid == myPID {
// PID file is correct, continue
} else {
d.log.log("PID file has wrong PID (expected %d, got %d), overwriting", myPID, pid)
_ = os.WriteFile(d.cfg.PIDFile, []byte(fmt.Sprintf("%d\n", myPID)), 0600)
}
} else {
d.log.log("PID file missing after lock acquisition, creating")
_ = os.WriteFile(d.cfg.PIDFile, []byte(fmt.Sprintf("%d\n", myPID)), 0600)
if err := ensurePIDFileCorrect(d.cfg.PIDFile); err != nil {
d.log.log("Warning: failed to verify PID file: %v", err)
}
return lock, nil
@@ -237,7 +226,7 @@ func (d *Daemon) determineDatabasePath() error {
d.cfg.DBPath = foundDB
d.cfg.BeadsDir = filepath.Dir(foundDB)
d.cfg.WorkspacePath = filepath.Dir(d.cfg.BeadsDir)
d.cfg.SocketPath = filepath.Join(d.cfg.BeadsDir, "bd.sock")
d.cfg.SocketPath = getSocketPath(d.cfg.BeadsDir)
return nil
}
@@ -293,7 +282,9 @@ func (d *Daemon) validateSchemaVersion() error {
return fmt.Errorf("failed to read database version: %w", err)
}
if dbVersion != "" && dbVersion != d.Version {
mismatch, missing := checkVersionMismatch(dbVersion, d.Version)
if mismatch {
d.log.log("Error: Database schema version mismatch")
d.log.log(" Database version: %s", dbVersion)
d.log.log(" Daemon version: %s", d.Version)
@@ -311,8 +302,7 @@ func (d *Daemon) validateSchemaVersion() error {
return fmt.Errorf("database version mismatch")
}
d.log.log("Warning: Proceeding despite version mismatch (BEADS_IGNORE_VERSION_MISMATCH=1)")
} else if dbVersion == "" {
// Old database without version metadata - set it now
} else if missing {
d.log.log("Warning: Database missing version metadata, setting to %s", d.Version)
if err := d.store.SetMetadata(ctx, "bd_version", d.Version); err != nil {
d.log.log("Error: failed to set database version: %v", err)

View File

@@ -6,6 +6,8 @@ import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
@@ -35,6 +37,55 @@ func (l *DaemonLock) Close() error {
return err
}
// getPIDFilePath returns the path to daemon.pid in the given beads directory
func getPIDFilePath(beadsDir string) string {
return filepath.Join(beadsDir, "daemon.pid")
}
// getSocketPath returns the path to bd.sock in the given beads directory
func getSocketPath(beadsDir string) string {
return filepath.Join(beadsDir, "bd.sock")
}
// readPIDFile reads the PID from daemon.pid
func readPIDFile(pidFile string) (int, error) {
// #nosec G304 - controlled path from config
data, err := os.ReadFile(pidFile)
if err != nil {
return 0, err
}
pid, err := strconv.Atoi(strings.TrimSpace(string(data)))
if err != nil {
return 0, fmt.Errorf("invalid PID in file: %w", err)
}
return pid, nil
}
// writePIDFile writes the current process PID to daemon.pid
func writePIDFile(pidFile string) error {
return os.WriteFile(pidFile, []byte(fmt.Sprintf("%d\n", os.Getpid())), 0600)
}
// ensurePIDFileCorrect verifies PID file has correct PID, fixes if wrong
func ensurePIDFileCorrect(pidFile string) error {
myPID := os.Getpid()
if pid, err := readPIDFile(pidFile); err == nil && pid == myPID {
return nil
}
return writePIDFile(pidFile)
}
// checkVersionMismatch checks if database version matches daemon version
func checkVersionMismatch(dbVersion, daemonVersion string) (mismatch bool, missing bool) {
if dbVersion == "" {
return false, true
}
if dbVersion != daemonVersion {
return true, false
}
return false, false
}
// acquireDaemonLock attempts to acquire an exclusive lock on daemon.lock
func acquireDaemonLock(beadsDir string, dbPath string, version string) (*DaemonLock, error) {
lockPath := filepath.Join(beadsDir, "daemon.lock")
@@ -71,8 +122,8 @@ func acquireDaemonLock(beadsDir string, dbPath string, version string) (*DaemonL
_ = f.Sync()
// Also write PID file for Windows compatibility
pidFile := filepath.Join(beadsDir, "daemon.pid")
_ = os.WriteFile(pidFile, []byte(fmt.Sprintf("%d\n", os.Getpid())), 0600)
pidFile := getPIDFilePath(beadsDir)
_ = writePIDFile(pidFile)
return &DaemonLock{file: f, path: lockPath}, nil
}