diff --git a/internal/driver/ioscompanion/device.go b/internal/driver/ioscompanion/device.go index e1170d2..6f474ad 100644 --- a/internal/driver/ioscompanion/device.go +++ b/internal/driver/ioscompanion/device.go @@ -23,7 +23,7 @@ import ( // carried here: realSpawnDeviceRunner reads them from the environment at the // point of use so secrets never reach the Options struct or run artifacts. type DeviceOptions struct { - // HardwareUDID feeds xcodebuild -destination and iproxy -u. + // HardwareUDID feeds xcodebuild -destination and the usbmux device match. HardwareUDID string // CoreDeviceID feeds devicectl install/uninstall. CoreDeviceID string @@ -39,7 +39,7 @@ type DeviceOptions struct { // Test seams. Production leaves them nil and NewDevice wires the real // build/spawn/tunnel/dial. spawnRunner func(ctx context.Context, address string) (*exec.Cmd, error) - spawnTunnel func(ctx context.Context, hardwareUDID, localPort, devicePort string) (*exec.Cmd, error) + startTunnel func(ctx context.Context, hardwareUDID, localAddress, devicePort string) (io.Closer, error) dialRunner func(address string) (transport.Companion, error) pickAddress func() (string, error) } @@ -80,13 +80,13 @@ func NewDevice(ctx context.Context, options DeviceOptions) (*Driver, error) { deviceMode: true, hybrid: false, spawnRunner: options.spawnRunner, - spawnTunnel: options.spawnTunnel, + startTunnel: options.startTunnel, } if d.spawnRunner == nil { d.spawnRunner = d.realSpawnDeviceRunner } - if d.spawnTunnel == nil { - d.spawnTunnel = spawnTunnel + if d.startTunnel == nil { + d.startTunnel = startUsbmuxTunnel } d.dialRunner = options.dialRunner if d.dialRunner == nil { @@ -147,25 +147,27 @@ func (d *Driver) bringUpDevice(ctx context.Context) error { } d.runnerChild = runnerChild - tunnelChild, err := d.spawnTunnel(d.processContext, d.udid, port, port) + // The forwarder listens on the host loopback port and bridges to the same + // port number on the device, where the runner listens. + tunnel, err := d.startTunnel(d.processContext, d.udid, address, port) if err != nil { d.stopRunnerChild() - return fmt.Errorf("spawn tunnel: %w", err) + return fmt.Errorf("start tunnel: %w", err) } - d.tunnelChild = tunnelChild + d.tunnel = tunnel startupCtx, cancel := context.WithTimeout(ctx, deviceStartupTimeout) defer cancel() if err := waitForListener(startupCtx, address); err != nil { - d.stopTunnelChild() + d.stopTunnel() d.stopRunnerChild() return fmt.Errorf("device runner listener: %w", err) } companion, err := d.dialRunner(address) if err != nil { - d.stopTunnelChild() + d.stopTunnel() d.stopRunnerChild() return fmt.Errorf("dial device runner: %w", err) } @@ -173,7 +175,7 @@ func (d *Driver) bringUpDevice(ctx context.Context) error { if err := d.waitForHealth(startupCtx); err != nil { _ = companion.Close() - d.stopTunnelChild() + d.stopTunnel() d.stopRunnerChild() return fmt.Errorf("device runner health: %w", err) } @@ -189,7 +191,7 @@ func (d *Driver) respawnDevice(ctx context.Context) error { _ = d.companion.Close() } d.stopRunnerChild() - d.stopTunnelChild() + d.stopTunnel() return d.bringUpDevice(ctx) } diff --git a/internal/driver/ioscompanion/driver.go b/internal/driver/ioscompanion/driver.go index f398ae5..373dbbd 100644 --- a/internal/driver/ioscompanion/driver.go +++ b/internal/driver/ioscompanion/driver.go @@ -127,12 +127,12 @@ type Driver struct { // Device-mode fields. On the physical-device path d.companion is the runner // dialed over a usbmux tunnel, hybrid is false, and runnerClient is nil. - // coreDeviceID feeds devicectl; tunnelChild is the iproxy process forwarding - // the host loopback port to the runner's device-side port. + // coreDeviceID feeds devicectl; tunnel is the in-process usbmux forwarder + // bridging the host loopback port to the runner's device-side port. deviceMode bool coreDeviceID string - tunnelChild *exec.Cmd - spawnTunnel func(ctx context.Context, hardwareUDID, localPort, devicePort string) (*exec.Cmd, error) + tunnel io.Closer + startTunnel func(ctx context.Context, hardwareUDID, localAddress, devicePort string) (io.Closer, error) pickDeviceAddress func() (string, error) // processContext owns the companion child's lifetime: it is derived from @@ -995,19 +995,21 @@ func (d *Driver) Close() { d.runnerClient = nil } d.stopRunnerChild() - d.stopTunnelChild() + d.stopTunnel() if d.processCancel != nil { d.processCancel() } } -// stopTunnelChild terminates the iproxy usbmux tunnel on the device path. -// SIGTERM lets it close its forwarded sockets before exit; a nil child (the -// simulator path) is a no-op. -func (d *Driver) stopTunnelChild() { - child := d.tunnelChild - d.tunnelChild = nil - stopProcess(child) +// stopTunnel closes the in-process usbmux forwarder on the device path. Closing +// its listener ends the accept loop and lets the open bridges drain; a nil +// tunnel (the simulator path) is a no-op. +func (d *Driver) stopTunnel() { + tunnel := d.tunnel + d.tunnel = nil + if tunnel != nil { + _ = tunnel.Close() + } } // stopChild terminates the companion child gracefully (SIGTERM, grace window,