feat: toggle managed instance start and stop
This commit is contained in:
@@ -121,6 +121,7 @@ func runWindow(logger *slog.Logger) {
|
||||
portAllocator: cdp,
|
||||
proxyResolver: proxies,
|
||||
}.Start, window.Invalidate)
|
||||
shell.OnStopInstance(instanceStopper{launcher: launcher, managedProfiles: launcher}.Stop, window.Invalidate)
|
||||
shell.OnRefreshInstances(instanceStatusRefresher{
|
||||
managedProfiles: launcher,
|
||||
externalProfiles: externalProfiles,
|
||||
@@ -263,6 +264,76 @@ type managedProfileInspector interface {
|
||||
InspectProfile(context.Context, string) (browser.ProfileUse, error)
|
||||
}
|
||||
|
||||
type profileStopper interface {
|
||||
StopProfile(context.Context, string, bool) error
|
||||
}
|
||||
|
||||
// instanceStopper is deliberately scoped to Chub's in-memory registry. It
|
||||
// requests graceful exit only, then waits for that registry entry to disappear;
|
||||
// it never enumerates or terminates browsers by process name.
|
||||
type instanceStopper struct {
|
||||
launcher profileStopper
|
||||
managedProfiles managedProfileInspector
|
||||
waitTimeout time.Duration
|
||||
waitInterval time.Duration
|
||||
}
|
||||
|
||||
func (s instanceStopper) Stop(ctx context.Context, row ui.InstanceRow) error {
|
||||
if row.Status != "运行中" && row.Status != "运行中(调试不可用)" {
|
||||
return errors.New("实例当前不是可停止的 Chub 托管状态")
|
||||
}
|
||||
if row.OccupancySource != "" && row.OccupancySource != "chub_registry" {
|
||||
return domain.ErrIdentityMismatch
|
||||
}
|
||||
if s.launcher == nil || s.managedProfiles == nil {
|
||||
return errors.New("浏览器停止服务尚未准备好")
|
||||
}
|
||||
use, err := s.managedProfiles.InspectProfile(ctx, row.UserDataDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("检查 Chub 实例身份失败:%w", err)
|
||||
}
|
||||
if !use.Occupied {
|
||||
return domain.ErrInstanceNotFound
|
||||
}
|
||||
if use.Source != "chub_registry" || (row.PID > 0 && use.PID > 0 && row.PID != use.PID) {
|
||||
return domain.ErrIdentityMismatch
|
||||
}
|
||||
if err := s.launcher.StopProfile(ctx, row.UserDataDir, false); err != nil {
|
||||
return err
|
||||
}
|
||||
timeout := s.waitTimeout
|
||||
if timeout <= 0 {
|
||||
timeout = 8 * time.Second
|
||||
}
|
||||
interval := s.waitInterval
|
||||
if interval <= 0 {
|
||||
interval = 100 * time.Millisecond
|
||||
}
|
||||
waitCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
for {
|
||||
use, err = s.managedProfiles.InspectProfile(waitCtx, row.UserDataDir)
|
||||
if err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
return errors.New("浏览器未在等待时间内退出;可稍后刷新状态")
|
||||
}
|
||||
return fmt.Errorf("检查浏览器退出状态失败:%w", err)
|
||||
}
|
||||
if !use.Occupied {
|
||||
return nil
|
||||
}
|
||||
timer := time.NewTimer(interval)
|
||||
select {
|
||||
case <-waitCtx.Done():
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
return errors.New("浏览器未在等待时间内退出;可稍后刷新状态")
|
||||
case <-timer.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type instanceStarter struct {
|
||||
launcher processLauncher
|
||||
resolver executableResolver
|
||||
|
||||
Reference in New Issue
Block a user