Skip to content

AetherEngine 5.17.3

Choose a tag to compare

@superuser404notfound superuser404notfound released this 21 Jul 11:38

Fixed

EXC_RESOURCE memory-limit crash on the native loopback pipeline: the persistent source reader kept pulling from the origin while the muxer was correctly backpressure-stalled (#174).

The persistent reader (the loopback HLSVideoEngine/HLSSegmentProducer playback path's source fetch) applied window backpressure by blocking the URLSession delegate callback until the consumer drained below the 16 MB high-water mark. Blocking the delegate has no flow-control contract: whether the connection actually stops reading from the socket is a transport implementation detail. Plain HTTP/1.1 happens to park the transfer after a few MB of internal buffering, but the TLS/H2 path keeps reading at line rate and buffers the undelivered body in unbounded URLSession-internal allocations. With a realtime consumer, that buffering grows at line rate minus playback rate, goes cold, gets compressed rather than freed, and kills the app at the jetsam limit after a few minutes on a fast origin.

The reporter's evidence pinned this precisely: every engine thread idle or correctly backpressured at crash time, only the network thread active mid SSL_read; avioFetchedMB (bytes actually delivered to the reader) growing at playback rate while mallocMB grew 12x faster; vmCmp climbing ~500 MB per 30 s with rss flat; the growth invisible to Instruments' live-allocation tracking.

The reader now uses task suspend/resume, the contractual flow control ("a task, while suspended, produces no network traffic") that the streaming-mode reader already used:

  • delivery never blocks; the task suspends once the sliding window exceeds the 16 MB high-water mark,
  • the read loop resumes it when the drain crosses the new 8 MB low-water mark, and before parking in the frontier wait (a suspended task would never deliver),
  • every reconnect and teardown path (startPersistentConnection, markClosed, close, the optimistic-open fallback) balances a pending suspend before cancel so the task's suspend count stays level.

Reported by Enoch Abiodun (#174), with a debugger-level thread-stack analysis and memprobe series that led straight to the failing layer. Covered by Issue174PersistentReadBackpressureTests against a loopback origin that counts every byte it actually manages to serve: a stalled consumer must park the transfer, a resumed consumer must keep receiving, and teardown while suspended must release the task without hanging.