feat: add scoped browser stop and job control

This commit is contained in:
QiuSW
2026-07-22 15:03:21 +08:00
parent d6f6bb0f2c
commit 1ce02bcf3b
8 changed files with 262 additions and 14 deletions
+37 -7
View File
@@ -5,6 +5,8 @@ import (
"errors"
"fmt"
"os/exec"
"path/filepath"
"strings"
"sync"
"chub/internal/domain"
@@ -21,6 +23,10 @@ type RunnerProcess interface {
Wait() (int, error)
}
type RunnerProcessController interface {
Stop(context.Context, bool) error
}
type ProfileUse struct {
Occupied bool
PID int
@@ -96,19 +102,33 @@ func (l *Launcher) InspectProfile(ctx context.Context, userDataDir string) (Prof
if err := ctx.Err(); err != nil {
return ProfileUse{}, err
}
normalized, err := (domain.LaunchSpec{Kind: domain.BrowserChrome, UserDataDir: userDataDir, TargetURL: "https://example.invalid"}).Normalize()
if err != nil {
return ProfileUse{}, err
}
profile := strings.TrimSpace(userDataDir)
if profile == "" || !filepath.IsAbs(profile) { return ProfileUse{}, fmt.Errorf("%w: user data dir must be absolute", domain.ErrInvalidLaunchSpec) }
profile = filepath.Clean(profile)
l.mu.Lock()
defer l.mu.Unlock()
process := l.profiles[normalized.UserDataDir]
process := l.profiles[profile]
if process == nil {
return ProfileUse{}, nil
}
return ProfileUse{Occupied: true, PID: process.PID(), Source: "chub_registry"}, nil
}
func (l *Launcher) StopProfile(ctx context.Context, userDataDir string, force bool) error {
if err := ctx.Err(); err != nil { return err }
profile := strings.TrimSpace(userDataDir)
if profile == "" || !filepath.IsAbs(profile) { return fmt.Errorf("%w: user data dir must be absolute", domain.ErrInvalidLaunchSpec) }
profile = filepath.Clean(profile)
l.mu.Lock()
process := l.profiles[profile]
l.mu.Unlock()
if process == nil { return domain.ErrInstanceNotFound }
if process.raw == nil { return errors.New("browser process is still starting") }
controller, ok := process.raw.(RunnerProcessController)
if !ok { return errors.New("browser process does not support stop") }
return controller.Stop(ctx, force)
}
func (l *Launcher) release(profile string, process *trackedProcess) {
l.mu.Lock()
defer l.mu.Unlock()
@@ -175,13 +195,23 @@ func (osRunner) Start(executable string, args []string) (RunnerProcess, error) {
if err := command.Start(); err != nil {
return nil, err
}
return &osProcess{command: command}, nil
process := &osProcess{command: command}
if err := process.job.assign(command.Process); err != nil {
_ = command.Process.Kill()
_, _ = command.Process.Wait()
return nil, err
}
return process, nil
}
type osProcess struct{ command *exec.Cmd }
type osProcess struct {
command *exec.Cmd
job jobHandle
}
func (p *osProcess) PID() int { return p.command.Process.Pid }
func (p *osProcess) Wait() (int, error) {
defer p.job.close()
err := p.command.Wait()
if p.command.ProcessState == nil {
return 0, err