udp: return recvmmsg-ed datagrams in order

When recvmmsg support was added it returned the datagrams in reverse
received order, which may impact some applications.

To restore the previous behavior, we call recv_cb one last time with
nread == 0 and addr == NULL so applications can free the buffer.

PR-URL: https://github.com/libuv/libuv/pull/2736
Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
This commit is contained in:
Saúl Ibarra Corretgé 2020-03-12 20:27:35 +01:00
parent 055e89f637
commit d9cd7d437d
3 changed files with 20 additions and 13 deletions

View File

@ -43,8 +43,7 @@ Data types
*/ */
UV_UDP_REUSEADDR = 4 UV_UDP_REUSEADDR = 4
/* /*
* Indicates that the message was received by recvmmsg and that it's not at * Indicates that the message was received by recvmmsg, so the buffer provided
* the beginning of the buffer allocated by alloc_cb - so the buffer provided
* must not be freed by the recv_cb callback. * must not be freed by the recv_cb callback.
*/ */
UV_UDP_MMSG_CHUNK = 8 UV_UDP_MMSG_CHUNK = 8
@ -72,9 +71,13 @@ Data types
The callee is responsible for freeing the buffer, libuv does not reuse it. The callee is responsible for freeing the buffer, libuv does not reuse it.
The buffer may be a null buffer (where `buf->base` == NULL and `buf->len` == 0) The buffer may be a null buffer (where `buf->base` == NULL and `buf->len` == 0)
on error. Don't free the buffer when the UV_UDP_MMSG_CHUNK flag is set. on error.
The final callback receives the whole buffer (containing the first chunk)
with the UV_UDP_MMSG_CHUNK flag cleared. When using :man:`recvmmsg(2)`, chunks will have the `UV_UDP_MMSG_CHUNK` flag set,
those must not be freed. There will be a final callback with `nread` set to 0,
`addr` set to NULL and the buffer pointing at the initially allocated data with
the `UV_UDP_MMSG_CHUNK` flag cleared. This is a good chance for the callee to
free the provided buffer.
.. note:: .. note::
The receive callback will be called with `nread` == 0 and `addr` == NULL when there is The receive callback will be called with `nread` == 0 and `addr` == NULL when there is
@ -373,6 +376,10 @@ API
:returns: 0 on success, or an error code < 0 on failure. :returns: 0 on success, or an error code < 0 on failure.
.. versionchanged:: 1.35.0 added support for :man:`recvmmsg(2)` on supported platforms).
The use of this feature requires a buffer larger than
2 * 64KB to be passed to `alloc_cb`.
.. c:function:: int uv_udp_recv_stop(uv_udp_t* handle) .. c:function:: int uv_udp_recv_stop(uv_udp_t* handle)
Stop listening for incoming datagrams. Stop listening for incoming datagrams.

View File

@ -607,8 +607,7 @@ enum uv_udp_flags {
*/ */
UV_UDP_REUSEADDR = 4, UV_UDP_REUSEADDR = 4,
/* /*
* Indicates that the message was received by recvmmsg and that it's not at * Indicates that the message was received by recvmmsg, so the buffer provided
* the beginning of the buffer allocated by alloc_cb - so the buffer provided
* must not be freed by the recv_cb callback. * must not be freed by the recv_cb callback.
*/ */
UV_UDP_MMSG_CHUNK = 8 UV_UDP_MMSG_CHUNK = 8

View File

@ -214,14 +214,11 @@ static int uv__udp_recvmmsg(uv_udp_t* handle, uv_buf_t* buf) {
else else
handle->recv_cb(handle, UV__ERR(errno), buf, NULL, 0); handle->recv_cb(handle, UV__ERR(errno), buf, NULL, 0);
} else { } else {
/* count to zero, so the buffer base comes last */ /* pass each chunk to the application */
for (k = nread; k > 0 && handle->recv_cb != NULL;) { for (k = 0; k < (size_t) nread && handle->recv_cb != NULL; k++) {
k--; flags = UV_UDP_MMSG_CHUNK;
flags = 0;
if (msgs[k].msg_hdr.msg_flags & MSG_TRUNC) if (msgs[k].msg_hdr.msg_flags & MSG_TRUNC)
flags |= UV_UDP_PARTIAL; flags |= UV_UDP_PARTIAL;
if (k != 0)
flags |= UV_UDP_MMSG_CHUNK;
chunk_buf = uv_buf_init(iov[k].iov_base, iov[k].iov_len); chunk_buf = uv_buf_init(iov[k].iov_base, iov[k].iov_len);
handle->recv_cb(handle, handle->recv_cb(handle,
@ -230,6 +227,10 @@ static int uv__udp_recvmmsg(uv_udp_t* handle, uv_buf_t* buf) {
msgs[k].msg_hdr.msg_name, msgs[k].msg_hdr.msg_name,
flags); flags);
} }
/* one last callback so the original buffer is freed */
if (handle->recv_cb != NULL)
handle->recv_cb(handle, 0, buf, NULL, 0);
} }
return nread; return nread;
} }