github doronz88/pymobiledevice3 v10.11.3

latest releases: v11.28.2, v11.28.1, v11.28.0...
one month ago

Highlights

🐛 Userspace tunnel: one eager relay listener

The dial plane previously bound one relay listener per device service port, lazily on the dial path. A deep race hunt (deterministic probes plus randomized dial/cancellation/teardown storms on both relay flavors) showed that shape leaks under concurrency — a dial racing teardown could register a listener nothing would ever close, a connect timeout could orphan a serving listener the event loop keeps alive forever, and concurrent first dials each bound their own listener. The relay now binds one listener eagerly at tunnel start and routes the target port in-band, so nothing binds on the dial path at all and those races are structurally impossible. Verified with a full probe battery (zero fd/task/socket deltas on both flavors) and live against a device at benchmark parity.

🐛 Userspace tunnel: safe concurrency for open/close

Two tasks establishing the userspace tunnel concurrently used to corrupt each other and leave the process-global network stack unusable. Every open/close transition now serializes on a lifecycle lock, and concurrent establish_userspace_rsd() callers share the winning tunnel — three concurrent callers all get the one RSD. An explicit aclose() racing the device-disconnect teardown now waits for it instead of returning early, so close-then-reopen is always safe.

🐛 Python <= 3.11: teardown could hang on a cancellation swallowed by wait_for

On older Pythons, asyncio.wait_for can swallow an external cancellation that races the awaited future's completion; the relay's header timeout hit exactly that window on slow machines, leaving tunnel teardown waiting forever. The timeout is now enforced without wait_for, and teardown retries its cancel pass as a backstop.

✨ CLI exits with a clean tunnel close

The CLI used to hard-exit (os._exit) with the tunnel abandoned. Its reasons no longer apply — teardown measures ~3 ms — so the tunnel is now closed properly at interpreter exit: the device gets clean FINs, and exit codes and other libraries' atexit handlers work normally.

📚 New guide: Understanding the network stacks

Diagrams for all four transport paths — usbmux, Wi-Fi lockdown, kernel tunnel, and the userspace tunnel (layer stack, dial sequence, and disconnect teardown):
https://doronz88.github.io/pymobiledevice3/guides/network-stacks/

What's Changed

Full Changelog: v10.11.2...v10.11.3

Don't miss a new pymobiledevice3 release

NewReleases is sending notifications on new releases.