This commit implements a proper version of SO_LINGER. Not sufficiently tested on initial commit.

Squashed commit of the following:

    net/: Fix some naming inconsistencies, Fix final compilation issies.

    net/inet/inet_close():  Now that we have logic to drain the buffered TX data, we can implement a proper lingering close.

    net/inet,tcp,udp:  Add functions to wait for write buffers to drain.

    net/udp:  Add support for notification when the UDP write buffer becomes empty.

    net/tcp:  Add support for notification when the TCP write buffer becomes empty.
This commit is contained in:
Gregory Nutt 2019-07-01 12:25:32 -06:00
parent ded1900927
commit de5a6163d5
19 changed files with 952 additions and 195 deletions

25
TODO
View File

@ -19,7 +19,7 @@ nuttx/:
(9) Kernel/Protected Build
(3) C++ Support
(5) Binary loaders (binfmt/)
(18) Network (net/, drivers/net)
(17) Network (net/, drivers/net)
(4) USB (drivers/usbdev, drivers/usbhost)
(2) Other drivers (drivers/)
(9) Libraries (libs/libc/, libs/libm/)
@ -1663,29 +1663,6 @@ o Network (net/, drivers/net)
anything but a well-known point-to-point configuration
impossible.
Title: SO_LINGER IMPLEMENTATION IS INCORRECT
Description: Support for the SO_LINGER socket option is implemented but
not correctly. Currently, it simply adds a timeout to the
"normal" delay for the FIN to be sent with an additional
timeout. That is not even close to the required behavior.
Per OpenGroup.org:
SO_LINGER
Lingers on a close() if data is present. This option
controls the action taken when unsent messages queue
on a socket and close() is performed. If SO_LINGER
is set, the system shall block the calling thread
during close() until it can transmit the data or
until the time expires. If SO_LINGER is not specified,
and close() is issued, the system handles the call
in a way that allows the calling thread to continue
as quickly as possible. This option takes a linger
structure, as defined in the <sys/socket.h> header,
to specify the state of the option and linger interval.
Status: Open
Priority: Medium Low.
o USB (drivers/usbdev, drivers/usbhost)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -284,11 +284,13 @@ struct work_s
enum work_evtype_e
{
WORK_IOB_AVAIL = 1, /* Notify availability of an IOB */
WORK_NET_DOWN, /* Notify that the network is down */
WORK_TCP_READAHEAD, /* Notify that TCP read-ahead data is available */
WORK_TCP_DISCONNECT, /* Notify loss of TCP connection */
WORK_UDP_READAHEAD /* Notify that TCP read-ahead data is available */
WORK_IOB_AVAIL = 1, /* Notify availability of an IOB */
WORK_NET_DOWN, /* Notify that the network is down */
WORK_TCP_READAHEAD, /* Notify that TCP read-ahead data is available */
WORK_TCP_WRITEBUFFER, /* Notify that TCP write buffer is empty */
WORK_TCP_DISCONNECT, /* Notify loss of TCP connection */
WORK_UDP_READAHEAD, /* Notify that UDP read-ahead data is available */
WORK_UDP_WRITEBUFFER /* Notify that UDP write buffer is empty */
};
/* This structure describes one notification and is provided as input to

View File

@ -52,6 +52,10 @@ ifeq ($(CONFIG_NET_IPv6),y)
SOCK_CSRCS += ipv6_setsockopt.c ipv6_getsockname.c ipv6_getpeername.c
endif
ifeq ($(CONFIG_NET_SOLINGER),y)
SOCK_CSRCS += inet_txdrain.c
endif
# Include inet build support
DEPPATH += --dep-path inet

View File

@ -305,6 +305,29 @@ ssize_t inet_recvfrom(FAR struct socket *psock, FAR void *buf, size_t len,
int inet_close(FAR struct socket *psock);
/****************************************************************************
* Name: inet_txdrain
*
* Description:
* Wait for any buffered Tx data to be sent from the socket. This is part
* of the implementation of SO_LINGER
*
* Parameters:
* psock - Pointer to the socket structure instance
* abstime - The absolute time when the timeout will occur
*
* Returned Value:
* Zero (OK) is returned on success; a negated error value is returned on
* any failure.
*
****************************************************************************/
#ifdef CONFIG_NET_SOLINGER
struct timespec;
int inet_txdrain(FAR struct socket *psock,
FAR const struct timespec *abstime);
#endif
#undef EXTERN
#if defined(__cplusplus)
}

View File

@ -56,10 +56,6 @@
#include <nuttx/net/tcp.h>
#include <nuttx/net/udp.h>
#ifdef CONFIG_NET_SOLINGER
# include <nuttx/clock.h>
#endif
#include "netdev/netdev.h"
#include "devif/devif.h"
#include "tcp/tcp.h"
@ -81,9 +77,6 @@ struct tcp_close_s
FAR struct socket *cl_psock; /* Reference to the TCP socket */
sem_t cl_sem; /* Signals disconnect completion */
int cl_result; /* The result of the close */
#ifdef CONFIG_NET_SOLINGER
clock_t cl_start; /* Time close started (in ticks) */
#endif
};
#endif
@ -91,51 +84,6 @@ struct tcp_close_s
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: tcp_close_timeout
*
* Description:
* Check for a timeout on a lingering close.
*
* Input Parameters:
* pstate - close state structure
*
* Returned Value:
* TRUE:timeout FALSE:no timeout
*
* Assumptions:
* The network is locked
*
****************************************************************************/
#if defined(NET_TCP_HAVE_STACK) && defined(CONFIG_NET_SOLINGER)
static inline int tcp_close_timeout(FAR struct tcp_close_s *pstate)
{
FAR struct socket *psock = 0;
/* Make sure that we are performing a lingering close */
if (pstate != NULL)
{
/* Yes Check for a timeout configured via setsockopts(SO_LINGER).
* If none... we well let the send wait forever.
*/
psock = pstate->cl_psock;
if (psock && psock->s_linger != 0)
{
/* Check if the configured timeout has elapsed */
return net_timeo(pstate->cl_start, psock->s_linger);
}
}
/* No timeout */
return FALSE;
}
#endif /* NET_TCP_HAVE_STACK && CONFIG_NET_SOLINGER */
/****************************************************************************
* Name: tcp_close_eventhandler
*
@ -202,19 +150,6 @@ static uint16_t tcp_close_eventhandler(FAR struct net_driver_s *dev,
}
}
#ifdef CONFIG_NET_SOLINGER
/* Check for a timeout. */
else if (pstate && tcp_close_timeout(pstate))
{
/* Yes.. Wake up the waiting thread and report the timeout */
nerr("ERROR: CLOSE timeout\n");
pstate->cl_result = -ETIMEDOUT;
goto end_wait;
}
#endif /* CONFIG_NET_SOLINGER */
#ifdef CONFIG_NET_TCP_WRITE_BUFFERS
/* Check if all outstanding bytes have been ACKed */
@ -337,6 +272,46 @@ static inline int tcp_close_disconnect(FAR struct socket *psock)
conn = (FAR struct tcp_conn_s *)psock->s_conn;
DEBUGASSERT(conn != NULL);
#ifdef CONFIG_NET_SOLINGER
/* SO_LINGER
* Lingers on a close() if data is present. This option controls the
* action taken when unsent messages queue on a socket and close() is
* performed. If SO_LINGER is set, the system shall block the calling
* thread during close() until it can transmit the data or until the
* time expires. If SO_LINGER is not specified, and close() is issued,
* the system handles the call in a way that allows the calling thread
* to continue as quickly as possible. This option takes a linger
* structure, as defined in the <sys/socket.h> header, to specify the
* state of the option and linger interval.
*/
linger = _SO_GETOPT(psock->s_options, SO_LINGER);
if (linger)
{
/* Get the current time */
DEBUGVERIFY(clock_gettime(CLOCK_REALTIME, &abstime));
/* NOTE: s_linger's unit is deciseconds so we don't need to update
* abstime.tv_nsec here.
*/
abstime.tv_sec += psock->s_linger / DSEC_PER_SEC;
/* Wait until abstime for the buffered TX data to be sent. */
ret = inet_txdrain(psock, &abstime);
if (ret < 0)
{
/* inet_txdrain may fail, but that won't stop us from closing the
* socket.
*/
nerr("ERROR: inet_txdrain() failed: %d\n", ret);
}
}
#endif
#ifdef CONFIG_NET_TCP_WRITE_BUFFERS
/* If we have a semi-permanent write buffer callback in place, then
* is needs to be be nullified.
@ -387,63 +362,13 @@ static inline int tcp_close_disconnect(FAR struct socket *psock)
nxsem_init(&state.cl_sem, 0, 0);
nxsem_setprotocol(&state.cl_sem, SEM_PRIO_NONE);
#ifdef CONFIG_NET_SOLINGER
/* Record the time that we started the wait (in ticks) */
state.cl_start = clock_systimer();
#endif
/* Notify the device driver of the availability of TX data */
tcp_close_txnotify(psock, conn);
/* Wait for the disconnect event */
#ifdef CONFIG_NET_SOLINGER
/* A non-NULL value of the priv field means that lingering is
* enabled.
*
* REVISIT: SO_LINGER is not really implemented. Per OpenGroup.org:
*
* SO_LINGER
* Lingers on a close() if data is present. This option
* controls the action taken when unsent messages queue
* on a socket and close() is performed. If SO_LINGER
* is set, the system shall block the calling thread
* during close() until it can transmit the data or
* until the time expires. If SO_LINGER is not specified,
* and close() is issued, the system handles the call
* in a way that allows the calling thread to continue
* as quickly as possible. This option takes a linger
* structure, as defined in the <sys/socket.h> header,
* to specify the state of the option and linger interval.
*
* Here it merely adds a pointless timeout on top of the normal
* close operation. It should first wait for all data in the
* protocol-specific write buffers to drain.
*/
linger = _SO_GETOPT(psock->s_options, SO_LINGER);
if (linger)
{
DEBUGVERIFY(clock_gettime(CLOCK_REALTIME, &abstime));
/* NOTE: s_linger's unit is deciseconds,
* so we don't need to update abstime.tv_nsec here.
*/
abstime.tv_sec += psock->s_linger / DSEC_PER_SEC;
if (-ETIMEDOUT == net_timedwait(&state.cl_sem, &abstime))
{
state.cl_result = -ETIMEDOUT;
}
}
else
#endif
{
(void)net_lockedwait(&state.cl_sem);
}
(void)net_lockedwait(&state.cl_sem);
/* We are now disconnected */

110
net/inet/inet_txdrain.c Normal file
View File

