Skip to content

v0.3.4

Choose a tag to compare

@doronz88 doronz88 released this 12 Aug 23:09
· 17 commits to main since this release

Stability release: a systematic leak/liveness audit of the stack (driven by live measurement through pymobiledevice3's userspace RSD tunnel), fixing every confirmed way a session, port, waiter task, or timer could be stranded.

Highlights

  • TCP: blocked recv() waiters are now woken on every session-death transition. RST in FIN_WAIT_1/FIN_WAIT_2/CLOSING/CLOSE_WAIT, the peer's FIN entering TIME_WAIT/CLOSING, the FIN_WAIT_2 orphan reap, and the 2MSL expiry all left a parked recv() sleeping forever while the socket was unregistered under it (measured live: 24/30 abandoned tunnel relays leaked their reader tasks permanently). _change_state now owns a catch-all wake, and a reset connection raises ConnectionResetError (POSIX parity) instead of reading as a clean EOF — data received ahead of the failure is still delivered first.
  • NUD: FAILED neighbours re-resolve on next TX (Linux parity). A neighbour that missed one solicit burst (~3 s of unresponsiveness) was black-holed until process restart on small caches — GC never runs below 128 entries, so a point-to-point tunnel never recovered. FAILED entries now restart resolution on demand, drop their undeliverable queued frames, and are always GC-evictable.
  • TCP: CLOSING (simultaneous close) can no longer dead-end. The state had no timer handler, no FIN retransmission, and no give-up path: one lost ACK pinned the session and its port forever, with a 1 kHz pump spin on top. It now retransmits the FIN on the RTO ladder and collapses through the R2 budget like its siblings.
  • Datagram sockets: close() and stack.stop() wake blocked receivers with EBADF. UDP/RAW/PACKET recv() waiters were woken by nothing (stop() even skipped AF_PACKET sockets entirely); the default blocking mode parked those tasks for the loop's lifetime.
  • Zero-idle-CPU restored. Gracefully closed sessions spun the 1 ms pump through their entire TIME_WAIT; persist-timer deactivation, RACK reorder ticks, and TLP declines all left expired deadlines that re-armed the coalesced service at its 1 ms floor forever. Also fixes the RFC 8985 §6.2 boundary (>=) the RACK spin was masking.

No API breaks; recv()-family calls can now raise ConnectionResetError/ConnectionAbortedError/TimeoutError/OSError(EBADF) where they previously blocked forever or returned misleading b"".

Verified per-commit on Python 3.9 with the CI suites plus the full test suite (4145 tests), and end-to-end against a physical iOS 27.0 device over the userspace tunnel: connect/close, relay-abandonment, connection-refused, and cancelled-connect churn now return to baseline with zero stranded tasks, at unchanged throughput and latency.