Files
nezha_domains/integration/agentcompat/internal/dashboard/lifecycle.go
T
2026-07-20 04:46:55 +00:00

157 lines
5.2 KiB
Go

//go:build linux
package dashboard
import (
"context"
"errors"
"fmt"
"github.com/nezhahq/nezha/integration/agentcompat/internal/client"
processharness "github.com/nezhahq/nezha/integration/agentcompat/internal/process"
)
func (dashboard *Dashboard) StopProcess(ctx context.Context) (RuntimeIdentity, error) {
dashboard.lifecycleMu.Lock()
defer dashboard.lifecycleMu.Unlock()
dashboard.stateMu.Lock()
process := dashboard.currentProcess
dashboard.currentProcess = nil
dashboard.supervisor = nil
dashboard.stateMu.Unlock()
if process == nil {
return RuntimeIdentity{}, errors.New("dashboard process is not running")
}
if process.receiptConn != nil {
_ = process.receiptConn.Close()
}
if process.httpTransport != nil {
process.httpTransport.CloseIdleConnections()
}
if process.tlsTransport != nil {
process.tlsTransport.CloseIdleConnections()
}
if err := process.supervisor.Stop(ctx); err != nil {
return process.identity, fmt.Errorf("stop dashboard process: %w", err)
}
process.record = process.supervisor.CleanupRecord()
return process.identity, nil
}
func (dashboard *Dashboard) StartProcess(ctx context.Context) (RuntimeIdentity, error) {
dashboard.lifecycleMu.Lock()
defer dashboard.lifecycleMu.Unlock()
dashboard.stateMu.Lock()
if dashboard.currentProcess != nil {
dashboard.stateMu.Unlock()
return RuntimeIdentity{}, errors.New("dashboard process is already running")
}
dashboard.generation++
generation := dashboard.generation
dashboard.stateMu.Unlock()
dashboard.receiptMu.Lock()
dashboard.receiptAccepted = false
dashboard.receiptAcceptedCount = 0
dashboard.receiptGeneration = 0
dashboard.receiptMu.Unlock()
process, err := dashboard.startGeneration(ctx, generation)
if err != nil {
return RuntimeIdentity{}, err
}
dashboard.stateMu.Lock()
dashboard.currentProcess = process
dashboard.supervisor = process.supervisor
dashboard.processes = append(dashboard.processes, process)
dashboard.stateMu.Unlock()
return process.identity, nil
}
func (dashboard *Dashboard) FixtureIdentity() FixtureIdentity {
identity := FixtureIdentity{WorkspaceRoot: dashboard.workspace.Root(), ConfigPath: dashboard.configPath, DatabasePath: dashboard.databasePath, BinaryPath: dashboard.binaryPath}
if dashboard.httpListener != nil {
identity.HTTP = dashboard.httpListener.Identity()
}
if dashboard.receiptListener != nil {
identity.Receipt = dashboard.receiptListener.Identity()
}
if dashboard.httpsListener != nil {
identity.HTTPS = dashboard.httpsListener.Identity()
}
return identity
}
func (dashboard *Dashboard) RuntimeIdentity() RuntimeIdentity {
dashboard.stateMu.Lock()
defer dashboard.stateMu.Unlock()
if dashboard.currentProcess == nil {
return RuntimeIdentity{}
}
return dashboard.currentProcess.identity
}
func (dashboard *Dashboard) Restart(ctx context.Context) error {
if _, err := dashboard.StopProcess(ctx); err != nil {
return err
}
_, err := dashboard.StartProcess(ctx)
return err
}
func (dashboard *Dashboard) cleanupOnCancellation(ctx context.Context) {
select {
case <-ctx.Done():
dashboard.cleanupOnce.Do(func() { go dashboard.cleanup(context.WithoutCancel(ctx)) })
case <-dashboard.cleanupDone:
}
}
func (dashboard *Dashboard) cleanup(ctx context.Context) {
defer close(dashboard.cleanupDone)
var stopError error
cleanupReceipt := dashboard.cleanupProcesses(ctx, &stopError)
if err := dashboard.workspace.Close(); err != nil {
stopError = errors.Join(stopError, fmt.Errorf("close dashboard workspace: %w", err))
cleanupReceipt = processharness.NewCleanupReceipt(append(cleanupReceipt.Processes, processharness.CleanupRecord{Name: "dashboard-workspace", Error: client.Redact(err.Error())}))
}
dashboard.cleanupMu.Lock()
dashboard.cleanupError = stopError
dashboard.cleanupReceipt = cleanupReceipt
dashboard.cleanupMu.Unlock()
}
func (dashboard *Dashboard) cleanupProcesses(ctx context.Context, stopError *error) processharness.CleanupReceipt {
cleanupReceipt := processharness.CleanupReceipt{}
dashboard.stateMu.Lock()
processes := append([]*dashboardGeneration(nil), dashboard.processes...)
legacySupervisor := dashboard.supervisor
dashboard.stateMu.Unlock()
if len(processes) == 0 && legacySupervisor != nil {
if err := legacySupervisor.Stop(ctx); err != nil {
*stopError = errors.Join(*stopError, fmt.Errorf("stop dashboard process: %w", err))
}
return processharness.NewCleanupReceipt([]processharness.CleanupRecord{legacySupervisor.CleanupRecord()})
}
for _, process := range processes {
if process.receiptConn != nil {
_ = process.receiptConn.Close()
}
if process.httpTransport != nil {
process.httpTransport.CloseIdleConnections()
}
if process.tlsTransport != nil {
process.tlsTransport.CloseIdleConnections()
}
stopContext, cancel := context.WithTimeout(ctx, failedStartCleanupTimeout)
if err := process.supervisor.Stop(stopContext); err != nil {
*stopError = errors.Join(*stopError, fmt.Errorf("stop dashboard process: %w", err))
}
cancel()
process.record = process.supervisor.CleanupRecord()
if process.record.Forced {
*stopError = errors.Join(*stopError, errors.New("dashboard required forced SIGKILL cleanup"))
}
cleanupReceipt.Processes = append(cleanupReceipt.Processes, process.record)
}
return processharness.NewCleanupReceipt(cleanupReceipt.Processes)
}