Covered the SMP suspension teardown and the long byte pool search (#677)
cortex_m / Cortex M0 build (push) Canceled after 0s
cortex_m / Cortex M3 build (push) Canceled after 0s
cortex_m / Cortex M4 build (push) Canceled after 0s
cortex_m / Cortex M7 build (push) Canceled after 0s
gcc_check / gnu (push) Canceled after 0s
regression_test / tx (push) Canceled after 0s
regression_test / smp (push) Canceled after 0s
regression_test / freertos (push) Canceled after 0s
regression_test / deploy (push) Canceled after 0s
regression_template / run_tests (push) Canceled after 0s
regression_template / deploy_code_coverage (push) Canceled after 0s

Against the merged SMP coverage report -- every build configuration instrumented
and unioned -- sixty-four lines of common_smp/src were uncovered, 5114 of 5178.
Fifty-three of them are closed here and the report reads 5167 of 5178. The SMP
coverage floor goes from 98 to 99 with it.

Thirty-six of the sixty-four were one loop repeated four times: the walk in
tx_block_pool_delete, tx_byte_pool_delete, tx_event_flags_delete and
tx_queue_delete that releases every thread suspended on the object with
TX_DELETED. The suite deletes all four object types after every single test, and
that is exactly why the loop never ran. test_control_cleanup in the ThreadX
suite deletes the application's objects first and its threads last, so a test
that ends with a thread parked on a queue has that thread walked out of it by
tx_queue_delete. The SMP suite's cleanup deletes the threads first, deliberately
-- it was changed so that no application-owned object is still referenced when
the object loops run, which is what stopped a class of teardown hang. The side
effect is that all four deletes now run against an empty suspension list.
tx_semaphore_delete is the one member of the family that was already covered,
because threadx_semaphore_delete_test deletes a busy semaphore on purpose.

threadx_object_delete_suspension_test is the same idea for the other four. Two
threads suspend on each of a block pool, a byte pool, an event flags group and a
queue; the control thread waits on the object's own suspended count through
tx_*_info_get rather than on an ordering it cannot guarantee across four cores,
deletes the object, and checks both waiters came out with TX_DELETED. Two
waiters rather than one so the loop takes its back edge as well as its body, and
every wait is bounded in ticks so a suspension that never arrives fails the test
instead of hanging it.

threadx_trace_entry_update_test and threadx_thread_misaligned_stack_test are
ports of the two tests that closed the equivalent gaps in common/src, and close
fourteen more lines here: tx_block_allocate 123, 175, 182, 319 and 326,
tx_byte_allocate 130, 210, 217, 359 and 366, tx_thread_system_suspend 504 and
560, tx_trace_object_register 221, and tx_thread_create 133. The one substantive
change is core confinement. The trace test needs thread 0 to suspend and thread
1 to then release what it waits for; on four cores thread 1 gives the block back
before thread 0 has suspended and the update block behind the suspension is
never reached, so both threads are excluded from cores 1 to 3. The misaligned
stack test needed no such change.

threadx_byte_memory_long_search_test closes three of the eleven in
tx_byte_pool_search. Lines 264, 267 and 270 are the
TX_BYTE_POOL_MULTIPLE_BLOCK_SEARCH limit -- twenty on this port -- where a long
search drops and retakes protection so that it cannot lock the other cores out
for the whole walk. No byte pool in the suite ever had twenty fragments. This
one is filled with small chunks until it refuses another and then has every
second chunk released, so the free fragments are never adjacent and cannot be
merged, and the request is larger than any of them but smaller than the pool's
theoretical total, which is what makes _tx_byte_pool_search walk rather than
refuse at the door. The layout is asserted rather than assumed: the test checks
the fragment count and checks the probe request really does fail before the
workers start, because either would otherwise turn it into a silent no-op.

Eleven lines remain and they are not a to-do list. Eight are the delay loop in
tx_byte_pool_search that fires when another thread claims the pool inside the
window the search opens. The Linux SMP port serialises all four cores on one
pthread mutex, so that window is an unlock immediately followed by a lock on
that mutex, and glibc hands an uncontended mutex straight back to the thread
that just released it: measured over 180,003 windows across three cores, with
zero handovers. The shipped test therefore does 250 searches per worker rather
than the sixty thousand that probe used, because the twenty-block threshold is
crossed by the first search. The other three are in tx_thread_smp_utilities.
Line 149 is a range guard placed after the shift it is meant to guard, so
reaching it needs a shift by the width of the type; the fix is to move the check
above the shift, matching the TX_MAX_PRIORITIES > 32 variant of the same
function, and that belongs in its own change. Lines 1073 and 1074 need a mutex
owner that is genuinely executing on another core when a waiter suspends, and
three shapes were tried without producing one on this port.

Measured twice before and twice after, every gcda deleted between runs and
570 of 570 tests passing each time: 5114 of 5178 both times before, 5167 of 5178
both times after. Branch coverage goes from 2768 to 2821 and 2823 of 3548. A
floor of 99 needs 5127, so the ratchet lands with forty lines of headroom
against a numerator that has been seen moving by two between runs.

Assisted-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Frédéric Desbiens
2026-08-28 10:41:39 -04:00
committed by GitHub
parent ff7fbe02f8
commit a2800fef16
6 changed files with 1361 additions and 9 deletions
+16 -9
View File
@@ -63,15 +63,22 @@ jobs:
cmake_path: ./test/smp/cmake
result_affix: SMP
skip_deploy: true
# Lower than ThreadX's, because this suite is: the merged report reads
# 5114/5178 lines -- 98.76% -- on the same run, with 64 uncovered lines
# across 11 files of common_smp/src. #666 closed the equivalent gaps in
# common/src only, so common_smp still carries them, plus a family of
# its own in the *_delete.c teardown paths and tx_byte_pool_search.c.
# A shared floor of 99 would therefore red-wall this job on every run
# while ThreadX passed -- probed against the pinned action. Raise this
# towards 99 as those lines are covered.
coverage_thresholds: '98 100'
# Raised from 98 with the four tests that closed 53 of the 64 lines this
# comment used to list: the merged report goes from 5114/5178 -- 98.76% --
# to 5167/5178, 99.79%. A floor of 99 needs 5127, so there are 40 lines of
# headroom against a numerator measured flickering by two between runs.
#
# Still not 100, and not because the last eleven lines were skipped. Eight
# are tx_byte_pool_search.c's ownership delay loop, which needs another
# core to take the pool inside the window the search opens when it drops
# protection every twenty blocks; this port serialises all four cores on
# one pthread mutex and the thread that releases it wins the re-acquire
# every time -- measured over 180,003 windows, zero handovers. The other
# three are in tx_thread_smp_utilities.c: one is a range guard placed
# after the shift it is meant to guard, so reaching it needs undefined
# behaviour, and two are a priority-inheritance branch that needs the
# mutex owner genuinely executing on another core. See T17.
coverage_thresholds: '99 100'
freertos:
permissions:
contents: read
+4
View File
@@ -25,6 +25,7 @@ set(regression_test_cases
${SOURCE_DIR}/threadx_block_memory_thread_terminate_test.c
${SOURCE_DIR}/threadx_byte_memory_basic_test.c
${SOURCE_DIR}/threadx_byte_memory_information_test.c
${SOURCE_DIR}/threadx_byte_memory_long_search_test.c
${SOURCE_DIR}/threadx_byte_memory_prioritize_test.c
${SOURCE_DIR}/threadx_byte_memory_suspension_test.c
${SOURCE_DIR}/threadx_byte_memory_suspension_timeout_test.c
@@ -52,6 +53,7 @@ set(regression_test_cases
${SOURCE_DIR}/threadx_mutex_proritize_test.c
${SOURCE_DIR}/threadx_mutex_suspension_timeout_test.c
${SOURCE_DIR}/threadx_mutex_thread_terminate_test.c
${SOURCE_DIR}/threadx_object_delete_suspension_test.c
${SOURCE_DIR}/threadx_queue_basic_eight_word_test.c
${SOURCE_DIR}/threadx_queue_basic_four_word_test.c
${SOURCE_DIR}/threadx_queue_basic_one_word_test.c
@@ -95,6 +97,7 @@ set(regression_test_cases
${SOURCE_DIR}/threadx_thread_create_preemption_threshold_test.c
${SOURCE_DIR}/threadx_thread_delayed_suspension_test.c
${SOURCE_DIR}/threadx_thread_information_test.c
${SOURCE_DIR}/threadx_thread_misaligned_stack_test.c
${SOURCE_DIR}/threadx_thread_multi_level_preemption_threshold_test.c
${SOURCE_DIR}/threadx_thread_multiple_non_current_test.c
${SOURCE_DIR}/threadx_thread_multiple_sleep_test.c
@@ -124,6 +127,7 @@ set(regression_test_cases
${SOURCE_DIR}/threadx_timer_multiple_test.c
${SOURCE_DIR}/threadx_timer_simple_test.c
${SOURCE_DIR}/threadx_trace_basic_test.c
${SOURCE_DIR}/threadx_trace_entry_update_test.c
${SOURCE_DIR}/threadx_initialize_kernel_setup_test.c)
add_custom_command(
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,156 @@
/***************************************************************************
* 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
**************************************************************************/
/* This test creates a thread whose stack does not begin on a ULONG boundary.
It is the SMP counterpart of the ThreadX suite's test of the same name.
tx_thread_create rounds the starting address up to the next ULONG and then
takes a ULONG off the size, so that the rounding cannot push the end of the
stack past the memory the caller supplied:
if (new_stack_start != updated_stack_start)
{
stack_size = stack_size - (sizeof(ULONG));
}
Every other test in the suite hands tx_thread_create an aligned stack, so
that subtraction was never executed -- the only uncovered line in
tx_thread_create.c across all five build configurations. A misaligned stack
is legitimate: the API takes a VOID * and the alignment fix-up exists
precisely to accept one.
The line is compiled only under TX_ENABLE_STACK_CHECKING, so it is absent
from three of the five configurations' reports rather than uncovered in
them.
The thread is then run to completion rather than merely created, because the
point of the adjustment is that the resulting stack is still usable and still
inside the caller's buffer. */
#include <stdio.h>
#include "tx_api.h"
void test_control_return(UINT status);
static TX_THREAD thread_0;
static TX_THREAD thread_1;
/* Thread 1's stack is carved out of this by hand so that its start can be put
deliberately off a ULONG boundary. The extra ULONG is what the misaligned
start is offset into, so that the stack still ends inside the array after
tx_thread_create has rounded the start up. */
static UCHAR misaligned_area[TEST_STACK_SIZE_PRINTF + sizeof(ULONG)];
static ULONG thread_1_counter = 0;
static void thread_0_entry(ULONG thread_input);
static void thread_1_entry(ULONG thread_input);
/* Define what the initial system looks like. */
#ifdef CTEST
void test_application_define(void *first_unused_memory)
#else
void threadx_thread_misaligned_stack_application_define(void *first_unused_memory)
#endif
{
UINT status;
CHAR *pointer;
CHAR *misaligned_stack;
/* Setup a pointer. */
pointer = (CHAR *) first_unused_memory;
status = tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,
pointer, TEST_STACK_SIZE_PRINTF,
16, 16, TX_NO_TIME_SLICE, TX_AUTO_START);
pointer = pointer + TEST_STACK_SIZE_PRINTF;
if (status != TX_SUCCESS)
{
printf("Running Thread Misaligned Stack Test................................ ERROR #1\n");
test_control_return(1);
}
/* Push the start one byte past a ULONG boundary. The array itself is
aligned, so adding one is enough to guarantee the fix-up runs whatever
the alignment of the array turned out to be. */
misaligned_stack = ((CHAR *) misaligned_area) + 1;
status = tx_thread_create(&thread_1, "thread 1", thread_1_entry, 0,
misaligned_stack, TEST_STACK_SIZE_PRINTF,
15, 15, TX_NO_TIME_SLICE, TX_AUTO_START);
if (status != TX_SUCCESS)
{
printf("Running Thread Misaligned Stack Test................................ ERROR #2\n");
test_control_return(1);
}
}
static void thread_0_entry(ULONG thread_input)
{
/* On this port the two threads start on separate cores, so this one does
not have to wait for a relinquish to run. It sleeps well past the single
tick thread 1 sleeps for, so the checks below are made against a thread
that has actually finished either way. */
tx_thread_sleep(5);
/* Both increments must have happened: one before thread 1 slept and one
after it woke, which is the half that needed the stack to survive a
context switch. */
if (thread_1_counter != ((ULONG) 2))
{
printf("Running Thread Misaligned Stack Test................................ ERROR #3\n");
test_control_return(1);
}
/* The thread that ran on the misaligned stack must have run to completion,
which is what says the adjusted stack was usable. */
if (thread_1.tx_thread_state != TX_COMPLETED)
{
printf("Running Thread Misaligned Stack Test................................ ERROR #4\n");
test_control_return(1);
}
printf("Running Thread Misaligned Stack Test................................ SUCCESS!\n");
test_control_return(0);
}
static void thread_1_entry(ULONG thread_input)
{
/* Use the stack either side of a context switch rather than returning
straight away. */
thread_1_counter++;
tx_thread_sleep(1);
thread_1_counter++;
}
File diff suppressed because it is too large Load Diff