summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common_smp/src/tx_thread_relinquish.c8
-rw-r--r--test/smp/cmake/regression/CMakeLists.txt1
-rw-r--r--test/smp/regression/threadx_smp_relinquish_rebalance_test.c250
3 files changed, 257 insertions, 2 deletions
diff --git a/common_smp/src/tx_thread_relinquish.c b/common_smp/src/tx_thread_relinquish.c
index 10982b26..f07d2b56 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 771fb44d..cdf0c585 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 00000000..68a887f9
--- /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 <stdio.h>
+#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);
+ }
+}