Skip to content

Fix collapsed upload throughput: backpressure instead of dropping packets on socketpair overflow#33

Open
jzthree wants to merge 1 commit into
cernekee:masterfrom
jzthree:macos-upload-backpressure
Open

Fix collapsed upload throughput: backpressure instead of dropping packets on socketpair overflow#33
jzthree wants to merge 1 commit into
cernekee:masterfrom
jzthree:macos-upload-backpressure

Conversation

@jzthree

@jzthree jzthree commented Jul 9, 2026

Copy link
Copy Markdown

Problem

lwip_data_out() silently drops upstream packets whenever writev() to the VPN socketpair returns EAGAIN/ENOBUFS (it increments a link-level drop stat and returns). On macOS, socketpair(AF_UNIX, SOCK_DGRAM) queues only ~2 datagrams by default (net.local.dgram.recvspace = 4 KB), so any sustained upload overflows the socketpair constantly. lwIP's TCP never gets fast-retransmit signals for those drops and sits in ~1 s retransmission timeouts — throughput collapses to roughly one send window per RTO.

Measured on a production AnyConnect VPN (macOS arm64): uploads capped at ~60 KB/s while downloads ran at 4.2 MB/s over the same session. Linux is less affected (~200 KB default unix-dgram buffers) but the drop-instead-of-backpressure behavior is identical.

Fix

  • On EAGAIN/EWOULDBLOCK/ENOBUFS, queue the packet (bounded FIFO, ordering preserved — new packets append while the queue is non-empty) and flush it from an EV_WRITE event when the socketpair drains. Lossless backpressure instead of silent loss.
  • Harden the VPN read path: EAGAIN/EINTR on the nonblocking socketpair is not a dead VPN, and the existing code fell through into pbuf_alloc() with a negative length after calling vpn_conn_down().

Results

before after
1 MB upload 61 KB/s 1.9 MB/s
20 MB sustained upload 3.8 MB/s
download 4.2 MB/s 4.2 MB/s (unchanged)

No configuration changes; lwipopts.h untouched.

…kets

lwip_data_out() dropped upstream packets whenever writev() to the
openconnect socketpair returned EAGAIN/ENOBUFS. On macOS the socketpair
buffers ~2 datagrams (net.local.dgram.recvspace=4KB), so upload bursts
overflowed constantly; lwIP's TCP stalled in retransmission timeouts and
uploads ran at ~one send window per RTO (~60KB/s measured) while downloads
ran at 4MB/s.

Queue the packet on EAGAIN/EWOULDBLOCK/ENOBUFS (bounded FIFO, ordering
preserved) and flush on EV_WRITE. Also harden the VPN read path: EAGAIN/
EINTR is not a dead VPN, and never fall through into pbuf_alloc() with a
negative length after vpn_conn_down().

Measured on an AnyConnect VPN (macOS arm64): uploads 61KB/s -> 1.9MB/s
single-stream, 3.8MB/s sustained; downloads unchanged at 4.2MB/s.

Co-Authored-By: Claude Fable 5 <[email protected]>
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.

1 participant