daemon

package
v0.0.0-...-16efc32 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 28, 2026 License: MIT Imports: 8 Imported by: 0

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

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetDefaultLogDir

func GetDefaultLogDir() (string, error)

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

func GetRunningPID(logDir string) (int, error)

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

func GetRunningWorkspacePID(logDir, workspaceName string) (int, error)

GetRunningWorkspacePID returns the PID of the running workspace watcher, or 0 if not running.

func GetWorkspaceLogFile

func GetWorkspaceLogFile(logDir, workspaceName string) string

GetWorkspaceLogFile returns the path to the log file for a workspace.

func GetWorkspacePIDFile

func GetWorkspacePIDFile(logDir, workspaceName string) string

GetWorkspacePIDFile returns the path to the PID file for a workspace.

func GetWorkspaceReadyFile

func GetWorkspaceReadyFile(logDir, workspaceName string) string

GetWorkspaceReadyFile returns the path to the ready file for a workspace.

func IsProcessRunning

func IsProcessRunning(pid int) bool

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 IsReady

func IsReady(logDir string) bool

IsReady checks if the ready marker file exists.

func IsWorkspaceReady

func IsWorkspaceReady(logDir, workspaceName string) bool

IsWorkspaceReady checks if the workspace ready marker file exists.

func ReadPIDFile

func ReadPIDFile(logDir string) (int, error)

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

func ReadWorkspacePIDFile(logDir, workspaceName string) (int, error)

ReadWorkspacePIDFile reads the process ID from the workspace PID file.

func RemovePIDFile

func RemovePIDFile(logDir string) error

RemovePIDFile removes the PID file and its associated lock file.

func RemoveReadyFile

func RemoveReadyFile(logDir string) error

RemoveReadyFile removes the ready marker file.

func RemoveWorkspacePIDFile

func RemoveWorkspacePIDFile(logDir, workspaceName string) error

RemoveWorkspacePIDFile removes the workspace PID file and its lock file.

func RemoveWorkspaceReadyFile

func RemoveWorkspaceReadyFile(logDir, workspaceName string) error

RemoveWorkspaceReadyFile removes the ready marker file for a workspace.

func SpawnBackground

func SpawnBackground(logDir string, args []string) (int, error)

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

func SpawnWorkspaceBackground(logDir, workspaceName string, extraArgs []string) (int, error)

SpawnWorkspaceBackground re-executes the current binary for workspace watch in background.

func StopProcess

func StopProcess(pid int) error

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

func WritePIDFile(logDir string) error

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

func WriteReadyFile(logDir string) error

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

func WriteWorkspacePIDFile(logDir, workspaceName string) error

WriteWorkspacePIDFile writes the current process ID to the workspace PID file.

func WriteWorkspaceReadyFile

func WriteWorkspaceReadyFile(logDir, workspaceName string) error

WriteWorkspaceReadyFile writes the ready marker file for a workspace.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL