mirror of
https://github.com/eclipse-threadx/threadx.git
synced 2026-10-06 06:59:08 +08:00
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
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:
@@ -63,15 +63,22 @@ jobs:
|
|||||||
cmake_path: ./test/smp/cmake
|
cmake_path: ./test/smp/cmake
|
||||||
result_affix: SMP
|
result_affix: SMP
|
||||||
skip_deploy: true
|
skip_deploy: true
|
||||||
# Lower than ThreadX's, because this suite is: the merged report reads
|
# Raised from 98 with the four tests that closed 53 of the 64 lines this
|
||||||
# 5114/5178 lines -- 98.76% -- on the same run, with 64 uncovered lines
|
# comment used to list: the merged report goes from 5114/5178 -- 98.76% --
|
||||||
# across 11 files of common_smp/src. #666 closed the equivalent gaps in
|
# to 5167/5178, 99.79%. A floor of 99 needs 5127, so there are 40 lines of
|
||||||
# common/src only, so common_smp still carries them, plus a family of
|
# headroom against a numerator measured flickering by two between runs.
|
||||||
# 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
|
# Still not 100, and not because the last eleven lines were skipped. Eight
|
||||||
# while ThreadX passed -- probed against the pinned action. Raise this
|
# are tx_byte_pool_search.c's ownership delay loop, which needs another
|
||||||
# towards 99 as those lines are covered.
|
# core to take the pool inside the window the search opens when it drops
|
||||||
coverage_thresholds: '98 100'
|
# 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:
|
freertos:
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ set(regression_test_cases
|
|||||||
${SOURCE_DIR}/threadx_block_memory_thread_terminate_test.c
|
${SOURCE_DIR}/threadx_block_memory_thread_terminate_test.c
|
||||||
${SOURCE_DIR}/threadx_byte_memory_basic_test.c
|
${SOURCE_DIR}/threadx_byte_memory_basic_test.c
|
||||||
${SOURCE_DIR}/threadx_byte_memory_information_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_prioritize_test.c
|
||||||
${SOURCE_DIR}/threadx_byte_memory_suspension_test.c
|
${SOURCE_DIR}/threadx_byte_memory_suspension_test.c
|
||||||
${SOURCE_DIR}/threadx_byte_memory_suspension_timeout_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_proritize_test.c
|
||||||
${SOURCE_DIR}/threadx_mutex_suspension_timeout_test.c
|
${SOURCE_DIR}/threadx_mutex_suspension_timeout_test.c
|
||||||
${SOURCE_DIR}/threadx_mutex_thread_terminate_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_eight_word_test.c
|
||||||
${SOURCE_DIR}/threadx_queue_basic_four_word_test.c
|
${SOURCE_DIR}/threadx_queue_basic_four_word_test.c
|
||||||
${SOURCE_DIR}/threadx_queue_basic_one_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_create_preemption_threshold_test.c
|
||||||
${SOURCE_DIR}/threadx_thread_delayed_suspension_test.c
|
${SOURCE_DIR}/threadx_thread_delayed_suspension_test.c
|
||||||
${SOURCE_DIR}/threadx_thread_information_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_multi_level_preemption_threshold_test.c
|
||||||
${SOURCE_DIR}/threadx_thread_multiple_non_current_test.c
|
${SOURCE_DIR}/threadx_thread_multiple_non_current_test.c
|
||||||
${SOURCE_DIR}/threadx_thread_multiple_sleep_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_multiple_test.c
|
||||||
${SOURCE_DIR}/threadx_timer_simple_test.c
|
${SOURCE_DIR}/threadx_timer_simple_test.c
|
||||||
${SOURCE_DIR}/threadx_trace_basic_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)
|
${SOURCE_DIR}/threadx_initialize_kernel_setup_test.c)
|
||||||
|
|
||||||
add_custom_command(
|
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
Reference in New Issue
Block a user