summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormzgrebnak <[email protected]>2026-05-29 15:15:07 +0200
committerGitHub <[email protected]>2026-05-29 09:15:07 -0400
commit53070b69eed6af6840b0a890fe749e73e3f280f2 (patch)
tree20d4f69b9c9de9c302dfa609cbee5eb8a32adabc
parent57410350d94bbcb5e6885d0fbc40c72b6d7ddd33 (diff)
Implemented BSD select improvements (#375)
* initial support * bsd: address PR review feedback - Fix accept() regression: preserve accepted socket FD even when recreating the secondary listen socket fails due to slot exhaustion. Set secondary_socket_id = NX_BSD_MAX_SOCKETS to invalidate the slot for future accept() calls, but still return the already-accepted FD. - Restore select() writefds 'not in use' case: closed/unallocated descriptors must be reported immediately as writable per BSD semantics. Several tests (netx_bsd_tcp_basic_blocking_test, _rcvbuf_test, _getaddrinfo_test) depend on this behaviour. - Fix select() exceptfds race with readfds: the readfds scan calls nx_tcp_socket_receive() which dequeues the head packet; a subsequent peek at nx_tcp_socket_receive_queue_head in the exceptfds scan then finds NULL. Move the URG/push-flag check into the readfds scan immediately after the successful dequeue while the packet is still accessible. - Add regression test netx_bsd_select_improvements_test covering: * zero-timeout select clears all fdsets without blocking * normal TCP data sets readfds, not exceptfds * a not-in-use descriptor appears in writefds Register test in regression/CMakeLists.txt. --------- Co-authored-by: Frédéric Desbiens <[email protected]> Co-authored-by: Copilot <[email protected]>
-rw-r--r--addons/BSD/nxd_bsd.c94
-rw-r--r--test/cmake/netxduo/regression/CMakeLists.txt3
-rw-r--r--test/regression/bsd_test/netx_bsd_select_improvements_test.c316
3 files changed, 404 insertions, 9 deletions
diff --git a/addons/BSD/nxd_bsd.c b/addons/BSD/nxd_bsd.c
index 202b39be..10a2dee5 100644
--- a/addons/BSD/nxd_bsd.c
+++ b/addons/BSD/nxd_bsd.c
@@ -139,6 +139,7 @@ static UINT nx_bsd_isxdigit(UCHAR c);
/* Standard BSD callback functions to register with NetX Duo. */
static VOID nx_bsd_tcp_receive_notify(NX_TCP_SOCKET *socket_ptr);
+static VOID nx_bsd_tcp_exception_notify(NX_TCP_SOCKET *socket_ptr);
static VOID nx_bsd_tcp_socket_disconnect_notify(NX_TCP_SOCKET *socket_ptr);
static VOID nx_bsd_udp_receive_notify(NX_UDP_SOCKET *socket_ptr);
#ifdef NX_ENABLE_IP_RAW_PACKET_FILTER
@@ -934,7 +935,8 @@ UINT index;
including the ones covered by tcp_socket_disconnect_notify. */
status = nx_tcp_socket_create(nx_bsd_default_ip, tcp_socket_ptr, "NetX BSD TCP Socket",
- NX_IP_NORMAL, NX_FRAGMENT_OKAY, NX_IP_TIME_TO_LIVE, NX_BSD_TCP_WINDOW, NX_NULL,
+ NX_IP_NORMAL, NX_FRAGMENT_OKAY, NX_IP_TIME_TO_LIVE, NX_BSD_TCP_WINDOW,
+ nx_bsd_tcp_exception_notify,
nx_bsd_tcp_socket_disconnect_notify);
/* Check for a successful status. */
@@ -3014,8 +3016,11 @@ struct nx_bsd_sockaddr_in6
/* Reset the master_socket_id */
ret = nx_bsd_tcp_create_listen_socket(sockID, 0);
- if(ret < 0)
+ if (ret < 0)
{
+ /* Could not create the next listen socket (e.g. all BSD socket slots are in use).
+ Mark the secondary slot invalid so it is not reused, but still return the
+ already-accepted socket descriptor to the caller. */
(bsd_socket_ptr -> nx_bsd_socket_union_id).nx_bsd_socket_secondary_socket_id = NX_BSD_MAX_SOCKETS;
}
@@ -3747,7 +3752,7 @@ USHORT peer_port = 0;
/* This is a UDP or raw socket. */
- /* Check whther or not the socket is AF_PACKET family. */
+ /* Check whether or not the socket is AF_PACKET family. */
if(bsd_socket_ptr -> nx_bsd_socket_family == AF_PACKET)
{
@@ -4248,7 +4253,7 @@ struct nx_bsd_sockaddr_in6
status = nx_tcp_socket_receive(tcp_socket_ptr, &packet_ptr, TX_NO_WAIT);
/* Check for no packet on the queue. */
- if (status == NX_NOT_CONNECTED)
+ if (status == NX_NOT_CONNECTED || status == NX_NOT_BOUND)
{
/* Release the protection mutex. */
@@ -5041,6 +5046,8 @@ UINT index;
tx_mutex_put(nx_bsd_protection_ptr);
+ nx_bsd_select_wakeup((UINT)sockID, FDSET_READ);
+
/* Return */
return(NX_SOC_OK);
@@ -5120,6 +5127,8 @@ UINT index;
/* Release the protection mutex. */
tx_mutex_put(nx_bsd_protection_ptr);
+ nx_bsd_select_wakeup((UINT)sockID, FDSET_READ);
+
return(NX_SOC_OK);
}
@@ -8156,6 +8165,21 @@ INT ret;
nx_bsd_socket_array[i].nx_bsd_socket_received_byte_count += packet_ptr -> nx_packet_length;
nx_bsd_socket_array[i].nx_bsd_socket_received_packet_count++;
+ /* Check for urgent data (URG bit) now, while the packet is still
+ accessible. The exceptfds scan runs after readfds and would miss
+ this packet because nx_tcp_socket_receive() has already removed it
+ from the TCP receive queue. */
+ if (exceptfds && NX_BSD_FD_ISSET(i + NX_BSD_SOCKFD_START, exceptfds))
+ {
+ NX_TCP_HEADER *tcp_header_ptr;
+
+ tcp_header_ptr = (NX_TCP_HEADER *)packet_ptr -> nx_packet_prepend_ptr;
+ if (tcp_header_ptr -> nx_tcp_header_word_3 & NX_TCP_URG_BIT)
+ {
+ NX_BSD_FD_SET(i + NX_BSD_SOCKFD_START, &exceptfds_found);
+ }
+ }
+
/* Add this socket to the read ready list. */
NX_BSD_FD_SET(i + NX_BSD_SOCKFD_START, &readfds_found);
}
@@ -8204,16 +8228,17 @@ INT ret;
/* Yes, decrement the number of read selectors left to search for. */
writefds_left--;
- /* Is this BSD socket in use? */
+ /* Is this BSD socket not in use? */
if (!(nx_bsd_socket_array[i].nx_bsd_socket_status_flags & NX_BSD_SOCKET_IN_USE))
{
- /* Yes, add this socket to the write ready list. */
+ /* Closed/unallocated descriptor: report as writable. */
NX_BSD_FD_SET(i + NX_BSD_SOCKFD_START, &writefds_found);
}
- /* Check to see if there is a connection request pending. */
- else if (nx_bsd_socket_array[i].nx_bsd_socket_status_flags & NX_BSD_SOCKET_CONNECTION_REQUEST)
+ /* Check to see if there is a connection request pending on a client socket. */
+ else if ((nx_bsd_socket_array[i].nx_bsd_socket_status_flags & NX_BSD_SOCKET_CONNECTION_REQUEST) &&
+ !(nx_bsd_socket_array[i].nx_bsd_socket_status_flags & NX_BSD_SOCKET_SERVER_MASTER_SOCKET))
{
/* Yes, add this socket to the write ready list. */
@@ -8226,6 +8251,21 @@ INT ret;
NX_BSD_FD_SET(i + NX_BSD_SOCKFD_START, &writefds_found);
}
+ /* Is this a TCP socket that is connected? */
+ else if ((nx_bsd_socket_array[i].nx_bsd_socket_tcp_socket) &&
+ (nx_bsd_socket_array[i].nx_bsd_socket_status_flags & NX_BSD_SOCKET_CONNECTED))
+ {
+
+ /* Yes, add this socket to the write ready list. */
+ NX_BSD_FD_SET(i + NX_BSD_SOCKFD_START, &writefds_found);
+ }
+ /* Is this BSD socket bound? */
+ else if (nx_bsd_socket_array[i].nx_bsd_socket_status_flags & NX_BSD_SOCKET_BOUND)
+ {
+
+ /* Yes, add this socket to the write ready list. */
+ NX_BSD_FD_SET(i + NX_BSD_SOCKFD_START, &writefds_found);
+ }
}
}
@@ -8337,6 +8377,12 @@ INT ret;
This happens if the wait option is set to zero. */
if (status == TX_NO_EVENTS)
{
+ if(readfds)
+ NX_BSD_FD_ZERO(readfds);
+ if(writefds)
+ NX_BSD_FD_ZERO(writefds);
+ if(exceptfds)
+ NX_BSD_FD_ZERO(exceptfds);
/* Determine if the effected sockets are non blocking (zero ticks for the wait option). */
if (ticks == 0)
@@ -8451,6 +8497,38 @@ UINT bsd_socket_index;
}
+
+static VOID nx_bsd_tcp_exception_notify(NX_TCP_SOCKET *socket_ptr)
+{
+UINT bsd_socket_index;
+
+ /* Figure out what BSD socket this is. */
+ bsd_socket_index = (UINT) socket_ptr -> nx_tcp_socket_reserved_ptr;
+
+ /* Determine if this is a good index into the BSD socket array. */
+ if (bsd_socket_index >= NX_BSD_MAX_SOCKETS)
+ {
+
+ /* Bad socket index... simply return! */
+ return;
+ }
+
+ /* Now check if the socket may have been released (e.g. socket closed) while
+ waiting for the mutex. */
+ if( socket_ptr -> nx_tcp_socket_id == 0 )
+ {
+
+ return;
+ }
+
+ /* Check the suspended socket list for one ready to receive or send packets. */
+ nx_bsd_select_wakeup(bsd_socket_index, FDSET_EXCEPTION);
+
+
+ return;
+}
+
+
/**************************************************************************/
/* */
/* FUNCTION RELEASE */
diff --git a/test/cmake/netxduo/regression/CMakeLists.txt b/test/cmake/netxduo/regression/CMakeLists.txt
index 7acc2783..4dbad145 100644
--- a/test/cmake/netxduo/regression/CMakeLists.txt
+++ b/test/cmake/netxduo/regression/CMakeLists.txt
@@ -54,7 +54,8 @@ if("-DNX_BSD_ENABLE" IN_LIST ${CMAKE_BUILD_TYPE})
${SOURCE_DIR}/bsd_test/netx_bsd_tcp_rcvbuf_test.c
${SOURCE_DIR}/bsd_test/netx_bsd_tcp_fionread_test.c
${SOURCE_DIR}/bsd_test/netx_bsd_select_spurious_event_test.c
- ${SOURCE_DIR}/bsd_test/netx_bsd_socket_options_test.c)
+ ${SOURCE_DIR}/bsd_test/netx_bsd_socket_options_test.c
+ ${SOURCE_DIR}/bsd_test/netx_bsd_select_improvements_test.c)
if("-DNX_BSD_RAW_SUPPORT" IN_LIST ${CMAKE_BUILD_TYPE})
list(
APPEND
diff --git a/test/regression/bsd_test/netx_bsd_select_improvements_test.c b/test/regression/bsd_test/netx_bsd_select_improvements_test.c
new file mode 100644
index 00000000..86aba67f
--- /dev/null
+++ b/test/regression/bsd_test/netx_bsd_select_improvements_test.c
@@ -0,0 +1,316 @@
+/***************************************************************************
+ * Copyright (C) 2026 Eclipse ThreadX contributors
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the MIT License which is available at
+ * https://opensource.org/licenses/MIT.
+ *
+ * AI Disclosure: This file was largely AI-generated by Copilot (Sonnet 4.6).
+ * The AI-generated portions may be considered public domain (CC0-1.0)
+ * and not subject to the project's licence. The human contributor has
+ * reviewed and verified that the code is correct.
+ *
+ * SPDX-License-Identifier: MIT and CC0-1.0
+ **************************************************************************/
+
+/* This test covers select() improvements from PR #375:
+ *
+ * 1. TCP readfds + exceptfds in one select() call — normal (non-urgent) data
+ * must set readfds but MUST NOT set exceptfds.
+ *
+ * 2. select() with zero timeout and no ready sockets must return 0 and must
+ * clear all output fd sets (TX_NO_EVENTS path).
+ *
+ * 3. A socket that is not in use must be reported in writefds (regression for
+ * the writefds "not in use" restore).
+ */
+
+#include "tx_api.h"
+#include "nx_api.h"
+#if defined(NX_BSD_ENABLE) && !defined(NX_DISABLE_IPV4)
+#include "nxd_bsd.h"
+
+#define DEMO_STACK_SIZE 4096
+#define BSD_THREAD_PRIORITY 2
+
+static TX_THREAD ntest_0;
+static TX_THREAD ntest_1;
+
+static NX_PACKET_POOL pool_0;
+static NX_IP ip_0;
+static NX_IP ip_1;
+static TX_SEMAPHORE sema_0;
+static TX_SEMAPHORE sema_1;
+
+static ULONG error_counter;
+
+static ULONG packet_pool_area[(512 + sizeof(NX_PACKET)) * 32 / 4];
+
+static void ntest_0_entry(ULONG thread_input);
+static void ntest_1_entry(ULONG thread_input);
+extern void test_control_return(UINT status);
+extern void _nx_ram_network_driver_256(struct NX_IP_DRIVER_STRUCT *driver_req);
+
+static ULONG bsd_thread_area[DEMO_STACK_SIZE / sizeof(ULONG)];
+
+#ifdef CTEST
+VOID test_application_define(void *first_unused_memory)
+#else
+void netx_bsd_select_improvements_test_application_define(void *first_unused_memory)
+#endif
+{
+CHAR *pointer = (CHAR *)first_unused_memory;
+UINT status;
+
+ error_counter = 0;
+
+ tx_thread_create(&ntest_0, "thread 0", ntest_0_entry, 0,
+ pointer, DEMO_STACK_SIZE, 3, 3,
+ TX_NO_TIME_SLICE, TX_AUTO_START);
+ pointer += DEMO_STACK_SIZE;
+
+ tx_thread_create(&ntest_1, "thread 1", ntest_1_entry, 0,
+ pointer, DEMO_STACK_SIZE, 3, 3,
+ TX_NO_TIME_SLICE, TX_AUTO_START);
+ pointer += DEMO_STACK_SIZE;
+
+ nx_system_initialize();
+
+ status = nx_packet_pool_create(&pool_0, "NetX Main Packet Pool", 512,
+ packet_pool_area, sizeof(packet_pool_area));
+ if (status)
+ error_counter++;
+
+ status = nx_ip_create(&ip_0, "NetX IP Instance 0",
+ IP_ADDRESS(1, 2, 3, 4), 0xFFFFFF00UL,
+ &pool_0, _nx_ram_network_driver_256,
+ pointer, 2048, 1);
+ pointer += 2048;
+ if (status)
+ error_counter++;
+
+ status = nx_ip_create(&ip_1, "NetX IP Instance 1",
+ IP_ADDRESS(1, 2, 3, 5), 0xFFFFFF00UL,
+ &pool_0, _nx_ram_network_driver_256,
+ pointer, 2048, 1);
+ pointer += 2048;
+ if (status)
+ error_counter++;
+
+ status = nx_arp_enable(&ip_0, (void *)pointer, 1024);
+ pointer += 1024;
+ if (status)
+ error_counter++;
+
+ status = nx_arp_enable(&ip_1, (void *)pointer, 1024);
+ pointer += 1024;
+ if (status)
+ error_counter++;
+
+ status = nx_tcp_enable(&ip_0);
+ status += nx_tcp_enable(&ip_1);
+ if (status)
+ error_counter++;
+
+ status = bsd_initialize(&ip_0, &pool_0, (CHAR *)&bsd_thread_area[0],
+ sizeof(bsd_thread_area), BSD_THREAD_PRIORITY);
+ if (status)
+ error_counter++;
+
+ status = tx_semaphore_create(&sema_0, "SEMA 0", 0);
+ status += tx_semaphore_create(&sema_1, "SEMA 1", 0);
+ if (status)
+ error_counter++;
+}
+
+/* Server thread: exercises select() with readfds + exceptfds. */
+static void ntest_0_entry(ULONG thread_input)
+{
+struct sockaddr_in server_addr;
+struct sockaddr_in client_addr;
+INT server_sock;
+INT conn_sock;
+INT client_addr_len;
+INT n;
+INT nfd;
+struct timeval tv;
+fd_set readfds;
+fd_set writefds;
+fd_set exceptfds;
+char buf[64];
+
+ NX_PARAMETER_NOT_USED(thread_input);
+
+ /* ================================================================
+ * Set up TCP server socket.
+ * ================================================================ */
+ server_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (server_sock < 0)
+ error_counter++;
+
+ memset(&server_addr, 0, sizeof(server_addr));
+ server_addr.sin_family = AF_INET;
+ server_addr.sin_port = htons(5555);
+ server_addr.sin_addr.s_addr = INADDR_ANY;
+
+ if (bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
+ error_counter++;
+
+ if (listen(server_sock, 1) < 0)
+ error_counter++;
+
+ /* Signal client thread to connect. */
+ tx_semaphore_put(&sema_1);
+
+ /* Accept the connection. */
+ client_addr_len = sizeof(client_addr);
+ conn_sock = accept(server_sock, (struct sockaddr *)&client_addr, &client_addr_len);
+ if (conn_sock < 0)
+ error_counter++;
+
+ /* ================================================================
+ * Test 1: zero-timeout select with no data ready must return 0
+ * and clear all output fd sets.
+ * ================================================================ */
+ FD_ZERO(&readfds);
+ FD_ZERO(&writefds);
+ FD_ZERO(&exceptfds);
+ FD_SET(conn_sock, &readfds);
+ FD_SET(conn_sock, &exceptfds);
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ nfd = conn_sock + 1;
+
+ n = select(nfd, &readfds, &writefds, &exceptfds, &tv);
+
+ if (n != 0)
+ error_counter++;
+
+ /* All sets must be cleared when select returns 0 (timeout). */
+ if (FD_ISSET(conn_sock, &readfds))
+ error_counter++;
+ if (FD_ISSET(conn_sock, &exceptfds))
+ error_counter++;
+
+ /* ================================================================
+ * Test 2: normal TCP data must set readfds and NOT exceptfds.
+ * ================================================================ */
+ FD_ZERO(&readfds);
+ FD_ZERO(&exceptfds);
+ FD_SET(conn_sock, &readfds);
+ FD_SET(conn_sock, &exceptfds);
+
+ tv.tv_sec = 5;
+ tv.tv_usec = 0;
+
+ /* Tell the client we are ready to receive. */
+ tx_semaphore_put(&sema_0);
+
+ n = select(nfd, &readfds, NX_NULL, &exceptfds, &tv);
+
+ if (n <= 0)
+ error_counter++;
+
+ /* readfds must be set (data arrived). */
+ if (!FD_ISSET(conn_sock, &readfds))
+ error_counter++;
+
+ /* exceptfds must NOT be set (no urgent data). */
+ if (FD_ISSET(conn_sock, &exceptfds))
+ error_counter++;
+
+ /* Consume the data. */
+ recv(conn_sock, buf, sizeof(buf), 0);
+
+ /* ================================================================
+ * Test 3: a socket that is not in use must appear in writefds.
+ * ================================================================ */
+ {
+ INT closed_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ INT open_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ INT max_fd;
+
+ if (closed_sock < 0 || open_sock < 0)
+ error_counter++;
+
+ /* Close one socket so its slot is free. */
+ soc_close(closed_sock);
+
+ FD_ZERO(&writefds);
+ FD_SET(closed_sock, &writefds);
+ FD_SET(open_sock, &writefds);
+
+ max_fd = (closed_sock > open_sock ? closed_sock : open_sock) + 1;
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+
+ n = select(max_fd, NX_NULL, &writefds, NX_NULL, &tv);
+
+ /* The not-in-use socket must be reported writable. */
+ if (!FD_ISSET(closed_sock, &writefds))
+ error_counter++;
+
+ soc_close(open_sock);
+ }
+
+ soc_close(conn_sock);
+ soc_close(server_sock);
+
+ if (error_counter)
+ test_control_return(1);
+ else
+ test_control_return(0);
+}
+
+/* Client thread: connects and sends normal (non-urgent) TCP data. */
+static void ntest_1_entry(ULONG thread_input)
+{
+struct sockaddr_in server_addr;
+INT client_sock;
+ULONG actual_status;
+
+ NX_PARAMETER_NOT_USED(thread_input);
+
+ nx_ip_status_check(&ip_1, NX_IP_INITIALIZE_DONE, &actual_status,
+ 5 * NX_IP_PERIODIC_RATE);
+
+ /* Wait for server to be ready. */
+ tx_semaphore_get(&sema_1, 5 * NX_IP_PERIODIC_RATE);
+
+ client_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (client_sock < 0)
+ error_counter++;
+
+ memset(&server_addr, 0, sizeof(server_addr));
+ server_addr.sin_family = AF_INET;
+ server_addr.sin_port = htons(5555);
+ server_addr.sin_addr.s_addr = htonl(IP_ADDRESS(1, 2, 3, 4));
+
+ if (connect(client_sock, (struct sockaddr *)&server_addr,
+ sizeof(server_addr)) < 0)
+ error_counter++;
+
+ /* Wait for server to enter select(). */
+ tx_semaphore_get(&sema_0, 5 * NX_IP_PERIODIC_RATE);
+
+ /* Send normal (non-urgent) data. */
+ if (send(client_sock, "Hello", 5, 0) < 0)
+ error_counter++;
+
+ soc_close(client_sock);
+}
+
+#else
+
+#ifdef CTEST
+VOID test_application_define(void *first_unused_memory)
+#else
+void netx_bsd_select_improvements_test_application_define(void *first_unused_memory)
+#endif
+{
+ NX_PARAMETER_NOT_USED(first_unused_memory);
+ test_control_return(3);
+}
+
+#endif /* NX_BSD_ENABLE && !NX_DISABLE_IPV4 */