mirror of
https://github.com/espressif/esp-lwip.git
synced 2026-10-22 07:28:15 +00:00
test: Fix flaky linger socket stress bind race
Teardown aborted TIME_WAIT PCBs from the app thread and never waited on tcp_tw_pcbs, so the next case's bind(:1234) failed under load. Clean up via tcpip_api_call, and accept RST or FIN for linger timeout 0.
This commit is contained in:
+36
-31
@@ -8,6 +8,7 @@
|
||||
#include "lwip/priv/sockets_priv.h"
|
||||
#include "lwip/priv/tcp_priv.h"
|
||||
#include "lwip/api.h"
|
||||
#include "lwip/priv/tcpip_priv.h"
|
||||
|
||||
Suite *sockets_suite(void);
|
||||
|
||||
@@ -28,20 +29,21 @@ test_sockets_get_used_count(void)
|
||||
return used;
|
||||
}
|
||||
|
||||
#if !SO_REUSE
|
||||
static int
|
||||
wait_for_pcbs_to_cleanup(void)
|
||||
/* Must run on tcpip thread: abort leftover PCBs so the next test can bind :1234.
|
||||
* Use tcpip_api_call (available on 2.1.x); tcpip_callback_wait is newer only. */
|
||||
static err_t
|
||||
tcp_cleanup_pcbs(struct tcpip_api_call_data *call)
|
||||
{
|
||||
struct tcp_pcb *pcb = tcp_active_pcbs;
|
||||
while (pcb != NULL) {
|
||||
if (pcb->state == TIME_WAIT || pcb->state == LAST_ACK) {
|
||||
return -1;
|
||||
}
|
||||
pcb = pcb->next;
|
||||
}
|
||||
return 0;
|
||||
LWIP_UNUSED_ARG(call);
|
||||
while (tcp_tw_pcbs != NULL) {
|
||||
tcp_abort(tcp_tw_pcbs);
|
||||
}
|
||||
while (tcp_active_pcbs != NULL) {
|
||||
tcp_abort(tcp_active_pcbs);
|
||||
}
|
||||
return ERR_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Setups/teardown functions */
|
||||
static void
|
||||
sockets_setup(void)
|
||||
@@ -52,12 +54,12 @@ sockets_setup(void)
|
||||
static void
|
||||
sockets_teardown(void)
|
||||
{
|
||||
struct tcpip_api_call_data call;
|
||||
fail_unless(test_sockets_get_used_count() == 0);
|
||||
/* poll until all memory is released... */
|
||||
while (tcp_tw_pcbs) {
|
||||
tcp_abort(tcp_tw_pcbs);
|
||||
}
|
||||
|
||||
/* Previous closes can still be in FIN_WAIT/TIME_WAIT after sockets are gone.
|
||||
* Aborting from the app thread races the tcpip thread and misses PCBs that
|
||||
* enter TIME_WAIT after a non-blocking walk of tcp_tw_pcbs — bind(port) then flakes. */
|
||||
fail_unless(tcpip_api_call(tcp_cleanup_pcbs, &call) == ERR_OK);
|
||||
}
|
||||
|
||||
|
||||
@@ -85,8 +87,8 @@ server_thread(void *arg)
|
||||
fail_unless(ret == 0);
|
||||
|
||||
#if LWIP_SO_LINGER
|
||||
/* if we linger with timout=0, we might not be able to even accept */
|
||||
if (params->so_linger.l_linger == 0 && srv < 0) {
|
||||
/* if we linger with timeout=0, we might not be able to even accept */
|
||||
if (params->so_linger.l_onoff == 1 && params->so_linger.l_linger == 0 && srv < 0) {
|
||||
return NULL;
|
||||
}
|
||||
fail_unless(srv >= 0);
|
||||
@@ -111,8 +113,14 @@ server_thread(void *arg)
|
||||
/* try to receive (could be data or EOF if the client lingers on closing) */
|
||||
ret = lwip_recv(srv, rxbuf, sizeof(rxbuf), 0);
|
||||
err = errno;
|
||||
if (params->so_linger.l_onoff == 1 && params->so_linger.l_linger == 0 && ret < 0) {
|
||||
/* RST may arrive before any payload is readable */
|
||||
fail_unless(err == ENOTCONN || err == ECONNRESET);
|
||||
ret = lwip_close(srv);
|
||||
fail_unless(ret == 0);
|
||||
return NULL;
|
||||
}
|
||||
fail_unless(ret >= 0);
|
||||
fail_unless(err == 0);
|
||||
|
||||
if (params->so_linger.l_onoff == 1 && params->so_linger.l_linger > 0) {
|
||||
/* if lingering enabled with a non-zero timeout, let's just close
|
||||
@@ -124,13 +132,16 @@ server_thread(void *arg)
|
||||
/* otherwise, check that we could receive no longer */
|
||||
ret = lwip_recv(srv, rxbuf, sizeof(rxbuf), 0);
|
||||
err = errno;
|
||||
if (params->so_linger.l_onoff == 0 || params->so_linger.l_linger > 0) {
|
||||
/* if lingering disabled or timeout nonzero, we should get a clean exit */
|
||||
if (params->so_linger.l_onoff == 0) {
|
||||
/* lingering disabled: expect a clean FIN/EOF */
|
||||
fail_unless(ret == 0);
|
||||
} else {
|
||||
/* linger with timeout=0, expect an abrupt closure */
|
||||
fail_unless(ret == -1);
|
||||
fail_unless(err == ENOTCONN || err == ECONNRESET);
|
||||
/* linger timeout=0: RST if unacked data remained at close(), else clean FIN
|
||||
* if the payload was already ACKed (timing-dependent on loopback). */
|
||||
fail_unless(ret == -1 || ret == 0);
|
||||
if (ret == -1) {
|
||||
fail_unless(err == ENOTCONN || err == ECONNRESET);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/* close server socket */
|
||||
@@ -207,12 +218,6 @@ test_socket_close_linger(int l_onoff, int l_linger)
|
||||
#endif
|
||||
|
||||
pthread_join(srv_thread, NULL);
|
||||
#if !SO_REUSE
|
||||
while (wait_for_pcbs_to_cleanup() != 0) {
|
||||
usleep(1000);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
START_TEST(test_sockets_close_state_machine_linger_off)
|
||||
|
||||
Reference in New Issue
Block a user