Compare commits

...

1 Commits

Author SHA1 Message Date
Nikhil Sonti
a7f835ca93 fix: rerun dev port cleanup before server restarts 2026-04-17 08:06:44 -07:00
5 changed files with 111 additions and 7 deletions

View File

@@ -2,6 +2,7 @@ package cmd
import (
"fmt"
"time"
"browseros-dev/proc"
@@ -33,7 +34,9 @@ func runCleanup(cmd *cobra.Command, args []string) error {
if doPorts {
ports := proc.DefaultLocalPorts()
proc.LogMsgf(proc.TagInfo, "Killing processes on ports %d, %d, %d...", ports.CDP, ports.Server, ports.Extension)
proc.KillPorts(ports)
if err := proc.KillPortsAndWait(ports, 3*time.Second); err != nil {
return err
}
proc.LogMsg(proc.TagInfo, "Ports cleared")
}

View File

@@ -8,6 +8,7 @@ import (
"path/filepath"
"sync"
"syscall"
"time"
"browseros-dev/browser"
"browseros-dev/proc"
@@ -62,7 +63,9 @@ func runWatch(cmd *cobra.Command, args []string) error {
return fmt.Errorf("creating user-data dir: %w", err)
}
proc.LogMsg(proc.TagInfo, "Killing processes on preferred ports...")
proc.KillPorts(defaultPorts)
if err := proc.KillPortsAndWait(defaultPorts, 3*time.Second); err != nil {
return err
}
proc.LogMsg(proc.TagInfo, "Ports cleared")
p, reservations, err = proc.ResolveWatchPorts(false)
@@ -159,6 +162,9 @@ func runWatch(cmd *cobra.Command, args []string) error {
Env: env,
Restart: true,
Cmd: []string{"bun", "--watch", "--env-file=.env.development", "src/index.ts"},
BeforeStart: func() error {
return proc.KillPortAndWait(p.Server, 3*time.Second)
},
}))
<-sigCh

View File

@@ -11,11 +11,12 @@ import (
)
type ProcConfig struct {
Tag Tag
Dir string
Env []string
Restart bool
Cmd []string
Tag Tag
Dir string
Env []string
Restart bool
Cmd []string
BeforeStart func() error
}
type ManagedProc struct {
@@ -49,6 +50,17 @@ func (mp *ManagedProc) run(ctx context.Context) {
return
}
if mp.Cfg.BeforeStart != nil {
if err := mp.Cfg.BeforeStart(); err != nil {
LogMsg(mp.Cfg.Tag, ErrorColor.Sprintf("Pre-start failed: %v", err))
if !mp.Cfg.Restart || ctx.Err() != nil {
return
}
time.Sleep(time.Second)
continue
}
}
LogMsgf(mp.Cfg.Tag, "Starting: %s", DimColor.Sprint(strings.Join(mp.Cfg.Cmd, " ")))
cmd := exec.Command(mp.Cfg.Cmd[0], mp.Cfg.Cmd[1:]...)

View File

@@ -0,0 +1,60 @@
package proc
import (
"context"
"os"
"path/filepath"
"sync"
"sync/atomic"
"testing"
"time"
)
func TestStartManagedRunsBeforeStartOnEachRetry(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 2200*time.Millisecond)
defer cancel()
var count atomic.Int32
var wg sync.WaitGroup
StartManaged(ctx, &wg, ProcConfig{
Tag: TagInfo,
Dir: t.TempDir(),
Restart: true,
Cmd: []string{"sh", "-c", "exit 1"},
BeforeStart: func() error {
count.Add(1)
return nil
},
})
wg.Wait()
if count.Load() < 2 {
t.Fatalf("expected BeforeStart to run on retries, got %d calls", count.Load())
}
}
func TestStartManagedSkipsLaunchWhenBeforeStartFails(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sentinel := filepath.Join(t.TempDir(), "started")
var wg sync.WaitGroup
StartManaged(ctx, &wg, ProcConfig{
Tag: TagInfo,
Dir: t.TempDir(),
Restart: false,
Cmd: []string{"sh", "-c", "touch " + sentinel},
BeforeStart: func() error {
return context.DeadlineExceeded
},
})
wg.Wait()
if _, err := os.Stat(sentinel); !os.IsNotExist(err) {
t.Fatalf("expected process launch to be skipped, stat err=%v", err)
}
}

View File

@@ -133,6 +133,29 @@ func KillPort(port int) {
exec.Command("sh", "-c", fmt.Sprintf("lsof -ti:%d | xargs kill -9 2>/dev/null || true", port)).Run()
}
func KillPortAndWait(port int, timeout time.Duration) error {
deadline := time.Now().Add(timeout)
for {
KillPort(port)
if IsPortAvailable(port) {
return nil
}
if time.Now().After(deadline) {
return fmt.Errorf("port %d is still in use after kill -9 cleanup", port)
}
time.Sleep(100 * time.Millisecond)
}
}
func KillPortsAndWait(p Ports, timeout time.Duration) error {
for _, port := range []int{p.CDP, p.Server, p.Extension} {
if err := KillPortAndWait(port, timeout); err != nil {
return err
}
}
return nil
}
func BuildEnv(p Ports, nodeEnv string) []string {
env := os.Environ()
env = append(env,