feat: add preferred debug port planning

This commit is contained in:
QiuSW
2026-07-27 09:29:10 +08:00
parent 3f382a431d
commit 79630e0d48
10 changed files with 518 additions and 74 deletions
+39 -2
View File
@@ -164,7 +164,7 @@ func runWindow(logger *slog.Logger) {
shell.SetProxies(proxyOptions(saved.Proxies))
rows := make([]ui.InstanceRow, 0, len(saved.Instances))
for _, item := range saved.Instances {
rows = append(rows, ui.InstanceRow{ID: item.ID, Name: item.Name, Browser: browserLabel(item.Launch.Kind), UserDataDir: item.Launch.UserDataDir, TargetURL: item.Launch.TargetURL, ProxyID: item.ProxyID, Status: "已退出"})
rows = append(rows, ui.InstanceRow{ID: item.ID, Name: item.Name, Browser: browserLabel(item.Launch.Kind), UserDataDir: item.Launch.UserDataDir, TargetURL: item.Launch.TargetURL, ProxyID: item.ProxyID, PreferredRemoteDebugPort: item.PreferredRemoteDebugPort, Status: "已退出"})
}
shell.SetInstances(rows)
shell.OnSave(func(value ui.SettingsState) {
@@ -493,7 +493,11 @@ func (s instanceStarter) Start(ctx context.Context, row ui.InstanceRow, settings
if err != nil {
return ui.InstanceStartOutcome{}, fmt.Errorf("无法找到%s可执行文件:%w", browserLabel(kind), err)
}
port, err := s.portAllocator.FindAvailableRemoteDebugPort(ctx, settings.RemoteDebugStartPort)
startPort := row.PreferredRemoteDebugPort
if !domain.ValidRemoteDebugPort(startPort) {
startPort = settings.RemoteDebugStartPort
}
port, err := findAvailableUnreservedRemoteDebugPort(ctx, s.portAllocator, startPort, settings.ReservedRemoteDebugPorts)
if err != nil {
return ui.InstanceStartOutcome{}, fmt.Errorf("无法分配本地调试端口:%w", err)
}
@@ -556,6 +560,7 @@ func mergeInstanceConfig(existing []config.Instance, rows []ui.InstanceRow) []co
item.Launch.UserDataDir = row.UserDataDir
item.Launch.TargetURL = row.TargetURL
item.ProxyID = row.ProxyID
item.PreferredRemoteDebugPort = row.PreferredRemoteDebugPort
if item.ProxyID != "" {
item.Launch.ProxyServer = ""
}
@@ -565,6 +570,38 @@ func mergeInstanceConfig(existing []config.Instance, rows []ui.InstanceRow) []co
return updated
}
func findAvailableUnreservedRemoteDebugPort(ctx context.Context, allocator browser.RemoteDebugPortAllocator, start int, reserved []int) (int, error) {
if allocator == nil {
return 0, errors.New("remote debug port allocator is required")
}
if !domain.ValidRemoteDebugPort(start) {
return 0, fmt.Errorf("invalid remote debug start port %d", start)
}
reservedSet := make(map[int]struct{}, len(reserved))
for _, port := range reserved {
if domain.ValidRemoteDebugPort(port) {
reservedSet[port] = struct{}{}
}
}
for candidate := start; candidate <= domain.MaxRemoteDebugPort; {
port, err := allocator.FindAvailableRemoteDebugPort(ctx, candidate)
if err != nil {
return 0, err
}
if !domain.ValidRemoteDebugPort(port) || port < candidate {
return 0, fmt.Errorf("port allocator returned invalid port %d", port)
}
if _, reservedByOtherInstance := reservedSet[port]; !reservedByOtherInstance {
return port, nil
}
if port == domain.MaxRemoteDebugPort {
break
}
candidate = port + 1
}
return 0, errors.New("no unreserved local remote debug port is available")
}
func mergeProxyConfig(existing []config.ProxyProfile, options []ui.ProxyOption) []config.ProxyProfile {
byID := make(map[string]config.ProxyProfile, len(existing))
for _, profile := range existing {
+36 -4
View File
@@ -38,6 +38,30 @@ func TestInstanceStarterBuildsLaunchSpecFromInstanceAndSettings(t *testing.T) {
}
}
func TestInstanceStarterUsesPreferredPortAndSkipsOtherInstancesReservations(t *testing.T) {
launcher := &fakeProcessLauncher{handle: fakeProcessHandle{pid: 4242}}
remote := &fakeRemoteDebugInspector{
endpoint: browser.RemoteDebugEndpoint{Port: 9668},
inspectErr: browser.ErrRemoteDebugEndpointUnavailable,
allocatedPorts: []int{9667, 9668},
}
starter := instanceStarter{
launcher: launcher,
resolver: &fakeExecutableResolver{path: `C:\Browser\chrome.exe`},
managedProfiles: fakeManagedProfileInspector{},
externalProfiles: fakeExternalProfileInspector{},
remoteDebug: remote,
portAllocator: remote,
}
outcome, err := starter.Start(context.Background(), ui.InstanceRow{ID: "chrome-a", Browser: "Chrome", UserDataDir: `C:\profiles\chrome-a`, PreferredRemoteDebugPort: 9666}, ui.SettingsState{RemoteDebugStartPort: 9777, ReservedRemoteDebugPorts: []int{9667}}, 1)
if err != nil || outcome.RemoteDebugPort != 9668 || launcher.spec.RemoteDebugPort != 9668 {
t.Fatalf("Start() = %#v, launch = %#v, error = %v", outcome, launcher.spec, err)
}
if len(remote.allocationStarts) != 2 || remote.allocationStarts[0] != 9666 || remote.allocationStarts[1] != 9668 {
t.Fatalf("allocator starts = %#v", remote.allocationStarts)
}
}
func TestInstanceStarterResolvesSelectedProxyAtLaunch(t *testing.T) {
launcher := &fakeProcessLauncher{handle: fakeProcessHandle{pid: 4242}}
resolver := &fakeExecutableResolver{path: `C:\Browser\chrome.exe`}
@@ -341,10 +365,12 @@ func (i fakeExternalProfileInspector) InspectProfile(context.Context, domain.Bro
}
type fakeRemoteDebugInspector struct {
endpoint browser.RemoteDebugEndpoint
inspectErr error
waitErr error
port int
endpoint browser.RemoteDebugEndpoint
inspectErr error
waitErr error
port int
allocatedPorts []int
allocationStarts []int
}
func (i *fakeRemoteDebugInspector) InspectRemoteDebugEndpoint(context.Context, domain.BrowserKind, string) (browser.RemoteDebugEndpoint, error) {
@@ -364,6 +390,12 @@ func (i *fakeRemoteDebugInspector) WaitForRemoteDebugPort(context.Context, domai
}
func (i *fakeRemoteDebugInspector) FindAvailableRemoteDebugPort(_ context.Context, start int) (int, error) {
i.allocationStarts = append(i.allocationStarts, start)
if len(i.allocatedPorts) > 0 {
port := i.allocatedPorts[0]
i.allocatedPorts = i.allocatedPorts[1:]
return port, nil
}
if i.port != 0 {
return i.port, nil
}