Documentation
¶
Overview ¶
Package daemon provides lifecycle management for the grepai watch daemon.
This package handles PID file management, process spawning, and process lifecycle operations for running grepai watch in background mode.
Basic Usage ¶
Start a background process:
logDir, _ := daemon.GetDefaultLogDir()
pid, err := daemon.SpawnBackground(logDir, []string{"watch"})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Started with PID %d\n", pid)
Check if the process is running:
pid, err := daemon.GetRunningPID(logDir)
if err != nil {
log.Fatal(err)
}
if pid > 0 {
fmt.Printf("Watcher is running (PID %d)\n", pid)
}
Stop the process:
daemon.StopProcess(pid) daemon.RemovePIDFile(logDir)
PID File Format ¶
The PID file contains a single line with the process ID as a decimal integer. This format is stable and will not change in future versions. If additional metadata is needed, it will be stored in separate files (e.g., grepai-watch.meta).
Platform Support ¶
Cross-platform support for Unix-like systems (Linux, macOS) and Windows. Platform-specific behavior is implemented in daemon_unix.go and daemon_windows.go.
Thread Safety ¶
PID file writes use file locking (flock) to prevent race conditions when multiple processes attempt to start simultaneously.
Package daemon provides workspace daemon management for multi-project indexing.
Index ¶
- func GetDefaultLogDir() (string, error)
- func GetRunningPID(logDir string) (int, error)
- func GetRunningWorkspacePID(logDir, workspaceName string) (int, error)
- func GetWorkspaceLogFile(logDir, workspaceName string) string
- func GetWorkspacePIDFile(logDir, workspaceName string) string
- func GetWorkspaceReadyFile(logDir, workspaceName string) string
- func IsProcessRunning(pid int) bool
- func IsReady(logDir string) bool
- func IsWorkspaceReady(logDir, workspaceName string) bool
- func ReadPIDFile(logDir string) (int, error)
- func ReadWorkspacePIDFile(logDir, workspaceName string) (int, error)
- func RemovePIDFile(logDir string) error
- func RemoveReadyFile(logDir string) error
- func RemoveWorkspacePIDFile(logDir, workspaceName string) error
- func RemoveWorkspaceReadyFile(logDir, workspaceName string) error
- func SpawnBackground(logDir string, args []string) (int, error)
- func SpawnWorkspaceBackground(logDir, workspaceName string, extraArgs []string) (int, error)
- func StopProcess(pid int) error
- func WritePIDFile(logDir string) error
- func WriteReadyFile(logDir string) error
- func WriteWorkspacePIDFile(logDir, workspaceName string) error
- func WriteWorkspaceReadyFile(logDir, workspaceName string) error
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func GetDefaultLogDir ¶
GetDefaultLogDir returns the OS-specific default log directory.
Platform-specific defaults:
- Linux: $XDG_STATE_HOME/grepai/logs or ~/.local/state/grepai/logs
- macOS: ~/Library/Logs/grepai
- Windows: %LOCALAPPDATA%\grepai\logs
This function is typically called once at startup to determine where PID files and logs should be stored. Use the --log-dir flag to override the default.
Returns an absolute path to the log directory. The directory may not exist yet; callers should create it with os.MkdirAll if needed.
Example ¶
ExampleGetDefaultLogDir demonstrates how to get the OS-specific default log directory.
package main
import (
"fmt"
"log"
"github.com/pprgva/code-memory/daemon"
)
func main() {
logDir, err := daemon.GetDefaultLogDir()
if err != nil {
log.Fatal(err)
}
fmt.Println("Log directory determined")
// On macOS: ~/Library/Logs/grepai
// On Linux: ~/.local/state/grepai/logs (or $XDG_STATE_HOME/grepai/logs)
// On Windows: %LOCALAPPDATA%\grepai\logs
_ = logDir
}
func GetRunningPID ¶
GetRunningPID returns the PID of the running watcher process, or 0 if not running. Automatically cleans up stale PID files (where the process no longer exists). This is a convenience function that combines ReadPIDFile, IsProcessRunning, and stale PID cleanup in one call.
Example ¶
ExampleGetRunningPID demonstrates how to check if a watcher is running and automatically clean up stale PID files.
package main
import (
"fmt"
"log"
"github.com/pprgva/code-memory/daemon"
)
func main() {
logDir := "/tmp/grepai-logs"
// Check if a watcher is running
pid, err := daemon.GetRunningPID(logDir)
if err != nil {
log.Fatal(err)
}
if pid == 0 {
fmt.Println("No watcher running")
} else {
fmt.Printf("Watcher running with PID %d\n", pid)
}
}
func GetRunningWorkspacePID ¶
GetRunningWorkspacePID returns the PID of the running workspace watcher, or 0 if not running.
func GetWorkspaceLogFile ¶
GetWorkspaceLogFile returns the path to the log file for a workspace.
func GetWorkspacePIDFile ¶
GetWorkspacePIDFile returns the path to the PID file for a workspace.
func GetWorkspaceReadyFile ¶
GetWorkspaceReadyFile returns the path to the ready file for a workspace.
func IsProcessRunning ¶
IsProcessRunning checks if a process with the given PID is running on Unix systems. Uses signal(0) which returns an error if the process doesn't exist or we don't have permission.
func IsWorkspaceReady ¶
IsWorkspaceReady checks if the workspace ready marker file exists.
func ReadPIDFile ¶
ReadPIDFile reads the process ID from the PID file in the given logDir.
Return values:
- (0, nil): No PID file exists (watcher not running or not started yet)
- (pid, nil): PID file exists and contains a valid process ID
- (0, error): PID file exists but is corrupt, unreadable, or has wrong permissions
Note: This function does NOT check if the process is actually running. Use GetRunningPID() for automatic stale PID detection and cleanup, or call IsProcessRunning(pid) to check the process status manually.
Example ¶
ExampleReadPIDFile demonstrates how to read the PID file directly. For most use cases, prefer GetRunningPID which also handles stale PIDs.
package main
import (
"fmt"
"log"
"github.com/pprgva/code-memory/daemon"
)
func main() {
logDir := "/tmp/grepai-logs"
// Read the PID file
pid, err := daemon.ReadPIDFile(logDir)
if err != nil {
log.Fatal(err)
}
if pid == 0 {
fmt.Println("No PID file found")
} else {
// Check if the process is actually running
if daemon.IsProcessRunning(pid) {
fmt.Printf("Process %d is running\n", pid)
} else {
fmt.Printf("Process %d is not running (stale PID)\n", pid)
}
}
}
func ReadWorkspacePIDFile ¶
ReadWorkspacePIDFile reads the process ID from the workspace PID file.
func RemovePIDFile ¶
RemovePIDFile removes the PID file and its associated lock file.
func RemoveReadyFile ¶
RemoveReadyFile removes the ready marker file.
func RemoveWorkspacePIDFile ¶
RemoveWorkspacePIDFile removes the workspace PID file and its lock file.
func RemoveWorkspaceReadyFile ¶
RemoveWorkspaceReadyFile removes the ready marker file for a workspace.
func SpawnBackground ¶
SpawnBackground re-executes the current binary as a background process.
The function spawns a detached child process with:
- stdout/stderr redirected to logDir/grepai-watch.log
- stdin set to nil (no input)
- GREPAI_BACKGROUND=1 environment variable set
- process group detachment (Unix only)
The parent process does NOT wait for the child - the child runs independently and will be reaped by the OS when it exits.
Args should be the command-line arguments to pass to the child process (e.g., []string{"watch"} for "grepai watch").
Returns the child process PID on success. The caller should verify the child started successfully by polling IsReady() or checking IsProcessRunning().
Example ¶
ExampleSpawnBackground demonstrates how to start a background process. This is a simplified example; real usage should include error handling and startup verification.
package main
import (
"fmt"
"log"
"path/filepath"
"github.com/pprgva/code-memory/daemon"
)
func main() {
logDir := "/tmp/grepai-logs"
// Spawn background process
pid, err := daemon.SpawnBackground(logDir, []string{"watch"})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Started background watcher with PID %d\n", pid)
fmt.Printf("Logs: %s\n", filepath.Join(logDir, "grepai-watch.log"))
// In real usage, poll for IsReady() to verify successful startup
}
func SpawnWorkspaceBackground ¶
SpawnWorkspaceBackground re-executes the current binary for workspace watch in background.
func StopProcess ¶
StopProcess sends an interrupt signal to the process with the given PID.
This sends SIGINT (Unix) or os.Interrupt (Windows) to request graceful shutdown. The target process should have signal handlers installed to catch the interrupt and clean up (persist state, close connections, etc.) before exiting.
This function returns immediately after sending the signal. It does NOT wait for the process to exit. Callers should poll IsProcessRunning() to verify the process has stopped.
Returns an error if the PID is invalid (<= 0) or if the signal cannot be sent (process doesn't exist, insufficient permissions, etc.).
Example ¶
ExampleStopProcess demonstrates how to gracefully stop a background process.
package main
import (
"fmt"
"log"
"github.com/pprgva/code-memory/daemon"
)
func main() {
logDir := "/tmp/grepai-logs"
// Get the running PID
pid, err := daemon.GetRunningPID(logDir)
if err != nil {
log.Fatal(err)
}
if pid == 0 {
fmt.Println("No process to stop")
return
}
// Send interrupt signal
if err := daemon.StopProcess(pid); err != nil {
log.Fatal(err)
}
fmt.Printf("Sent interrupt signal to PID %d\n", pid)
// In real usage, poll IsProcessRunning to wait for shutdown
// Then call RemovePIDFile to clean up
}
func WritePIDFile ¶
WritePIDFile writes the current process ID to the PID file. Uses file locking to prevent race conditions when multiple processes attempt to start simultaneously. The lock is held for the lifetime of the process (released by the OS on exit).
PID file format: single line containing process ID as decimal integer. This format is stable and will not change. If additional metadata is needed in the future, it will be stored in a separate file (e.g., grepai-watch.meta).
Example ¶
ExampleWritePIDFile demonstrates how to write the current process PID to a file. This is typically called after daemonizing to record the background process PID.
package main
import (
"fmt"
"log"
"github.com/pprgva/code-memory/daemon"
)
func main() {
logDir := "/tmp/grepai-logs"
// Write PID file for current process
if err := daemon.WritePIDFile(logDir); err != nil {
log.Fatal(err)
}
fmt.Println("PID file written")
// Clean up when done
defer daemon.RemovePIDFile(logDir)
}
func WriteReadyFile ¶
WriteReadyFile writes the ready marker file to indicate the daemon has successfully initialized and is ready to serve. This should be called after all initialization is complete (embedder, store, initial scan).
func WriteWorkspacePIDFile ¶
WriteWorkspacePIDFile writes the current process ID to the workspace PID file.
func WriteWorkspaceReadyFile ¶
WriteWorkspaceReadyFile writes the ready marker file for a workspace.
Types ¶
This section is empty.