diff options
| author | mzgrebnak <[email protected]> | 2026-05-29 15:15:07 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-05-29 09:15:07 -0400 |
| commit | 53070b69eed6af6840b0a890fe749e73e3f280f2 (patch) | |
| tree | 20d4f69b9c9de9c302dfa609cbee5eb8a32adabc | |
| parent | 57410350d94bbcb5e6885d0fbc40c72b6d7ddd33 (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.c | 94 | ||||
| -rw-r--r-- | test/cmake/netxduo/regression/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | test/regression/bsd_test/netx_bsd_select_improvements_test.c | 316 |
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 */ |
