mirror of
https://github.com/apache/nuttx.git
synced 2026-05-27 19:36:35 +08:00
tcp_send_buffered.c: fix iob allocation deadlock
Ensure to put the wrb back onto the write_q when blocking on iob allocation. Otherwise, it can deadlock with other threads doing the same thing.
This commit is contained in:
committed by
Xiang Xiao
parent
5b1f2dff5f
commit
98ec46d726
+92
-77
@@ -1129,69 +1129,73 @@ ssize_t psock_tcp_send(FAR struct socket *psock, FAR const void *buf,
|
|||||||
}
|
}
|
||||||
#endif /* CONFIG_NET_SEND_BUFSIZE */
|
#endif /* CONFIG_NET_SEND_BUFSIZE */
|
||||||
|
|
||||||
/* Allocate a write buffer. Careful, the network will be momentarily
|
while (true)
|
||||||
* unlocked here.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Try to coalesce into the last wrb.
|
|
||||||
*
|
|
||||||
* But only when it might yield larger segments.
|
|
||||||
* (REVISIT: It might make sense to lift this condition.
|
|
||||||
* IOB boundaries and segment boundaries usually do not match.
|
|
||||||
* It makes sense to save the number of IOBs.)
|
|
||||||
*
|
|
||||||
* Also, for simplicity, do it only when we haven't sent anything
|
|
||||||
* from the the wrb yet.
|
|
||||||
*/
|
|
||||||
|
|
||||||
max_wrb_size = tcp_max_wrb_size(conn);
|
|
||||||
wrb = (FAR struct tcp_wrbuffer_s *)sq_tail(&conn->write_q);
|
|
||||||
if (wrb != NULL && TCP_WBSENT(wrb) == 0 && TCP_WBNRTX(wrb) == 0 &&
|
|
||||||
TCP_WBPKTLEN(wrb) < max_wrb_size &&
|
|
||||||
(TCP_WBPKTLEN(wrb) % conn->mss) != 0)
|
|
||||||
{
|
{
|
||||||
wrb = (FAR struct tcp_wrbuffer_s *)sq_remlast(&conn->write_q);
|
struct iob_s *iob;
|
||||||
ninfo("coalesce %zu bytes to wrb %p (%" PRIu16 ")\n", len, wrb,
|
|
||||||
TCP_WBPKTLEN(wrb));
|
|
||||||
DEBUGASSERT(TCP_WBPKTLEN(wrb) > 0);
|
|
||||||
}
|
|
||||||
else if (nonblock)
|
|
||||||
{
|
|
||||||
wrb = tcp_wrbuffer_tryalloc();
|
|
||||||
ninfo("new wrb %p (non blocking)\n", wrb);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
wrb = tcp_wrbuffer_alloc();
|
|
||||||
ninfo("new wrb %p\n", wrb);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wrb == NULL)
|
/* Allocate a write buffer. Careful, the network will be
|
||||||
{
|
* momentarily unlocked here.
|
||||||
/* A buffer allocation error occurred */
|
*/
|
||||||
|
|
||||||
nerr("ERROR: Failed to allocate write buffer\n");
|
/* Try to coalesce into the last wrb.
|
||||||
ret = nonblock ? -EAGAIN : -ENOMEM;
|
*
|
||||||
goto errout_with_lock;
|
* But only when it might yield larger segments.
|
||||||
}
|
* (REVISIT: It might make sense to lift this condition.
|
||||||
|
* IOB boundaries and segment boundaries usually do not match.
|
||||||
|
* It makes sense to save the number of IOBs.)
|
||||||
|
*
|
||||||
|
* Also, for simplicity, do it only when we haven't sent anything
|
||||||
|
* from the the wrb yet.
|
||||||
|
*/
|
||||||
|
|
||||||
/* Initialize the write buffer */
|
max_wrb_size = tcp_max_wrb_size(conn);
|
||||||
|
wrb = (FAR struct tcp_wrbuffer_s *)sq_tail(&conn->write_q);
|
||||||
|
if (wrb != NULL && TCP_WBSENT(wrb) == 0 && TCP_WBNRTX(wrb) == 0 &&
|
||||||
|
TCP_WBPKTLEN(wrb) < max_wrb_size &&
|
||||||
|
(TCP_WBPKTLEN(wrb) % conn->mss) != 0)
|
||||||
|
{
|
||||||
|
wrb = (FAR struct tcp_wrbuffer_s *)sq_remlast(&conn->write_q);
|
||||||
|
ninfo("coalesce %zu bytes to wrb %p (%" PRIu16 ")\n", len, wrb,
|
||||||
|
TCP_WBPKTLEN(wrb));
|
||||||
|
DEBUGASSERT(TCP_WBPKTLEN(wrb) > 0);
|
||||||
|
}
|
||||||
|
else if (nonblock)
|
||||||
|
{
|
||||||
|
wrb = tcp_wrbuffer_tryalloc();
|
||||||
|
ninfo("new wrb %p (non blocking)\n", wrb);
|
||||||
|
DEBUGASSERT(TCP_WBPKTLEN(wrb) == 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wrb = tcp_wrbuffer_alloc();
|
||||||
|
ninfo("new wrb %p\n", wrb);
|
||||||
|
DEBUGASSERT(TCP_WBPKTLEN(wrb) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
TCP_WBSEQNO(wrb) = (unsigned)-1;
|
if (wrb == NULL)
|
||||||
TCP_WBNRTX(wrb) = 0;
|
{
|
||||||
|
/* A buffer allocation error occurred */
|
||||||
|
|
||||||
off = TCP_WBPKTLEN(wrb);
|
nerr("ERROR: Failed to allocate write buffer\n");
|
||||||
if (off + chunk_len > max_wrb_size)
|
ret = nonblock ? -EAGAIN : -ENOMEM;
|
||||||
{
|
goto errout_with_lock;
|
||||||
chunk_len = max_wrb_size - off;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Copy the user data into the write buffer. We cannot wait for
|
/* Initialize the write buffer */
|
||||||
* buffer space if the socket was opened non-blocking.
|
|
||||||
*/
|
TCP_WBSEQNO(wrb) = (unsigned)-1;
|
||||||
|
TCP_WBNRTX(wrb) = 0;
|
||||||
|
|
||||||
|
off = TCP_WBPKTLEN(wrb);
|
||||||
|
if (off + chunk_len > max_wrb_size)
|
||||||
|
{
|
||||||
|
chunk_len = max_wrb_size - off;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy the user data into the write buffer. We cannot wait for
|
||||||
|
* buffer space.
|
||||||
|
*/
|
||||||
|
|
||||||
if (nonblock)
|
|
||||||
{
|
|
||||||
/* The return value from TCP_WBTRYCOPYIN is either OK or
|
/* The return value from TCP_WBTRYCOPYIN is either OK or
|
||||||
* -ENOMEM if less than the entire data chunk could be allocated.
|
* -ENOMEM if less than the entire data chunk could be allocated.
|
||||||
* If -ENOMEM is returned, check if at least a part of the data
|
* If -ENOMEM is returned, check if at least a part of the data
|
||||||
@@ -1220,34 +1224,48 @@ ssize_t psock_tcp_send(FAR struct socket *psock, FAR const void *buf,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
nerr("ERROR: Failed to add data to the I/O chain\n");
|
chunk_result = 0;
|
||||||
ret = -EWOULDBLOCK;
|
|
||||||
goto errout_with_wrb;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DEBUGASSERT(chunk_result == chunk_len);
|
DEBUGASSERT(chunk_result == chunk_len);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
unsigned int count;
|
|
||||||
int blresult;
|
|
||||||
|
|
||||||
/* iob_copyin might wait for buffers to be freed, but if network is
|
if (chunk_result > 0)
|
||||||
* locked this might never happen, since network driver is also
|
{
|
||||||
* locked, therefore we need to break the lock
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* release wrb */
|
||||||
|
|
||||||
|
if (TCP_WBPKTLEN(wrb) > 0)
|
||||||
|
{
|
||||||
|
DEBUGASSERT(TCP_WBSENT(wrb) == 0);
|
||||||
|
DEBUGASSERT(TCP_WBPKTLEN(wrb) > 0);
|
||||||
|
sq_addlast(&wrb->wb_node, &conn->write_q);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tcp_wrbuffer_release(wrb);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nonblock)
|
||||||
|
{
|
||||||
|
nerr("ERROR: Failed to add data to the I/O chain\n");
|
||||||
|
ret = -EAGAIN;
|
||||||
|
goto errout_with_lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for at least one IOB getting available.
|
||||||
|
*
|
||||||
|
* Note: net_ioballoc releases the network lock when blocking.
|
||||||
|
* It allows our write_q being drained in the meantime. Otherwise,
|
||||||
|
* we risk a deadlock with other threads competing on IOBs.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
blresult = net_breaklock(&count);
|
iob = net_ioballoc(true, IOBUSER_NET_TCP_WRITEBUFFER);
|
||||||
ninfo("starting copyin to wrb %p\n", wrb);
|
iob_free_chain(iob, IOBUSER_NET_TCP_WRITEBUFFER);
|
||||||
chunk_result = TCP_WBCOPYIN(wrb, cp, chunk_len, off);
|
|
||||||
ninfo("finished copyin to wrb %p\n", wrb);
|
|
||||||
if (blresult >= 0)
|
|
||||||
{
|
|
||||||
net_restorelock(count);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dump I/O buffer chain */
|
/* Dump I/O buffer chain */
|
||||||
@@ -1311,9 +1329,6 @@ ssize_t psock_tcp_send(FAR struct socket *psock, FAR const void *buf,
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
errout_with_wrb:
|
|
||||||
tcp_wrbuffer_release(wrb);
|
|
||||||
|
|
||||||
errout_with_lock:
|
errout_with_lock:
|
||||||
net_unlock();
|
net_unlock();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user