Skip to content

fix(iProxy): guard releaseConnection against undefined port#2051

Merged
saikrishna321 merged 1 commit into
AppiumTestDistribution:mainfrom
outi5:fix/iproxy-release-connection
May 26, 2026
Merged

fix(iProxy): guard releaseConnection against undefined port#2051
saikrishna321 merged 1 commit into
AppiumTestDistribution:mainfrom
outi5:fix/iproxy-release-connection

Conversation

@outi5

@outi5 outi5 commented May 22, 2026

Copy link
Copy Markdown
Contributor

Bug

When a session ends, DevicePlugin.deleteSession calls DEVICE_CONNECTIONS_FACTORY.releaseConnection(device.udid) (plugin.ts:757) without a port — the intent being "release ALL listeners cached under this udid". But releaseConnection runs await checkPortStatus(port, LOCALHOST) before its (!udid && !port) guard. With port=undefined, that resolves to net.Socket.connect(undefined, '127.0.0.1') which throws TypeError [ERR_MISSING_ARGS]. The throw propagates up to the catch in plugin.ts:758, gets logged as a warn, and the cleanup body never runs.

The iProxy net.Server instances (WDA + MJPEG ports) are orphaned. Each iOS session leaks two ports. Over time the local port pool exhausts and subsequent sessions fail with The port #<N> is occupied by an other process or proxy ECONNREFUSED. The only recovery is restarting Appium.

A second related issue: even if the first throw is bypassed, line 278's await this.waitForPortTobeReleased(port, isPortBusy) is also called with port=undefined, which is meaningless.

Production log signature

[XCUITestDriver] Cached connections count: 0
[device-farm-main] Error while releasing connection for device <udid>. Error: TypeError [ERR_MISSING_ARGS]: The "options" or "port" or "path" argument must be specified

The xcuitest line confirms its own releaseConnection cleans up correctly. The bug is entirely inside device-farm.

Fix

Move the (!udid && !port) guard to the top, and gate both checkPortStatus and waitForPortTobeReleased on if (port). The cleanup body (_releaseProxiedConnections, _connectionsMapping cleanup) now runs unconditionally when called with (udid, undefined) — which is the path plugin.ts:757 exercises and which I believe is the intended semantics ("release all by udid").

   async releaseConnection(udid: any, port?: any) {
-    const isPortBusy = (await checkPortStatus(port, LOCALHOST)) === 'open';
     if (!udid && !port) {
       log.warn(
         'Neither device UDID nor local port is set. ' +
           'Did not know how to release the connection',
       );
       return;
     }
     log.info(`Releasing connections for ${udid || 'any'} device on ${port || 'any'} port number`);

     const keys = this.listConnections(udid, port, true);
     // ... (unchanged) ...
     for (const key of keys) {
       delete this._connectionsMapping[key];
     }
-    await this.waitForPortTobeReleased(port, isPortBusy);
+    if (port) {
+      const isPortBusy = (await checkPortStatus(port, LOCALHOST)) === 'open';
+      await this.waitForPortTobeReleased(port, isPortBusy);
+    }
     log.debug(`Cached connections count: ${_.size(this._connectionsMapping)}`);
   }

Reproduction

  1. Start Appium with appium-device-farm plugin and one or more real iOS devices.
  2. Run a test that creates a session against the device (e.g., a Nightwatch/WebdriverIO test).
  3. Allow the session to end normally.
  4. In the Appium log, look for Error while releasing connection ... ERR_MISSING_ARGS immediately after deleteSession. No iProxy ... Closing the connection lines appear for the deleted session.
  5. Repeat steps 2–4. Eventually new sessions fail with The port #<N> is occupied by an other process.

Validation

I compiled the patched src/iProxy.ts standalone with tsc and verified the JS output: guard moves up, port probe wrapped in if (port), cleanup body runs unconditionally. The bug is identical on main HEAD, so this fix applies to either base.

I was unable to soak-test the fix as part of a built bundle locally — v11.3.2 references private submodules so anonymous source builds are stripped of ~280 KB of code, and main includes the public-ization commit (#2039) which substantively refactors the dashboard middleware in a way that breaks WDA proxying in my environment. Happy to provide more detail on the runtime log signature from production if useful.

When deleteSession calls releaseConnection(udid) without a port,
checkPortStatus(undefined, '127.0.0.1') threw ERR_MISSING_ARGS before
the existing (!udid && !port) guard could run. The throw skipped the
cleanup body, so iProxy net.Server listeners were never closed and
cached connections leaked on every session end.

Move the guard to the top and run the port probe / wait only when
port is defined.
@outi5 outi5 force-pushed the fix/iproxy-release-connection branch from 51ca6df to c2104e0 Compare May 22, 2026 04:39
@saikrishna321 saikrishna321 merged commit d1ad9ba into AppiumTestDistribution:main May 26, 2026
1 of 5 checks passed
@luprochazka-cen63872

luprochazka-cen63872 commented Jun 3, 2026

Copy link
Copy Markdown

Hi, @saikrishna321, could you make a release with this fix, please?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants