bd-154: Implement bd daemons stop and restart subcommands
This commit is contained in:
@@ -212,3 +212,25 @@ func CleanupStaleSockets(daemons []DaemonInfo) (int, error) {
|
||||
}
|
||||
return cleaned, nil
|
||||
}
|
||||
|
||||
// StopDaemon gracefully stops a daemon by sending shutdown command via RPC
|
||||
// Falls back to SIGTERM if RPC fails
|
||||
func StopDaemon(daemon DaemonInfo) error {
|
||||
if !daemon.Alive {
|
||||
return fmt.Errorf("daemon is not running")
|
||||
}
|
||||
|
||||
// Try graceful shutdown via RPC first
|
||||
client, err := rpc.TryConnectWithTimeout(daemon.SocketPath, 500*time.Millisecond)
|
||||
if err == nil && client != nil {
|
||||
defer client.Close()
|
||||
if err := client.Shutdown(); err == nil {
|
||||
// Wait a bit for daemon to shut down
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to SIGTERM if RPC failed
|
||||
return killProcess(daemon.PID)
|
||||
}
|
||||
|
||||
15
internal/daemon/kill_unix.go
Normal file
15
internal/daemon/kill_unix.go
Normal file
@@ -0,0 +1,15 @@
|
||||
//go:build unix
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func killProcess(pid int) error {
|
||||
if err := syscall.Kill(pid, syscall.SIGTERM); err != nil {
|
||||
return fmt.Errorf("failed to send SIGTERM to PID %d: %w", pid, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
18
internal/daemon/kill_windows.go
Normal file
18
internal/daemon/kill_windows.go
Normal file
@@ -0,0 +1,18 @@
|
||||
//go:build windows
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func killProcess(pid int) error {
|
||||
// Use taskkill on Windows
|
||||
cmd := exec.Command("taskkill", "/PID", strconv.Itoa(pid), "/F")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to kill PID %d: %w", pid, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -209,6 +209,12 @@ func (c *Client) Health() (*HealthResponse, error) {
|
||||
return &health, nil
|
||||
}
|
||||
|
||||
// Shutdown sends a graceful shutdown request to the daemon
|
||||
func (c *Client) Shutdown() error {
|
||||
_, err := c.Execute(OpShutdown, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// Metrics retrieves daemon metrics
|
||||
func (c *Client) Metrics() (*MetricsSnapshot, error) {
|
||||
resp, err := c.Execute(OpMetrics, nil)
|
||||
|
||||
@@ -31,6 +31,7 @@ const (
|
||||
OpExport = "export"
|
||||
OpImport = "import"
|
||||
OpEpicStatus = "epic_status"
|
||||
OpShutdown = "shutdown"
|
||||
)
|
||||
|
||||
// Request represents an RPC request from client to daemon
|
||||
|
||||
@@ -688,6 +688,8 @@ func (s *Server) handleRequest(req *Request) Response {
|
||||
resp = s.handleImport(req)
|
||||
case OpEpicStatus:
|
||||
resp = s.handleEpicStatus(req)
|
||||
case OpShutdown:
|
||||
resp = s.handleShutdown(req)
|
||||
default:
|
||||
s.metrics.RecordError(req.Operation)
|
||||
return Response{
|
||||
@@ -2093,6 +2095,21 @@ func (s *Server) handleEpicStatus(req *Request) Response {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleShutdown(_ *Request) Response {
|
||||
// Schedule shutdown in a goroutine so we can return a response first
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond) // Give time for response to be sent
|
||||
if err := s.Stop(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error during shutdown: %v\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return Response{
|
||||
Success: true,
|
||||
Data: json.RawMessage(`{"message":"Daemon shutting down"}`),
|
||||
}
|
||||
}
|
||||
|
||||
// GetLastImportTime returns the last JSONL import timestamp
|
||||
func (s *Server) GetLastImportTime() time.Time {
|
||||
s.importMu.RLock()
|
||||
|
||||
Reference in New Issue
Block a user