sockets: Refactor event_callback()

This refactors event_callback() to separate updating socket event
state from processing the select list (to apply socket event change)

Refactoring changes:

1) select_list_cb processing has been moved to a new local function called
select_check_waiters()
2) goto no_select_wakeup has been removed and now we use a flag
to track whether to call select_check_waiters()
3) There is a small functional change for !LWIP_TCPIP_CORE_LOCKING.
We call SYS_ARCH_UNPROTECT after saving events but before calling
select_check_waiters() (which now calls PROTECT before starting the loop).
Before the code held the PROTECT across saving the events and the first
loop iteration, but this didn't protect against anything because each loop
iteration we do an UNPROTECT/PROTECT
4) Better documentation for both LWIP_TCPIP_CORE_LOCKING and
!LWIP_TCPIP_CORE_LOCKING
This commit is contained in:
Joel Cunningham 2017-07-21 14:36:57 -05:00
parent 0ee6ad0a3a
commit c5db278746

View File

@ -273,7 +273,7 @@ static struct lwip_sock sockets[NUM_SOCKETS];
#define LWIP_SOCKET_SELECT_PROTECT(lev) SYS_ARCH_PROTECT(lev) #define LWIP_SOCKET_SELECT_PROTECT(lev) SYS_ARCH_PROTECT(lev)
#define LWIP_SOCKET_SELECT_UNPROTECT(lev) SYS_ARCH_UNPROTECT(lev) #define LWIP_SOCKET_SELECT_UNPROTECT(lev) SYS_ARCH_UNPROTECT(lev)
/** This counter is increased from lwip_select when the list is changed /** This counter is increased from lwip_select when the list is changed
and checked in event_callback to see if it has changed. */ and checked in select_check_waiters to see if it has changed. */
static volatile int select_cb_ctr; static volatile int select_cb_ctr;
#endif /* LWIP_TCPIP_CORE_LOCKING */ #endif /* LWIP_TCPIP_CORE_LOCKING */
/** The global list of tasks waiting for select */ /** The global list of tasks waiting for select */
@ -289,6 +289,7 @@ static struct lwip_select_cb *select_cb_list;
#if LWIP_SOCKET_SELECT #if LWIP_SOCKET_SELECT
static void event_callback(struct netconn *conn, enum netconn_evt evt, u16_t len); static void event_callback(struct netconn *conn, enum netconn_evt evt, u16_t len);
#define DEFAULT_SOCKET_EVENTCB event_callback #define DEFAULT_SOCKET_EVENTCB event_callback
static void select_check_waiters(int s, int has_recvevent, int has_sendevent, int has_errevent);
#else #else
#define DEFAULT_SOCKET_EVENTCB NULL #define DEFAULT_SOCKET_EVENTCB NULL
#endif #endif
@ -1896,7 +1897,7 @@ lwip_select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,
} }
select_cb_list = &select_cb; select_cb_list = &select_cb;
#if !LWIP_TCPIP_CORE_LOCKING #if !LWIP_TCPIP_CORE_LOCKING
/* Increasing this counter tells event_callback that the list has changed. */ /* Increasing this counter tells select_check_waiters that the list has changed. */
select_cb_ctr++; select_cb_ctr++;
#endif #endif
@ -1999,7 +2000,7 @@ lwip_select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,
select_cb.prev->next = select_cb.next; select_cb.prev->next = select_cb.next;
} }
#if !LWIP_TCPIP_CORE_LOCKING #if !LWIP_TCPIP_CORE_LOCKING
/* Increasing this counter tells event_callback that the list has changed. */ /* Increasing this counter tells select_check_waiters that the list has changed. */
select_cb_ctr++; select_cb_ctr++;
#endif #endif
LWIP_SOCKET_SELECT_UNPROTECT(lev2); LWIP_SOCKET_SELECT_UNPROTECT(lev2);
@ -2050,17 +2051,19 @@ return_copy_fdsets:
/** /**
* Callback registered in the netconn layer for each socket-netconn. * Callback registered in the netconn layer for each socket-netconn.
* Processes recvevent (data available) and wakes up tasks waiting for select. * Processes recvevent (data available) and wakes up tasks waiting for select.
*
* @note for LWIP_TCPIP_CORE_LOCKING any caller of this function
* must have the core lock held when signaling the following events
* as they might cause select_list_cb to be checked:
* NETCONN_EVT_RCVPLUS
* NETCONN_EVT_SENDPLUS
* NETCONN_EVT_ERROR
*/ */
static void static void
event_callback(struct netconn *conn, enum netconn_evt evt, u16_t len) event_callback(struct netconn *conn, enum netconn_evt evt, u16_t len)
{ {
int s; int s, check_waiters;
struct lwip_sock *sock; struct lwip_sock *sock;
struct lwip_select_cb *scb;
int has_recvevent, has_sendevent, has_errevent;
#if !LWIP_TCPIP_CORE_LOCKING
int last_select_cb_ctr;
#endif
SYS_ARCH_DECL_PROTECT(lev); SYS_ARCH_DECL_PROTECT(lev);
LWIP_UNUSED_ARG(len); LWIP_UNUSED_ARG(len);
@ -2096,27 +2099,28 @@ event_callback(struct netconn *conn, enum netconn_evt evt, u16_t len)
return; return;
} }
check_waiters = 1;
SYS_ARCH_PROTECT(lev); SYS_ARCH_PROTECT(lev);
/* Set event as required */ /* Set event as required */
switch (evt) { switch (evt) {
case NETCONN_EVT_RCVPLUS: case NETCONN_EVT_RCVPLUS:
sock->rcvevent++; sock->rcvevent++;
if (sock->rcvevent > 1) { if (sock->rcvevent > 1) {
goto no_select_wakeup; check_waiters = 0;
} }
break; break;
case NETCONN_EVT_RCVMINUS: case NETCONN_EVT_RCVMINUS:
sock->rcvevent--; sock->rcvevent--;
goto no_select_wakeup; check_waiters = 0;
case NETCONN_EVT_SENDPLUS: case NETCONN_EVT_SENDPLUS:
if (sock->sendevent) { if (sock->sendevent) {
goto no_select_wakeup; check_waiters = 0;
} }
sock->sendevent = 1; sock->sendevent = 1;
break; break;
case NETCONN_EVT_SENDMINUS: case NETCONN_EVT_SENDMINUS:
sock->sendevent = 0; sock->sendevent = 0;
goto no_select_wakeup; check_waiters = 0;
case NETCONN_EVT_ERROR: case NETCONN_EVT_ERROR:
sock->errevent = 1; sock->errevent = 1;
break; break;
@ -2125,26 +2129,44 @@ event_callback(struct netconn *conn, enum netconn_evt evt, u16_t len)
break; break;
} }
if (sock->select_waiting == 0) { if (sock->select_waiting && check_waiters) {
no_select_wakeup: /* Save which events are active */
/* noone is waiting for this socket, no need to check select_cb_list */ int has_recvevent, has_sendevent, has_errevent;
has_recvevent = sock->rcvevent > 0;
has_sendevent = sock->sendevent != 0;
has_errevent = sock->errevent != 0;
SYS_ARCH_UNPROTECT(lev);
/* Check any select calls waiting on this socket */
select_check_waiters(s, has_recvevent, has_sendevent, has_errevent);
} else {
SYS_ARCH_UNPROTECT(lev); SYS_ARCH_UNPROTECT(lev);
done_socket(sock);
return;
} }
done_socket(sock);
}
/* Now decide if anyone is waiting for this socket */ /**
/* NOTE: This code goes through the select_cb_list list multiple times * Check if any select waiters are waiting on this socket and its events
ONLY IF a select was actually waiting. We go through the list the number *
of waiting select calls + 1. This list is expected to be small. */ * @note on synchronization of select_cb_list:
* LWIP_TCPIP_CORE_LOCKING: the select_cb_list must only be accessed while holding
* the core lock. We do a single pass through the list and signal any waiters.
* Core lock should already be held when calling here!!!!
/* At this point, SYS_ARCH is still protected! */ * !LWIP_TCPIP_CORE_LOCKING: we use SYS_ARCH_PROTECT but unlock on each iteration
has_recvevent = sock->rcvevent > 0; * of the loop, thus creating a possibility where a thread could modify the
has_sendevent = sock->sendevent != 0; * select_cb_list during our UNPROTECT/PROTECT. We use a generational counter to
has_errevent = sock->errevent != 0; * detect this change and restart the list walk. The list is expected to be small
#if LWIP_TCPIP_CORE_LOCKING */
SYS_ARCH_UNPROTECT(lev); static void select_check_waiters(int s, int has_recvevent, int has_sendevent, int has_errevent)
#else {
struct lwip_select_cb *scb;
#if !LWIP_TCPIP_CORE_LOCKING
int last_select_cb_ctr;
SYS_ARCH_DECL_PROTECT(lev);
#endif
#if !LWIP_TCPIP_CORE_LOCKING
SYS_ARCH_PROTECT(lev);
again: again:
/* remember the state of select_cb_list to detect changes */ /* remember the state of select_cb_list to detect changes */
last_select_cb_ctr = select_cb_ctr; last_select_cb_ctr = select_cb_ctr;
@ -2171,8 +2193,9 @@ again:
} }
if (do_signal) { if (do_signal) {
scb->sem_signalled = 1; scb->sem_signalled = 1;
/* Don't call SYS_ARCH_UNPROTECT() before signaling the semaphore, as this might /* For !LWIP_TCPIP_CORE_LOCKING, we don't call SYS_ARCH_UNPROTECT() before signaling
lead to the select thread taking itself off the list, invalidating the semaphore. */ the semaphore, as this might lead to the select thread taking itself off the list,
invalidating the semaphore. */
sys_sem_signal(SELECT_SEM_PTR(scb->sem)); sys_sem_signal(SELECT_SEM_PTR(scb->sem));
} }
} }
@ -2192,7 +2215,6 @@ again:
} }
SYS_ARCH_UNPROTECT(lev); SYS_ARCH_UNPROTECT(lev);
#endif #endif
done_socket(sock);
} }
#endif /* LWIP_SOCKET_SELECT */ #endif /* LWIP_SOCKET_SELECT */