feat: add scoped browser stop and job control
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user