daemon

package
v0.0.0-...-5b37cd7 Latest Latest
Warning

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

Go to latest
Published: Mar 2, 2026 License: MIT Imports: 9 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, exitCh, err := daemon.SpawnBackground(logDir, []string{"watch"})
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Started with PID %d\n", pid)
// exitCh receives when child exits (detects early failures)

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/Boshommi/grepai/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/Boshommi/grepai/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 GetRunningWorktreePID

func GetRunningWorktreePID(logDir, worktreeID string) (int, error)

GetRunningWorktreePID returns the PID of the running worktree 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 GetWorktreeLogFile

func GetWorktreeLogFile(logDir, worktreeID string) string

GetWorktreeLogFile returns the path to the log file for a worktree.

func GetWorktreePIDFile

func GetWorktreePIDFile(logDir, worktreeID string) string

GetWorktreePIDFile returns the path to the PID file for a worktree.

func GetWorktreeReadyFile

func GetWorktreeReadyFile(logDir, worktreeID string) string

GetWorktreeReadyFile returns the path to the ready file for a worktree.

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 IsWorktreeReady

func IsWorktreeReady(logDir, worktreeID string) bool

IsWorktreeReady checks if the worktree 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/Boshommi/grepai/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 ReadWorktreePIDFile

func ReadWorktreePIDFile(logDir, worktreeID string) (int, error)

ReadWorktreePIDFile reads the process ID from the worktree 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 RemoveWorktreePIDFile

func RemoveWorktreePIDFile(logDir, worktreeID string) error

RemoveWorktreePIDFile removes the worktree PID file and its lock file.

func RemoveWorktreeReadyFile

func RemoveWorktreeReadyFile(logDir, worktreeID string) error

RemoveWorktreeReadyFile removes the ready marker file for a worktree.

func SpawnBackground

func SpawnBackground(logDir string, args []string) (int, <-chan struct{}, 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)

Args should be the command-line arguments to pass to the child process (e.g., []string{"watch"} for "grepai watch").

Returns the child PID and an exit channel. The exit channel receives when the child process terminates, enabling callers to detect early failures without relying on kill(0) which cannot distinguish zombie processes.

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/Boshommi/grepai/daemon"
)

func main() {
	logDir := "/tmp/grepai-logs"

	// Spawn background process (exitCh signals when child exits)
	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() and check exitCh to verify successful startup
}

func SpawnWorkspaceBackground

func SpawnWorkspaceBackground(logDir, workspaceName string, extraArgs []string) (int, <-chan struct{}, error)

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

func SpawnWorktreeBackground

func SpawnWorktreeBackground(logDir, worktreeID string, extraArgs []string) (int, <-chan struct{}, error)

SpawnWorktreeBackground re-executes the current binary for worktree watch in background.

func StopChannel

func StopChannel() <-chan struct{}

StopChannel returns a channel that never fires on Unix. Signal-based shutdown is handled via os/signal, so no additional mechanism is needed.

func StopProcess

func StopProcess(pid int) error

StopProcess sends SIGINT to the process with the given PID.

Example

ExampleStopProcess demonstrates how to gracefully stop a background process.

package main

import (
	"fmt"
	"log"

	"github.com/Boshommi/grepai/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/Boshommi/grepai/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.

func WriteWorktreePIDFile

func WriteWorktreePIDFile(logDir, worktreeID string) error

WriteWorktreePIDFile writes the current process ID to the worktree PID file.

func WriteWorktreeReadyFile

func WriteWorktreeReadyFile(logDir, worktreeID string) error

WriteWorktreeReadyFile writes the ready marker file for a worktree.

Types

This section is empty.

Jump to

Keyboard shortcuts

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