@ -0,0 +1,110 @@
/****************************************************************************
* net/inet/inet_txdrain.c
*
* Copyright (C) 2019 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name NuttX nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <nuttx/net/net.h>
#include "udp/udp.h"
#include "tcp/tcp.h"
#include "inet/inet.h"
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: inet_txdrain
*
* Description:
* Wait for any buffered Tx data to be sent from the socket. This is part
* of the implementation of SO_LINGER
*
* Parameters:
* psock - Pointer to the socket structure instance
* abstime - The absolute time when the timeout will occur
*
* Returned Value:
* Zero (OK) is returned on success; a negated error value is returned on
* any failure.
*
****************************************************************************/
int inet_txdrain(FAR struct socket *psock,
FAR const struct timespec *abstime)
{
int ret = OK;
DEBUGASSERT(psock != NULL);
/* Draining depends on the socket family */
switch (psock->s_type)
{
#if defined(NET_TCP_HAVE_STACK) && defined(CONFIG_NET_TCP_WRITE_BUFFERS)
case SOCK_STREAM:
{
ret = tcp_txdrain(psock, abstime);
}
break;
#endif
#if defined(NET_UDP_HAVE_STACK) && defined(CONFIG_NET_UDP_WRITE_BUFFERS)
case SOCK_DGRAM:
{
ret = udp_txdrain(psock, abstime);
}
break;
#endif
/* Other protocols do no support write buffering */
default:
break;
}
return ret;
}

View File

@ -42,8 +42,12 @@ if NET_SOCKOPTS
config NET_SOLINGER
bool "SO_LINGER socket option"
default n
depends on NET_TCP_WRITE_BUFFERS || NET_UDP_WRITE_BUFFERS
select TCP_NOTIFIER if NET_TCP
select UDP_NOTIFIER if NET_UDP
---help---
Enable or disable support for the SO_LINGER socket option.
Enable or disable support for the SO_LINGER socket option. Requires
write buffer support.
endif # NET_SOCKOPTS
endmenu # Socket Support

View File

@ -56,6 +56,9 @@ ifeq ($(CONFIG_NET_TCP_READAHEAD),y)
SOCK_CSRCS += tcp_netpoll.c
ifeq ($(CONFIG_TCP_NOTIFIER),y)
SOCK_CSRCS += tcp_notifier.c
ifeq ($(CONFIG_NET_TCP_WRITE_BUFFERS),y)
SOCK_CSRCS += tcp_txdrain.c
endif
endif
endif

View File

@ -1558,7 +1558,7 @@ int tcp_pollteardown(FAR struct socket *psock, FAR struct pollfd *fds);
* Input Parameters:
* worker - The worker function to execute on the high priority work
* queue when data is available in the TCP read-ahead buffer.
* conn - The TCP connection where read-ahead data is needed.
* conn - The TCP connection where read-ahead data is needed.
* arg - A user-defined argument that will be available to the worker
* function when it runs.
*
@ -1581,7 +1581,40 @@ int tcp_readahead_notifier_setup(worker_t worker,
#endif
/****************************************************************************
* Name: tcp_readahead_disconnect_setup
* Name: tcp_writebuffer_notifier_setup
*
* Description:
* Set up to perform a callback to the worker function when an TCP write
* buffer is emptied. The worker function will execute on the high
* priority worker thread.
*
* Input Parameters:
* worker - The worker function to execute on the high priority work
* queue when all buffer TX data has been sent.
* conn - The TCP connection where buffer write data is pending.
* arg - A user-defined argument that will be available to the worker
* function when it runs.
*
* Returned Value:
* > 0 - The signal notification is in place. The returned value is a
* key that may be used later in a call to
* tcp_notifier_teardown().
* == 0 - There is already buffered read-ahead data. No signal
* notification will be provided.
* < 0 - An unexpected error occurred and no signal will be sent. The
* returned value is a negated errno value that indicates the
* nature of the failure.
*
****************************************************************************/
#ifdef CONFIG_TCP_NOTIFIER
int tcp_writebuffer_notifier_setup(worker_t worker,
FAR struct tcp_conn_s *conn,
FAR void *arg);
#endif
/****************************************************************************
* Name: tcp_disconnect_notifier_setup
*
* Description:
* Set up to perform a callback to the worker function if the TCP
@ -1607,9 +1640,9 @@ int tcp_readahead_notifier_setup(worker_t worker,
****************************************************************************/
#ifdef CONFIG_TCP_NOTIFIER
int tcp_readahead_disconnect_setup(worker_t worker,
FAR struct tcp_conn_s *conn,
FAR void *arg);
int tcp_disconnect_notifier_setup(worker_t worker,
FAR struct tcp_conn_s *conn,
FAR void *arg);
#endif
/****************************************************************************
@ -1656,10 +1689,35 @@ int tcp_notifier_teardown(int key);
*
****************************************************************************/
#ifdef CONFIG_TCP_NOTIFIER
#if defined(CONFIG_NET_TCP_READAHEAD) && defined(CONFIG_TCP_NOTIFIER)
void tcp_readahead_signal(FAR struct tcp_conn_s *conn);
#endif
/****************************************************************************
* Name: tcp_writebuffer_signal
*
* Description:
* All buffer Tx data has been sent. Signal all threads waiting for the
* write buffers to become empty.
*
* When write buffer becomes empty, *all* of the workers waiting
* for that event data will be executed. If there are multiple workers
* waiting for read-ahead data then only the first to execute will get the
* data. Others will need to call tcp_writebuffer_notifier_setup() once
* again.
*
* Input Parameters:
* conn - The TCP connection where read-ahead data was just buffered.
*
* Returned Value:
* None.
*
****************************************************************************/
#if defined(CONFIG_NET_TCP_WRITE_BUFFERS) && defined(CONFIG_TCP_NOTIFIER)
void tcp_writebuffer_signal(FAR struct tcp_conn_s *conn);
#endif
/****************************************************************************
* Name: tcp_disconnect_signal
*
@ -1679,6 +1737,30 @@ void tcp_readahead_signal(FAR struct tcp_conn_s *conn);
void tcp_disconnect_signal(FAR struct tcp_conn_s *conn);
#endif
/****************************************************************************
* Name: tcp_txdrain
*
* Description:
* Wait for all write buffers to be sent (or for a timeout to occur).
*
* Input Parameters:
* psock - An instance of the internal socket structure.
* abstime - The absolute time when the timeout will occur
*
* Returned Value:
* Zero (OK) is returned on success; a negated errno value is returned
* on any failure.
*
****************************************************************************/
#if defined(CONFIG_NET_TCP_WRITE_BUFFERS) && defined(CONFIG_TCP_NOTIFIER)
struct timespec;
int tcp_txdrain(FAR struct socket *psock,
FAR const struct timespec *abstime);
#else
# define udp_txdrain(conn, abstime) (0)
#endif
#undef EXTERN
#ifdef __cplusplus
}

View File

@ -1,7 +1,7 @@
/****************************************************************************
* net/tcp/tcp_notifier.c
*
* Copyright (C) 2018 Gregory Nutt. All rights reserved.
* Copyright (C) 2018-2019 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* Redistribution and use in source and binary forms, with or without
@ -84,6 +84,7 @@ int tcp_readahead_notifier_setup(worker_t worker,
FAR struct tcp_conn_s *conn,
FAR void *arg)
{
#ifdef CONFIG_NET_TCP_READAHEAD
struct work_notifier_s info;
DEBUGASSERT(worker != NULL);
@ -106,10 +107,72 @@ int tcp_readahead_notifier_setup(worker_t worker,
info.worker = worker;
return work_notifier_setup(&info);
#else
return 0;
#endif
}
/****************************************************************************
* Name: tcp_readahead_disconnect_setup
* Name: tcp_writebuffer_notifier_setup
*
* Description:
* Set up to perform a callback to the worker function when an TCP write
* buffer is emptied. The worker function will execute on the high
* priority worker thread.
*
* Input Parameters:
* worker - The worker function to execute on the high priority work
* queue when all buffer TX data has been sent.
* conn - The TCP connection where buffer write data is pending.
* arg - A user-defined argument that will be available to the worker
* function when it runs.
*
* Returned Value:
* > 0 - The signal notification is in place. The returned value is a
* key that may be used later in a call to
* tcp_notifier_teardown().
* == 0 - There is already buffered read-ahead data. No signal
* notification will be provided.
* < 0 - An unexpected error occurred and no signal will be sent. The
* returned value is a negated errno value that indicates the
* nature of the failure.
*
****************************************************************************/
int tcp_writebuffer_notifier_setup(worker_t worker,
FAR struct tcp_conn_s *conn,
FAR void *arg)
{
#ifdef CONFIG_NET_TCP_WRITE_BUFFERS
struct work_notifier_s info;
DEBUGASSERT(worker != NULL);
/* If the write buffers are already empty, then return zero without
* setting up the notification.
*/
if (sq_empty(&conn->write_q) && sq_empty(&conn->unacked_q))
{
return 0;
}
/* Otherwise, this is just a simple wrapper around work_notifer_setup(). */
info.evtype = WORK_TCP_WRITEBUFFER;
info.qid = LPWORK;
info.qualifier = conn;
info.arg = arg;
info.worker = worker;
return work_notifier_setup(&info);
#else
return 0;
#endif
}
/****************************************************************************
* Name: tcp_disconnect_notifier_setup
*
* Description:
* Set up to perform a callback to the worker function if the TCP
@ -133,9 +196,9 @@ int tcp_readahead_notifier_setup(worker_t worker,
*
****************************************************************************/
int tcp_readahead_disconnect_setup(worker_t worker,
FAR struct tcp_conn_s *conn,
FAR void *arg)
int tcp_disconnect_notifier_setup(worker_t worker,
FAR struct tcp_conn_s *conn,
FAR void *arg)
{
struct work_notifier_s info;
@ -206,12 +269,44 @@ int tcp_notifier_teardown(int key)
*
****************************************************************************/
#ifdef CONFIG_NET_TCP_READAHEAD
void tcp_readahead_signal(FAR struct tcp_conn_s *conn)
{
/* This is just a simple wrapper around work_notifier_signal(). */
return work_notifier_signal(WORK_TCP_READAHEAD, conn);
}
#endif
/****************************************************************************
* Name: tcp_writebuffer_signal
*
* Description:
* All buffer Tx data has been sent. Signal all threads waiting for the
* write buffers to become empty.
*
* When write buffer becomes empty, *all* of the workers waiting
* for that event data will be executed. If there are multiple workers
* waiting for read-ahead data then only the first to execute will get the
* data. Others will need to call tcp_writebuffer_notifier_setup() once
* again.
*
* Input Parameters:
* conn - The TCP connection where read-ahead data was just buffered.
*
* Returned Value:
* None.
*
****************************************************************************/
#ifdef CONFIG_NET_TCP_WRITE_BUFFERS
void tcp_writebuffer_signal(FAR struct tcp_conn_s *conn)
{
/* This is just a simple wrapper around work_notifier_signal(). */
return work_notifier_signal(WORK_TCP_WRITEBUFFER, conn);
}
#endif
/****************************************************************************
* Name: tcp_disconnect_signal

View File

@ -158,6 +158,37 @@ static void psock_insert_segment(FAR struct tcp_wrbuffer_s *wrb,
}
}
/****************************************************************************
* Name: psock_writebuffer_notify
*
* Description:
* The TCP connection has been lost. Free all write buffers.
*
* Input Parameters:
* psock The socket structure
* conn The connection structure associated with the socket
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_TCP_NOTIFIER
static void psock_writebuffer_notify(FAR struct tcp_conn_s *conn)
{
/* Check if all write buffers have been sent and ACKed */
if (sq_empty(&conn->write_q) && sq_empty(&conn->unacked_q))
{
/* Notify any waiters that the write buffers have been drained. */
tcp_writebuffer_signal(conn);
}
}
#else
# define psock_writebuffer_notify(conn)
#endif
/****************************************************************************
* Name: psock_lost_connection
*
@ -207,6 +238,11 @@ static inline void psock_lost_connection(FAR struct socket *psock,
sq_init(&conn->unacked_q);
sq_init(&conn->write_q);
/* Notify any waiters if the write buffers have been drained. */
psock_writebuffer_notify(conn);
conn->sent = 0;
conn->sndseq_max = 0;
}
@ -508,6 +544,12 @@ static uint16_t psock_send_eventhandler(FAR struct net_driver_s *dev,
/* And return the write buffer to the pool of free buffers */
tcp_wrbuffer_release(wrb);
/* Notify any waiters if the write buffers have been
* drained.
*/
psock_writebuffer_notify(conn);
}
else
{
@ -664,6 +706,12 @@ static uint16_t psock_send_eventhandler(FAR struct net_driver_s *dev,
tcp_wrbuffer_release(wrb);
/* Notify any waiters if the write buffers have been
* drained.
*/
psock_writebuffer_notify(conn);
/* NOTE expired is different from un-ACKed, it is designed to
* represent the number of segments that have been sent,
* retransmitted, and un-ACKed, if expired is not zero, the
@ -722,6 +770,12 @@ static uint16_t psock_send_eventhandler(FAR struct net_driver_s *dev,
tcp_wrbuffer_release(wrb);
/* Notify any waiters if the write buffers have been
* drained.
*/
psock_writebuffer_notify(conn);
/* NOTE expired is different from un-ACKed, it is designed to
* represent the number of segments that have been sent,
* retransmitted, and un-ACKed, if expired is not zero, the

146
net/tcp/tcp_txdrain.c Normal file
View File

@ -0,0 +1,146 @@
/****************************************************************************
* net/tcp/tcp_txdrain.c
*
* Copyright (C) 2019 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name NuttX nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <semaphore.h>
#include <sched.h>
#include <time.h>
#include <assert.h>
#include <errno.h>
#include <nuttx/wqueue.h>
#include <nuttx/net/net.h>
#include "tcp/tcp.h"
#if defined(CONFIG_NET_TCP_WRITE_BUFFERS) && defined(CONFIG_TCP_NOTIFIER)
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: txdrain_worker
*
* Description:
* Called with the write buffers have all been sent.
*
* Input Parameters:
* arg - The semaphore that will wake up tcp_txdrain
*
* Returned Value:
* None.
*
****************************************************************************/
static void txdrain_worker(FAR void *arg)
{
FAR sem_t *waitsem = (FAR sem_t *)arg;
DEBUGASSERT(waitsem != NULL);
sem_post(waitsem);
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: tcp_txdrain
*
* Description:
* Wait for all write buffers to be sent (or for a timeout to occur).
*
* Input Parameters:
* psock - An instance of the internal socket structure.
* abstime - The absolute time when the timeout will occur
*
* Returned Value:
* Zero (OK) is returned on success; a negated errno value is returned
* on any failure.
*
****************************************************************************/
int tcp_txdrain(FAR struct socket *psock,
FAR const struct timespec *abstime)
{
FAR struct tcp_conn_s *conn;
sem_t waitsem;
int ret;
DEBUGASSERT(psock != NULL && psock->s_crefs > 0 && psock->s_conn != NULL);
DEBUGASSERT(psock->s_type == SOCK_DGRAM);
conn = (FAR struct tcp_conn_s *)psock->s_conn;
/* Initialize the wait semaphore */
nxsem_init(&waitsem, 0, 0);
/* The following needs to be done with the network stable */
net_lock();
ret = tcp_writebuffer_notifier_setup(txdrain_worker, conn, &waitsem);
if (ret > 0)
{
int key = ret;
/* There is pending write data.. wait for it to drain. */
do
{
ret = net_timedwait(&waitsem, abstime);
}
while (ret == EINTR);
/* Tear down the notifier (in case we timed out or were canceled) */
if (ret < 0)
{
tcp_notifier_teardown(key);
}
}
net_unlock();
nxsem_destroy(&waitsem);
return ret;
}
#endif /* CONFIG_NET_TCP_WRITE_BUFFERS && CONFIG_TCP_NOTIFIER */

View File

@ -57,18 +57,6 @@ config NET_UDP_READAHEAD
select NET_READAHEAD
select MM_IOB
config UDP_READAHEAD_NOTIFIER
bool "Support UDP read-ahead notifications"
default n
depends on NET_UDP_READAHEAD && SCHED_WORKQUEUE
select WQUEUE_NOTIFIER
---help---
Enable building of UDP read-ahead notifier logic that will execute a
worker function on the high priority work queue when read-ahead data
is available. This is is a general purpose notifier, but was
developed specifically to support poll() logic where the poll must
wait for read-ahead data to become available.
config NET_UDP_WRITE_BUFFERS
bool "Enable UDP/IP write buffering"
default n
@ -117,5 +105,17 @@ config NET_UDP_WRBUFFER_DUMP
endif # NET_UDP_WRITE_BUFFERS
config UDP_NOTIFIER
bool "Support UDP read-ahead notifications"
default n
depends on SCHED_WORKQUEUE
select WQUEUE_NOTIFIER
---help---
Enable building of UDP read-ahead notifier logic that will execute a
worker function on the high priority work queue when read-ahead data
is available. This is is a general purpose notifier, but was
developed specifically to support poll() logic where the poll must
wait for read-ahead data to become available.
endif # NET_UDP && !NET_UDP_NO_STACK
endmenu # UDP Networking

View File

@ -54,9 +54,12 @@ endif
ifeq ($(CONFIG_NET_UDP_READAHEAD),y)
SOCK_CSRCS += udp_netpoll.c
ifeq ($(CONFIG_UDP_READAHEAD_NOTIFIER),y)
ifeq ($(CONFIG_UDP_NOTIFIER),y)
SOCK_CSRCS += udp_notifier.c
endif
ifeq ($(CONFIG_NET_UDP_WRITE_BUFFERS),y)
SOCK_CSRCS += udp_txdrain.c
endif
endif
# Transport layer

View File

@ -53,7 +53,7 @@
# include <nuttx/mm/iob.h>
#endif
#ifdef CONFIG_UDP_READAHEAD_NOTIFIER
#ifdef CONFIG_UDP_NOTIFIER
# include <nuttx/wqueue.h>
#endif
@ -671,7 +671,7 @@ int udp_pollteardown(FAR struct socket *psock, FAR struct pollfd *fds);
#endif
/****************************************************************************
* Name: udp_notifier_setup
* Name: udp_readahead_notifier_setup
*
* Description:
* Set up to perform a callback to the worker function when an UDP data
@ -696,9 +696,42 @@ int udp_pollteardown(FAR struct socket *psock, FAR struct pollfd *fds);
*
****************************************************************************/
#ifdef CONFIG_UDP_READAHEAD_NOTIFIER
int udp_notifier_setup(worker_t worker, FAR struct udp_conn_s *conn,
FAR void *arg);
#ifdef CONFIG_UDP_NOTIFIER
int udp_readahead_notifier_setup(worker_t worker,
FAR struct udp_conn_s *conn,
FAR void *arg);
#endif
/****************************************************************************
* Name: udp_writebuffer_notifier_setup
*
* Description:
* Set up to perform a callback to the worker function when an UDP write
* buffer is emptied. The worker function will execute on the high
* priority worker thread.
*
* Input Parameters:
* worker - The worker function to execute on the low priority work
* queue when data is available in the UDP read-ahead buffer.
* conn - The UDP connection where read-ahead data is needed.
* arg - A user-defined argument that will be available to the worker
* function when it runs.
*
* Returned Value:
* > 0 - The notification is in place. The returned value is a key that
* may be used later in a call to udp_notifier_teardown().
* == 0 - There is already buffered read-ahead data. No notification
* will be provided.
* < 0 - An unexpected error occurred and no notification will occur.
* The returned value is a negated errno value that indicates the
* nature of the failure.
*
****************************************************************************/
#ifdef CONFIG_UDP_NOTIFIER
int udp_writebuffer_notifier_setup(worker_t worker,
FAR struct udp_conn_s *conn,
FAR void *arg);
#endif
/****************************************************************************
@ -706,13 +739,13 @@ int udp_notifier_setup(worker_t worker, FAR struct udp_conn_s *conn,
*
* Description:
* Eliminate a UDP read-ahead notification previously setup by
* udp_notifier_setup(). This function should only be called if the
* udp_readahead_notifier_setup(). This function should only be called if the
* notification should be aborted prior to the notification. The
* notification will automatically be torn down after the notification.
*
* Input Parameters:
* key - The key value returned from a previous call to
* udp_notifier_setup().
* udp_readahead_notifier_setup().
*
* Returned Value:
* Zero (OK) is returned on success; a negated errno value is returned on
@ -720,12 +753,12 @@ int udp_notifier_setup(worker_t worker, FAR struct udp_conn_s *conn,
*
****************************************************************************/
#ifdef CONFIG_UDP_READAHEAD_NOTIFIER
#ifdef CONFIG_UDP_NOTIFIER
int udp_notifier_teardown(int key);
#endif
/****************************************************************************
* Name: udp_notifier_signal
* Name: udp_readahead_signal
*
* Description:
* Read-ahead data has been buffered. Notify all threads waiting for
@ -734,7 +767,7 @@ int udp_notifier_teardown(int key);
* When read-ahead data becomes available, *all* of the workers waiting
* for read-ahead data will be executed. If there are multiple workers
* waiting for read-ahead data then only the first to execute will get the
* data. Others will need to call udp_notifier_setup() once again.
* data. Others will need to call udp_readahead_notifier_setup() once again.
*
* Input Parameters:
* conn - The UDP connection where read-ahead data was just buffered.
@ -744,8 +777,57 @@ int udp_notifier_teardown(int key);
*
****************************************************************************/
#ifdef CONFIG_UDP_READAHEAD_NOTIFIER
void udp_notifier_signal(FAR struct udp_conn_s *conn);
#if defined(CONFIG_NET_UDP_READAHEAD) && defined(CONFIG_UDP_NOTIFIER)
void udp_readahead_signal(FAR struct udp_conn_s *conn);
#endif
/****************************************************************************
* Name: udp_writebuffer_signal
*
* Description:
* All buffer Tx data has been sent. Signal all threads waiting for the
* write buffers to become empty.
*
* When write buffer becomes empty, *all* of the workers waiting
* for that event data will be executed. If there are multiple workers
* waiting for read-ahead data then only the first to execute will get the
* data. Others will need to call tcp_writebuffer_notifier_setup() once
* again.
*
* Input Parameters:
* conn - The UDP connection where read-ahead data was just buffered.
*
* Returned Value:
* None.
*
****************************************************************************/
#if defined(CONFIG_NET_UDP_WRITE_BUFFERS) && defined(CONFIG_UDP_NOTIFIER)
void udp_writebuffer_signal(FAR struct udp_conn_s *conn);
#endif
/****************************************************************************
* Name: udp_txdrain
*
* Description:
* Wait for all write buffers to be sent (or for a timeout to occur).
*
* Input Parameters:
* psock - An instance of the internal socket structure.
* abstime - The absolute time when the timeout will occur
*
* Returned Value:
* Zero (OK) is returned on success; a negated errno value is returned
* on any failure.
*
****************************************************************************/
#if defined(CONFIG_NET_UDP_WRITE_BUFFERS) && defined(CONFIG_UDP_NOTIFIER)
struct timespec;
int udp_txdrain(FAR struct socket *psock,
FAR const struct timespec *abstime);
#else
# define udp_txdrain(conn, abstime) (0)
#endif
#undef EXTERN

View File

@ -230,12 +230,12 @@ static uint16_t udp_datahandler(FAR struct net_driver_s *dev, FAR struct udp_con
return 0;
}
#ifdef CONFIG_UDP_READAHEAD_NOTIFIER
#ifdef CONFIG_UDP_NOTIFIER
/* Provided notification(s) that additional UDP read-ahead data is
* available.
*/
udp_notifier_signal(conn);
udp_readahead_signal(conn);
#endif
ninfo("Buffered %d bytes\n", buflen);

View File

@ -1,7 +1,7 @@
/****************************************************************************
* net/udp/udp_notifier.c
*
* Copyright (C) 2018 Gregory Nutt. All rights reserved.
* Copyright (C) 2018-2019 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* Redistribution and use in source and binary forms, with or without
@ -47,14 +47,14 @@
#include "udp/udp.h"
#ifdef CONFIG_UDP_READAHEAD_NOTIFIER
#ifdef CONFIG_UDP_NOTIFIER
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: udp_notifier_setup
* Name: udp_readahead_notifier_setup
*
* Description:
* Set up to perform a callback to the worker function when an UDP data
@ -79,9 +79,11 @@
*
****************************************************************************/
int udp_notifier_setup(worker_t worker, FAR struct udp_conn_s *conn,
FAR void *arg)
int udp_readahead_notifier_setup(worker_t worker,
FAR struct udp_conn_s *conn,
FAR void *arg)
{
#ifdef CONFIG_NET_UDP_READAHEAD
struct work_notifier_s info;
DEBUGASSERT(worker != NULL);
@ -104,6 +106,67 @@ int udp_notifier_setup(worker_t worker, FAR struct udp_conn_s *conn,
info.worker = worker;
return work_notifier_setup(&info);
#else
return 0;
#endif
}
/****************************************************************************
* Name: udp_writebuffer_notifier_setup
*
* Description:
* Set up to perform a callback to the worker function when an UDP write
* buffer is emptied. The worker function will execute on the high
* priority worker thread.
*
* Input Parameters:
* worker - The worker function to execute on the low priority work
* queue when data is available in the UDP read-ahead buffer.
* conn - The UDP connection where read-ahead data is needed.
* arg - A user-defined argument that will be available to the worker
* function when it runs.
*
* Returned Value:
* > 0 - The notification is in place. The returned value is a key that
* may be used later in a call to udp_notifier_teardown().
* == 0 - There is already buffered read-ahead data. No notification
* will be provided.
* < 0 - An unexpected error occurred and no notification will occur.
* The returned value is a negated errno value that indicates the
* nature of the failure.
*
****************************************************************************/
int udp_writebuffer_notifier_setup(worker_t worker,
FAR struct udp_conn_s *conn,
FAR void *arg)
{
#ifdef CONFIG_NET_UDP_WRITE_BUFFERS
struct work_notifier_s info;
DEBUGASSERT(worker != NULL);
/* If there is already buffered read-ahead data, then return zero without
* setting up the notification.
*/
if (sq_empty(&conn->write_q))
{
return 0;
}
/* Otherwise, this is just a simple wrapper around work_notifer_setup(). */
info.evtype = WORK_UDP_WRITEBUFFER;
info.qid = LPWORK;
info.qualifier = conn;
info.arg = arg;
info.worker = worker;
return work_notifier_setup(&info);
#else
return 0;
#endif
}
/****************************************************************************
@ -111,13 +174,13 @@ int udp_notifier_setup(worker_t worker, FAR struct udp_conn_s *conn,
*
* Description:
* Eliminate a UDP read-ahead notification previously setup by
* udp_notifier_setup(). This function should only be called if the
* udp_readahead_notifier_setup(). This function should only be called if the
* notification should be aborted prior to the notification. The
* notification will automatically be torn down after the notification.
*
* Input Parameters:
* key - The key value returned from a previous call to
* udp_notifier_setup().
* udp_readahead_notifier_setup().
*
* Returned Value:
* Zero (OK) is returned on success; a negated errno value is returned on
@ -133,7 +196,7 @@ int udp_notifier_teardown(int key)
}
/****************************************************************************
* Name: udp_notifier_signal
* Name: udp_readahead_signal
*
* Description:
* Read-ahead data has been buffered. Notify all threads waiting for
@ -142,7 +205,7 @@ int udp_notifier_teardown(int key)
* When read-ahead data becomes available, *all* of the workers waiting
* for read-ahead data will be executed. If there are multiple workers
* waiting for read-ahead data then only the first to execute will get the
* data. Others will need to call udp_notifier_setup() once again.
* data. Others will need to call udp_readahead_notifier_setup() once again.
*
* Input Parameters:
* conn - The UDP connection where read-ahead data was just buffered.
@ -152,11 +215,43 @@ int udp_notifier_teardown(int key)
*
****************************************************************************/
void udp_notifier_signal(FAR struct udp_conn_s *conn)
#ifdef CONFIG_NET_UDP_READAHEAD
void udp_readahead_signal(FAR struct udp_conn_s *conn)
{
/* This is just a simple wrapper around work_notifier_signal(). */
return work_notifier_signal(WORK_UDP_READAHEAD, conn);
}
#endif
#endif /* CONFIG_UDP_READAHEAD_NOTIFIER */
/****************************************************************************
* Name: udp_writebuffer_signal
*
* Description:
* All buffer Tx data has been sent. Signal all threads waiting for the
* write buffers to become empty.
*
* When write buffer becomes empty, *all* of the workers waiting
* for that event data will be executed. If there are multiple workers
* waiting for read-ahead data then only the first to execute will get the
* data. Others will need to call tcp_writebuffer_notifier_setup() once
* again.
*
* Input Parameters:
* conn - The UDP connection where read-ahead data was just buffered.
*
* Returned Value:
* None.
*
****************************************************************************/
#ifdef CONFIG_NET_UDP_WRITE_BUFFERS
void udp_writebuffer_signal(FAR struct udp_conn_s *conn)
{
/* This is just a simple wrapper around work_notifier_signal(). */
return work_notifier_signal(WORK_UDP_WRITEBUFFER, conn);
}
#endif
#endif /* CONFIG_UDP_NOTIFIER */

View File

@ -167,6 +167,12 @@ static void sendto_writebuffer_release(FAR struct socket *psock,
psock->s_sndcb->priv = NULL;
psock->s_sndcb->event = NULL;
wrb = NULL;
#ifdef CONFIG_TCP_NOTIFIER
/* Notify any waiters that the write buffers have been drained. */
udp_writebuffer_signal(conn);
#endif
}
else
{

146
net/udp/udp_txdrain.c Normal file
View File

@ -0,0 +1,146 @@
/****************************************************************************
* net/udp/udp_txdrain.c
*
* Copyright (C) 2019 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name NuttX nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <semaphore.h>
#include <sched.h>
#include <time.h>
#include <assert.h>
#include <errno.h>
#include <nuttx/wqueue.h>
#include <nuttx/net/net.h>
#include "udp/udp.h"
#if defined(CONFIG_NET_UDP_WRITE_BUFFERS) && defined(CONFIG_UDP_NOTIFIER)
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: txdrain_worker
*
* Description:
* Called with the write buffers have all been sent.
*
* Input Parameters:
* arg - The semaphore that will wake up udp_txdrain
*
* Returned Value:
* None.
*
****************************************************************************/
static void txdrain_worker(FAR void *arg)
{
FAR sem_t *waitsem = (FAR sem_t *)arg;
DEBUGASSERT(waitsem != NULL);
sem_post(waitsem);
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: udp_txdrain
*
* Description:
* Wait for all write buffers to be sent (or for a timeout to occur).
*
* Input Parameters:
* psock - An instance of the internal socket structure.
* abstime - The absolute time when the timeout will occur
*
* Returned Value:
* Zero (OK) is returned on success; a negated errno value is returned
* on any failure.
*
****************************************************************************/
int udp_txdrain(FAR struct socket *psock,
FAR const struct timespec *abstime)
{
FAR struct udp_conn_s *conn;
sem_t waitsem;
int ret;
DEBUGASSERT(psock != NULL && psock->s_crefs > 0 && psock->s_conn != NULL);
DEBUGASSERT(psock->s_type == SOCK_DGRAM);
conn = (FAR struct udp_conn_s *)psock->s_conn;
/* Initialize the wait semaphore */
nxsem_init(&waitsem, 0, 0);
/* The following needs to be done with the network stable */
net_lock();
ret = udp_writebuffer_notifier_setup(txdrain_worker, conn, &waitsem);
if (ret > 0)
{
int key = ret;
/* There is pending write data.. wait for it to drain. */
do
{
ret = net_timedwait(&waitsem, abstime);
}
while (ret == EINTR);
/* Tear down the notifier (in case we timed out or were canceled) */
if (ret < 0)
{
udp_notifier_teardown(key);
}
}
net_unlock();
nxsem_destroy(&waitsem);
return ret;
}
#endif /* CONFIG_NET_UDP_WRITE_BUFFERS && CONFIG_UDP_NOTIFIER */