go-process/pidfile.go
Claude a09ca4f408
chore: migrate to dappco.re vanity import path
Module path: forge.lthn.ai/core/go-process -> dappco.re/go/core/process

Import path updates:
- forge.lthn.ai/core/go-log -> dappco.re/go/core/log
- forge.lthn.ai/core/go-io -> dappco.re/go/core/io
- forge.lthn.ai/core/go-ws -> dappco.re/go/core/ws
- forge.lthn.ai/core/go-process (self) -> dappco.re/go/core/process
- forge.lthn.ai/core/api left as-is (not yet migrated)

Local replace directives added until vanity URL server is configured.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 23:49:08 +00:00

98 lines
2.2 KiB
Go

package process
import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
coreio "dappco.re/go/core/io"
coreerr "dappco.re/go/core/log"
)
// PIDFile manages a process ID file for single-instance enforcement.
type PIDFile struct {
path string
mu sync.Mutex
}
// NewPIDFile creates a PID file manager.
func NewPIDFile(path string) *PIDFile {
return &PIDFile{path: path}
}
// Acquire writes the current PID to the file.
// Returns error if another instance is running.
func (p *PIDFile) Acquire() error {
p.mu.Lock()
defer p.mu.Unlock()
if data, err := coreio.Local.Read(p.path); err == nil {
pid, err := strconv.Atoi(strings.TrimSpace(data))
if err == nil && pid > 0 {
if proc, err := os.FindProcess(pid); err == nil {
if err := proc.Signal(syscall.Signal(0)); err == nil {
return coreerr.E("PIDFile.Acquire", fmt.Sprintf("another instance is running (PID %d)", pid), nil)
}
}
}
_ = coreio.Local.Delete(p.path)
}
if dir := filepath.Dir(p.path); dir != "." {
if err := coreio.Local.EnsureDir(dir); err != nil {
return coreerr.E("PIDFile.Acquire", "failed to create PID directory", err)
}
}
pid := os.Getpid()
if err := coreio.Local.Write(p.path, strconv.Itoa(pid)); err != nil {
return coreerr.E("PIDFile.Acquire", "failed to write PID file", err)
}
return nil
}
// Release removes the PID file.
func (p *PIDFile) Release() error {
p.mu.Lock()
defer p.mu.Unlock()
if err := coreio.Local.Delete(p.path); err != nil {
return coreerr.E("PIDFile.Release", "failed to remove PID file", err)
}
return nil
}
// Path returns the PID file path.
func (p *PIDFile) Path() string {
return p.path
}
// ReadPID reads a PID file and checks if the process is still running.
// Returns (pid, true) if the process is alive, (pid, false) if dead/stale,
// or (0, false) if the file doesn't exist or is invalid.
func ReadPID(path string) (int, bool) {
data, err := coreio.Local.Read(path)
if err != nil {
return 0, false
}
pid, err := strconv.Atoi(strings.TrimSpace(data))
if err != nil || pid <= 0 {
return 0, false
}
proc, err := os.FindProcess(pid)
if err != nil {
return pid, false
}
if err := proc.Signal(syscall.Signal(0)); err != nil {
return pid, false
}
return pid, true
}