diff --git a/common_smp/src/tx_thread_relinquish.c b/common_smp/src/tx_thread_relinquish.c index 10982b264..f07d2b56d 100644 --- a/common_smp/src/tx_thread_relinquish.c +++ b/common_smp/src/tx_thread_relinquish.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -290,8 +292,10 @@ UINT finished; } } while ((next_thread != thread_ptr) && (finished == TX_FALSE)); - /* Determine if we are finished. */ - if (finished == TX_FALSE) + /* Determine if we are finished. A rebalance request means the loop stopped + at a thread it could not place rather than running out of threads, so the + execute list still has to be rebuilt and this is not that case. */ + if ((finished == TX_FALSE) && (rebalance == TX_FALSE)) { /* No other thread is ready at this priority... simply return. */ diff --git a/test/smp/cmake/regression/CMakeLists.txt b/test/smp/cmake/regression/CMakeLists.txt index 771fb44dd..cdf0c5859 100644 --- a/test/smp/cmake/regression/CMakeLists.txt +++ b/test/smp/cmake/regression/CMakeLists.txt @@ -86,6 +86,7 @@ set(regression_test_cases ${SOURCE_DIR}/threadx_smp_random_resume_suspend_exclusion_test.c ${SOURCE_DIR}/threadx_smp_random_resume_suspend_test.c ${SOURCE_DIR}/threadx_smp_rebalance_exclusion_test.c + ${SOURCE_DIR}/threadx_smp_relinquish_rebalance_test.c ${SOURCE_DIR}/threadx_smp_relinquish_test.c ${SOURCE_DIR}/threadx_smp_resume_suspend_ascending_order_test.c ${SOURCE_DIR}/threadx_smp_resume_suspend_descending_order_test.c diff --git a/test/smp/regression/threadx_smp_relinquish_rebalance_test.c b/test/smp/regression/threadx_smp_relinquish_rebalance_test.c new file mode 100644 index 000000000..68a887f97 --- /dev/null +++ b/test/smp/regression/threadx_smp_relinquish_rebalance_test.c @@ -0,0 +1,250 @@ +/*************************************************************************** + * 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 Claude Code (Opus 5). + * 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 + **************************************************************************/ + +/* Define the ThreadX SMP relinquish rebalance test. This test fills every core with + threads of one priority that relinquish in a loop, and places two more threads of the + same priority behind them in the ready list: one that no core may run, and one that any + core may run. Walking the ready list, a relinquish reaches the unrunnable thread first + and cannot schedule it, so the execute list has to be rebalanced for the runnable thread + to reach a core. The runnable thread never running is the failure this test detects. */ + +#include +#include "tx_api.h" + + +/* Fill the four cores, so a thread behind these in the ready list only reaches a core + through the execute list being rebalanced. */ + +#define SPIN_THREADS 4 + + +/* The spinning threads keep their cores whatever else happens, so they carry the deadline. + One second is three orders of magnitude more than the handful of relinquishes a healthy + system needs, and the tick advances independently of which threads are scheduled. */ + +#define DEADLINE_TICKS 100 + + +static TX_THREAD thread_control; +static TX_THREAD thread_spin[SPIN_THREADS]; +static TX_THREAD thread_blocked; +static TX_THREAD thread_starved; + +static ULONG spin_counter[SPIN_THREADS]; +static ULONG starved_counter; +static ULONG blocked_counter; +static ULONG reported; + + +/* Define thread prototypes. */ + +static void thread_control_entry(ULONG thread_input); +static void thread_spin_entry(ULONG thread_input); +static void thread_blocked_entry(ULONG thread_input); +static void thread_starved_entry(ULONG thread_input); + + +/* Prototype for test control return. */ + +void test_control_return(UINT status); + + +/* Define what the initial system looks like. */ + +#ifdef CTEST +void test_application_define(void *first_unused_memory) +#else +void threadx_smp_relinquish_rebalance_test(void *first_unused_memory) +#endif +{ + +UINT status; +CHAR *pointer; +UINT i; + + + /* Put first available memory address into a character pointer. */ + pointer = (CHAR *) first_unused_memory; + + /* Create the control thread, which only resumes the others and then completes. */ + status = tx_thread_create(&thread_control, "control thread", thread_control_entry, 0, + pointer, TEST_STACK_SIZE_PRINTF, + 0, 0, TX_NO_TIME_SLICE, TX_AUTO_START); + pointer = pointer + TEST_STACK_SIZE_PRINTF; + status += tx_thread_smp_core_exclude(&thread_control, 0xE); /* Core 0 only! */ + + /* Check status. */ + if (status != TX_SUCCESS) + { + + printf("Running SMP Relinquish Rebalance Test............................... ERROR #1\n"); + test_control_return(1); + } + + /* Create one spinning thread per core. */ + for (i = 0; i < SPIN_THREADS; i++) + { + + status = tx_thread_create(&thread_spin[i], "spin thread", thread_spin_entry, i, + pointer, TEST_STACK_SIZE_PRINTF, + 20, 20, TX_NO_TIME_SLICE, TX_DONT_START); + pointer = pointer + TEST_STACK_SIZE_PRINTF; + + /* Check status. */ + if (status != TX_SUCCESS) + { + + printf("Running SMP Relinquish Rebalance Test............................... ERROR #2\n"); + test_control_return(1); + } + } + + /* Create the thread no core may run. Excluding every core leaves it ready and + permanently unmapped, which is what makes it a stable obstacle in the ready list + rather than a thread that eventually gets a turn. */ + status = tx_thread_create(&thread_blocked, "blocked thread", thread_blocked_entry, 0, + pointer, TEST_STACK_SIZE_PRINTF, + 20, 20, TX_NO_TIME_SLICE, TX_DONT_START); + pointer = pointer + TEST_STACK_SIZE_PRINTF; + status += tx_thread_smp_core_exclude(&thread_blocked, 0xF); /* No core at all! */ + + /* Check status. */ + if (status != TX_SUCCESS) + { + + printf("Running SMP Relinquish Rebalance Test............................... ERROR #3\n"); + test_control_return(1); + } + + /* Create the thread that any core may run. It is resumed last so that it sits behind + the blocked thread in the ready list at this priority. */ + status = tx_thread_create(&thread_starved, "starved thread", thread_starved_entry, 0, + pointer, TEST_STACK_SIZE_PRINTF, + 20, 20, TX_NO_TIME_SLICE, TX_DONT_START); + pointer = pointer + TEST_STACK_SIZE_PRINTF; + + /* Check status. */ + if (status != TX_SUCCESS) + { + + printf("Running SMP Relinquish Rebalance Test............................... ERROR #4\n"); + test_control_return(1); + } +} + + +/* Resume the others in ready list order and complete, so core 0 is released. */ + +static void thread_control_entry(ULONG thread_input) +{ + +UINT status; +UINT i; + + + status = TX_SUCCESS; + + /* Fill the cores first. */ + for (i = 0; i < SPIN_THREADS; i++) + { + + status += tx_thread_resume(&thread_spin[i]); + } + + /* Then the obstacle, then the thread behind it. */ + status += tx_thread_resume(&thread_blocked); + status += tx_thread_resume(&thread_starved); + + /* Check status. */ + if (status != TX_SUCCESS) + { + + printf("Running SMP Relinquish Rebalance Test............................... ERROR #5\n"); + test_control_return(1); + } +} + + +/* Hold a core and relinquish. These threads keep running whatever the scheduler does with + the rest, so they are the ones that can enforce a deadline on the starved thread. */ + +static void thread_spin_entry(ULONG thread_input) +{ + +ULONG start; + + + start = tx_time_get(); + + while (starved_counter == 0) + { + + spin_counter[thread_input]++; + + /* The tick advances whether or not any thread is being scheduled, so this bounds + the test even when nothing is. */ + if ((tx_time_get() - start) > DEADLINE_TICKS) + { + + if (reported == 0) + { + + reported = 1; + printf("Running SMP Relinquish Rebalance Test............................... ERROR #6\n"); + test_control_return(1); + } + + return; + } + + tx_thread_relinquish(); + } +} + + +/* Never runs: every core is excluded for this thread. */ + +static void thread_blocked_entry(ULONG thread_input) +{ + + blocked_counter++; +} + + +/* Reaching a core at all is the pass condition. */ + +static void thread_starved_entry(ULONG thread_input) +{ + + starved_counter++; + + if (reported == 0) + { + + reported = 1; + + /* The blocked thread must not have run, or the obstacle was not an obstacle and + the test proved nothing. */ + if (blocked_counter != 0) + { + + printf("Running SMP Relinquish Rebalance Test............................... ERROR #7\n"); + test_control_return(1); + } + + printf("Running SMP Relinquish Rebalance Test............................... SUCCESS!\n"); + test_control_return(0); + } +}