Fix a mbox free thread-safe issue that can lead to crash in sys_arch_mbox_fetch.
LWIP_ASSERT("conn has no op_completed", sys_sem_valid(&conn->op_completed));
sys_sem_free(&conn->op_completed);
#endif /* !LWIP_NETCONN_SEM_PER_THREAD */
+#if ESP_THREAD_SAFE
+ sys_mbox_set_owner(&conn->recvmbox, NULL);
+#endif
sys_mbox_free(&conn->recvmbox);
memp_free(MEMP_NETCONN, conn);
return NULL;
tcp_err(pcb, NULL);
/* remove reference from to the pcb from this netconn */
newconn->pcb.tcp = NULL;
+#if ESP_THREAD_SAFE
+ sys_mbox_set_owner(&newconn->recvmbox, NULL);
+#endif
/* no need to drain since we know the recvmbox is empty. */
sys_mbox_free(&newconn->recvmbox);
sys_mbox_set_invalid(&newconn->recvmbox);
#endif
#if ESP_THREAD_SAFE
- conn->recvmbox_ref = conn->recvmbox;
sys_mbox_set_owner(&conn->recvmbox, conn);
-#if LWIP_TCP
- sys_mbox_set_invalid(&conn->acceptmbox_ref);
-#endif
#endif
#if LWIP_TCP
+#if ESP_THREAD_SAFE
+ /* Init acceptmbox to NULL because sys_mbox_set_invalid is implemented as empty macro */
+ conn->acceptmbox = NULL;
+#endif
sys_mbox_set_invalid(&conn->acceptmbox);
#endif
conn->state = NETCONN_NONE;
netconn_free(struct netconn *conn)
{
LWIP_ASSERT("PCB must be deallocated outside this function", conn->pcb.tcp == NULL);
+
+#if !ESP_THREAD_SAFE
LWIP_ASSERT("recvmbox must be deallocated before calling this function",
!sys_mbox_valid(&conn->recvmbox));
#if LWIP_TCP
LWIP_ASSERT("acceptmbox must be deallocated before calling this function",
!sys_mbox_valid(&conn->acceptmbox));
#endif /* LWIP_TCP */
-
-#if ESP_THREAD_SAFE
- if (conn->recvmbox_ref) {
- sys_mbox_free(&conn->recvmbox_ref);
- }
+#else /* !ESP_THREAD_SAFE */
+ sys_mbox_free(&conn->recvmbox);
#if LWIP_TCP
- if (conn->acceptmbox_ref) {
- sys_mbox_free(&conn->acceptmbox_ref);
- }
-#endif
+ sys_mbox_free(&conn->acceptmbox);
#endif
+#endif /* !ESP_THREAD_SAFE */
#if !LWIP_NETCONN_SEM_PER_THREAD
sys_sem_free(&conn->op_completed);
}
if (msg->err == ERR_OK) {
#if ESP_THREAD_SAFE
- msg->conn->acceptmbox_ref = msg->conn->acceptmbox;
sys_mbox_set_owner(&msg->conn->acceptmbox, msg->conn);
#endif
msg->conn->state = NETCONN_LISTEN;
by the application thread */
sys_mbox_t acceptmbox;
#endif /* LWIP_TCP */
-
-#if ESP_THREAD_SAFE
- /** point to the same mbox as recvmbox */
- sys_mbox_t recvmbox_ref;
-#if LWIP_TCP
- /** point to the same mbox as acceptmbox */
- sys_mbox_t acceptmbox_ref;
-#endif
-#endif
/** only used for socket layer */
#if LWIP_SOCKET
int socket;
#endif\r
\r
#define sys_mbox_valid( x ) ( ( ( *x ) == NULL) ? pdFALSE : pdTRUE )\r
-#define sys_mbox_set_invalid( x ) ( ( *x ) = NULL )\r
+
+/* Define the sys_mbox_set_invalid() to empty to support lock-free mbox in ESP LWIP.
+ *
+ * The basic idea about the lock-free mbox is that the mbox should always be valid unless
+ * no socket APIs are using the socket and the socket is closed. ESP LWIP achieves this by
+ * following two changes to official LWIP:
+ * 1. Postpone the deallocation of mbox to netconn_free(), in other words, free the mbox when
+ * no one is using the socket.
+ * 2. Define the sys_mbox_set_invalid() to empty if the mbox is not actually freed.
+
+ * The second change is necessary. Consider a common scenario: the application task calls
+ * recv() to receive packets from the socket, the sys_mbox_valid() returns true. Because there
+ * is no lock for the mbox, the LWIP CORE can call sys_mbox_set_invalid() to set the mbox at
+ * anytime and the thread-safe issue may happen.
+ *
+ * However, if the sys_mbox_set_invalid() is not called after sys_mbox_free(), e.g. in netconn_alloc(),
+ * we need to initialize the mbox to invalid explicitly since sys_mbox_set_invalid() now is empty.
+ */
+#define sys_mbox_set_invalid( x )
#define sys_sem_valid( x ) ( ( ( *x ) == NULL) ? pdFALSE : pdTRUE )\r
#define sys_sem_set_invalid( x ) ( ( *x ) = NULL )\r
uint32_t mbox_message_num = 0;
if ( (NULL == mbox) || (NULL == *mbox) ) {
- ESP_LOGW(TAG, "WARNING: free null mbox\n");
return;
}
/* For recvmbox or acceptmbox, free them in netconn_free() when all sockets' API are returned */
vQueueDelete((*mbox)->os_mbox);
free(*mbox);
+ *mbox = NULL;
}
#else
vQueueDelete((*mbox)->os_mbox);
free(*mbox);
-#endif
*mbox = NULL;
+#endif
}
/*-----------------------------------------------------------------------------------*/