reactor: a write must not be truncated, and an awaited writev must hold its handle - #263
reactor: a write must not be truncated, and an awaited writev must hold its handle#263EdmondDantes wants to merge 7 commits into
Conversation
|
Second review round, two commits on top. A write past libuv's own limit is a short write, not a refusal. The awaited single-buffer write pins its handle too. A failed Suites after the round: |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
The read side of the same hazard, one commit. An awaited read holds no reference on its handle, and A reference would not do here: a read request is sometimes freed by the handle's own Suites: |
|
A maximum-effort review round killed the handle reference this PR added two rounds ago. The reference had one unbalanced exit. What replaces it costs nothing. A finished write hands the request to its awaiter and Also from that round: a fire-and-forget write above Suites after the round: |
Two defects in the write path, found while reviewing the awaited vectored write (#262).
A write longer than 32 bits was truncated and reported as complete
uv_buf_init()takes anunsigned intlength, whileuv_buf_titself carries asize_ton POSIX and aULONGon Windows. Every write site went throughuv_buf_init()and clamped:libuv_io_writeatINT_MAX,libuv_io_writevatUINT_MAXper slot,libuv_udp_sendtoby a plain cast that wraps.req->max_sizekept the caller's full length, and
io_pipe_write_cb/io_pipe_writev_cbreportmax_sizeastransferredon success — so an awaiter comparingtransferredagainst what it asked for was told a truncated write had finished. On the HTTP/1
frame path that means a chunk header announcing one length followed by a shorter
body.
async_uv_buf_set()fills the fields instead: POSIX carries the whole length, andlibuv walks its own per-buffer
size_ton a shortwritev(2). A length theplatform field cannot hold (Windows only) is refused before submit, with nothing
on the wire, rather than sent as a prefix.
An awaited vectored write did not hold its handle
The awaiter reads the status after its resume and disposes the request there, and
libuv_io_req_disposereadsreq->io. Between the completion and the resume thehandle could be closed and freed —
io_close_cbruns in the same loop turn. Afire-and-forget write needs no reference, because
uv_closeruns the pending writecallbacks before
io_close_cb; an awaited one does. It now takesZEND_ASYNC_EVENT_ADD_REFafter a successful submit and releases it in dispose,and
ASYNC_IO_REQ_F_AWAITEDmoved past the submit so a failed submit neverreleases a reference it did not take.
Also here
writev_nbufsisuint16_t,and a wrapped count made the completion release nothing — one leaked reference per
buffer.
libuv_writev_release()replaces three copies of the pre-submit release.Evidence
ext/async/tests: 1104 passed, 0 failed (188 skipped).does not belong in CI.