Conversation
lwip_tcp_send sizes a write from the tcp_sndbuf counter, but tcp_write copies into PBUF_RAM from the shared MEM_SIZE heap and returns ERR_MEM when that heap is exhausted while the counter still shows room. The retry loop then polls and retries for up to 10s, and its own comment notes that a non-blocking socket blocks there. A plain EAGAIN would be wrong: POLLOUT reads tcp_sndbuf, so a select-driven caller sees the socket writable and busy-spins. On ERR_MEM a non-blocking socket now shrinks write_len and retries to write the prefix that fits, returning that partial count, and returns ENOBUFS only when not even one byte fits -- the resource error, not would-block. Blocking sockets keep the retry loop. Signed-off-by: srgg <srggal@gmail.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #19705 +/- ##
==========================================
+ Coverage 98.55% 98.59% +0.03%
==========================================
Files 182 182
Lines 23335 23335
Branches 5 5
==========================================
+ Hits 22998 23006 +8
+ Misses 336 328 -8
Partials 1 1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Code size report: |
| if (socket->timeout == 0) { | ||
| if (write_len <= 1) { | ||
| MICROPY_PY_LWIP_EXIT | ||
| *_errno = MP_ENOBUFS; |
| // as a partial count. Only when not even one byte fits is it ENOBUFS -- a resource | ||
| // error distinct from EAGAIN, which POLLOUT (reading tcp_sndbuf, not the pool) would | ||
| // contradict into a busy-spin. | ||
| if (socket->timeout == 0) { |
There was a problem hiding this comment.
It might be a bit simpler in code logic to move this block down befor the call to poll_sockets() below.
|
@dpgeorge, thank you for the review. There is an issue with
PR reworked, three commits:
Tested on an OpenMV RT1062 (cyw43 Wi-Fi), v1.28-based tree with the same change on its
|
Summary
A non-blocking
socket.write()blocks for up to 10 s onERR_MEM(analysis in #19704).This change makes that path write the prefix that fits — halving
write_lenand retrying — and returnENOBUFSonly when nothing fits, instead of blocking.EAGAINis not used:POLLOUTreadstcp_sndbuf, so it would busy-spin aselectcaller;ENOBUFSis a resource error, not would-block. Blocking sockets are unchanged.Resolves #19704.
Testing
Measured on an OpenMV RT1062 (cyw43 Wi-Fi) streaming MJPEG to a reader throttled to 20 KB/s, on a v1.28.0-based tree: worst
write()1553086 us, 54 events over 100 ms per 320 s; the early return removes both.Not built against
masterand run on no port here — the added block copies the adjacenttcp_sndbuf == 0return, and CI compiles theMICROPY_PY_LWIPports.Trade-offs and Alternatives
Halving costs up to log2(
tcp_sndbuf)tcp_writeattempts on the exhausted pass; returningENOBUFSwith zero bytes is O(1) but makes no progress.Generative AI
I used generative AI tools when creating this PR, but a human has checked the code and is responsible for the code and the description above.