mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-20 04:21:23 +00:00
* feat(openclaw): add gateway image inspection * feat(openclaw): pull gateway image from registry * refactor(vm): decouple readiness from image cache * refactor(openclaw): remove vm cache from runtime factory * feat(openclaw): detect current gateway image * feat(openclaw): prewarm vm runtime and reuse current gateway * feat(openclaw): prewarm runtime on server startup * refactor(vm): remove browseros image cache runtime * refactor(build-tools): remove openclaw tarball pipeline * chore: self-review fixes * fix(openclaw): suppress prewarm pull progress logs * fix(openclaw): address review feedback * fix(openclaw): resolve review findings * fix(dev): stop stale watch supervisors
117 lines
2.6 KiB
Go
117 lines
2.6 KiB
Go
package proc
|
|
|
|
import (
|
|
"fmt"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
// StopExistingWatchProcesses terminates older default-profile watch supervisors.
|
|
// Port cleanup cannot see a previous watch process while it is still waiting
|
|
// for CDP, but that process will wake up later and race the new supervisor.
|
|
func StopExistingWatchProcesses(timeout time.Duration) (int, error) {
|
|
currentPGID, err := syscall.Getpgid(0)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("reading current process group: %w", err)
|
|
}
|
|
|
|
groups, err := currentWatchProcessGroups(currentPGID)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if len(groups) == 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
for _, pgid := range groups {
|
|
if err := signalProcessGroup(pgid, syscall.SIGTERM); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
|
|
deadline := time.Now().Add(timeout)
|
|
for {
|
|
remaining, err := currentWatchProcessGroups(currentPGID)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if len(remaining) == 0 {
|
|
return len(groups), nil
|
|
}
|
|
if time.Now().After(deadline) {
|
|
for _, pgid := range remaining {
|
|
if err := signalProcessGroup(pgid, syscall.SIGKILL); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
return len(groups), nil
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
func currentWatchProcessGroups(currentPGID int) ([]int, error) {
|
|
output, err := exec.Command("ps", "-axo", "pid=,pgid=,command=").Output()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("listing processes: %w", err)
|
|
}
|
|
return watchProcessGroupsFromPS(string(output), currentPGID), nil
|
|
}
|
|
|
|
func watchProcessGroupsFromPS(output string, currentPGID int) []int {
|
|
seen := map[int]struct{}{}
|
|
for _, line := range strings.Split(output, "\n") {
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 3 {
|
|
continue
|
|
}
|
|
pgid, err := strconv.Atoi(fields[1])
|
|
if err != nil || pgid == currentPGID {
|
|
continue
|
|
}
|
|
if isDefaultWatchCommand(fields[2:]) {
|
|
seen[pgid] = struct{}{}
|
|
}
|
|
}
|
|
|
|
groups := make([]int, 0, len(seen))
|
|
for pgid := range seen {
|
|
groups = append(groups, pgid)
|
|
}
|
|
sort.Ints(groups)
|
|
return groups
|
|
}
|
|
|
|
func isDefaultWatchCommand(commandFields []string) bool {
|
|
if len(commandFields) < 2 {
|
|
return false
|
|
}
|
|
if filepath.Base(commandFields[0]) != "browseros-dev" {
|
|
return false
|
|
}
|
|
if commandFields[1] != "watch" {
|
|
return false
|
|
}
|
|
for _, field := range commandFields[2:] {
|
|
if field == "--new" {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func signalProcessGroup(pgid int, signal syscall.Signal) error {
|
|
if pgid <= 0 {
|
|
return nil
|
|
}
|
|
if err := syscall.Kill(-pgid, signal); err != nil && err != syscall.ESRCH {
|
|
return fmt.Errorf("signaling process group %d: %w", pgid, err)
|
|
}
|
|
return nil
|
|
}
|