From d2db0b3faae073f28f6b42921736ccfb69ed1d00 Mon Sep 17 00:00:00 2001 From: Yang Hau Date: Wed, 14 Oct 2026 01:58:13 +0800 Subject: [PATCH] Restored inherited priority when a mutex waiter stops waiting When a thread blocks on a TX_INHERIT mutex, the owner is raised to the waiter's priority. If the waiter then leaves the suspension list without the mutex -- on a wait timeout, tx_thread_wait_abort or tx_thread_terminate -- _tx_mutex_cleanup unlinked it but left tx_mutex_highest_priority_waiting and the owner's priority as they were. Only tx_mutex_put recomputed them, so the owner kept the departed waiter's priority for as long as it held the mutex, and every thread between the two priorities was starved. _tx_mutex_cleanup now calls a new internal function, _tx_mutex_inherit_priority_update, after unlinking a waiter from an inheriting mutex. It recomputes the mutex's highest waiting priority from its suspension list, recomputes the owner's inherited priority across every mutex the owner holds, and changes the owner's priority if it differs from the higher of that and the owner's own. It follows the preemption-disable pattern _tx_mutex_put uses around _tx_mutex_prioritize and _tx_mutex_priority_change. Applied to common and common_smp, where both files are identical. threadx_mutex_inherit_wait_exit_test removes a single waiter by each of the three routes, then aborts four waiters spread over two mutexes in turn. Added to both suites. Without the fix it fails with ERROR #12 in both; with it, the mutex tests pass in both suites' default_build_coverage configuration, and the changed sources build cleanly under -Wall -Wextra -Werror with TX_NOT_INTERRUPTABLE, TX_MISRA_ENABLE, TX_DISABLE_PREEMPTION_THRESHOLD and event trace. --- common/inc/tx_mutex.h | 1 + common/src/tx_mutex_cleanup.c | 211 ++++++++++++++++ common_smp/inc/tx_mutex.h | 1 + common_smp/src/tx_mutex_cleanup.c | 211 ++++++++++++++++ test/smp/cmake/regression/CMakeLists.txt | 1 + test/smp/regression/testcontrol.c | 2 + .../threadx_mutex_inherit_wait_exit_test.c | 232 ++++++++++++++++++ test/tx/cmake/regression/CMakeLists.txt | 1 + test/tx/regression/testcontrol.c | 2 + .../threadx_mutex_inherit_wait_exit_test.c | 232 ++++++++++++++++++ 10 files changed, 894 insertions(+) create mode 100644 test/smp/regression/threadx_mutex_inherit_wait_exit_test.c create mode 100644 test/tx/regression/threadx_mutex_inherit_wait_exit_test.c diff --git a/common/inc/tx_mutex.h b/common/inc/tx_mutex.h index b0b46c2f9..f062cc4a5 100644 --- a/common/inc/tx_mutex.h +++ b/common/inc/tx_mutex.h @@ -84,6 +84,7 @@ VOID _tx_mutex_initialize(VOID); VOID _tx_mutex_cleanup(TX_THREAD *thread_ptr, ULONG suspension_sequence); VOID _tx_mutex_thread_release(TX_THREAD *thread_ptr); VOID _tx_mutex_priority_change(TX_THREAD *thread_ptr, UINT new_priority); +VOID _tx_mutex_inherit_priority_update(TX_MUTEX *mutex_ptr); /* Mutex management component data declarations follow. */ diff --git a/common/src/tx_mutex_cleanup.c b/common/src/tx_mutex_cleanup.c index d32f69fc0..3eba0ac1f 100644 --- a/common/src/tx_mutex_cleanup.c +++ b/common/src/tx_mutex_cleanup.c @@ -59,6 +59,7 @@ /* */ /* _tx_thread_system_resume Resume thread service */ /* _tx_thread_system_ni_resume Non-interruptable resume thread */ +/* _tx_mutex_inherit_priority_update Update priority inheritance */ /* */ /* CALLED BY */ /* */ @@ -155,6 +156,37 @@ TX_THREAD *previous_thread; } } + /* Determine if priority inheritance is in effect. */ + if (mutex_ptr -> tx_mutex_inherit == TX_TRUE) + { + + /* The departing thread may be the one whose priority the owner + inherited, and it has not obtained the mutex, so nothing else + will undo that inheritance until the owner releases the mutex. + Recompute the owner's priority from the remaining waiters. */ + +#ifndef TX_NOT_INTERRUPTABLE + + /* Temporarily disable preemption. */ + _tx_thread_preempt_disable++; + + /* Restore interrupts. */ + TX_RESTORE +#endif + + /* Update the priority inheritance of the mutex owner. */ + _tx_mutex_inherit_priority_update(mutex_ptr); + +#ifndef TX_NOT_INTERRUPTABLE + + /* Disable interrupts. */ + TX_DISABLE + + /* Back off the preemption disable. */ + _tx_thread_preempt_disable--; +#endif + } + /* Now we need to determine if this cleanup is from a terminate, timeout, or from a wait abort. */ if (thread_ptr -> tx_thread_state == TX_MUTEX_SUSP) @@ -307,3 +339,182 @@ UINT status; TX_RESTORE } + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _tx_mutex_inherit_priority_update PORTABLE C */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function brings the priority inheritance of a mutex and of */ +/* its owner up to date after the set of threads waiting on the mutex, */ +/* or the priority of one of them, has changed other than through */ +/* tx_mutex_get or tx_mutex_put. The highest priority waiting on the */ +/* mutex is recomputed from its suspension list, the owner's inherited */ +/* priority is recomputed from every priority inheritance mutex it */ +/* owns, and the owner's priority is changed if it differs from the */ +/* higher of that and its own user priority. */ +/* */ +/* Unless TX_NOT_INTERRUPTABLE is defined, the caller must have */ +/* preemption disabled and interrupts enabled, as for */ +/* _tx_mutex_priority_change. Otherwise interrupts must be disabled. */ +/* */ +/* INPUT */ +/* */ +/* mutex_ptr Pointer to mutex control block */ +/* */ +/* OUTPUT */ +/* */ +/* None */ +/* */ +/* CALLS */ +/* */ +/* _tx_mutex_prioritize Move highest priority waiter to */ +/* the front of the list */ +/* _tx_mutex_priority_change Change owner's priority */ +/* */ +/* CALLED BY */ +/* */ +/* ThreadX internal code */ +/* */ +/**************************************************************************/ +VOID _tx_mutex_inherit_priority_update(TX_MUTEX *mutex_ptr) +{ + +#ifndef TX_NOT_INTERRUPTABLE +TX_INTERRUPT_SAVE_AREA +#endif + +TX_THREAD *owner_ptr; +TX_THREAD *suspended_thread; +TX_MUTEX *next_mutex; +UINT inheritance_priority; +UINT new_priority; +#ifdef TX_MISRA_ENABLE +UINT status; +#endif + + + /* Determine if there is more than one thread suspended on the mutex. */ + if (mutex_ptr -> tx_mutex_suspended_count > ((UINT) 1)) + { + + /* Yes, prioritize the list so the highest priority thread is placed at the + front of the suspension list. */ +#ifdef TX_MISRA_ENABLE + do + { + status = _tx_mutex_prioritize(mutex_ptr); + } while (status != TX_SUCCESS); +#else + _tx_mutex_prioritize(mutex_ptr); +#endif + } + +#ifndef TX_NOT_INTERRUPTABLE + + /* Disable interrupts. */ + TX_DISABLE +#endif + + /* Pickup the thread at the front of the suspension list. */ + suspended_thread = mutex_ptr -> tx_mutex_suspension_list; + + /* Determine if there still are threads suspended for this mutex. */ + if (suspended_thread != TX_NULL) + { + + /* Setup the highest priority thread waiting on this mutex. */ + mutex_ptr -> tx_mutex_highest_priority_waiting = suspended_thread -> tx_thread_priority; + } + else + { + + /* No thread is waiting on this mutex. */ + mutex_ptr -> tx_mutex_highest_priority_waiting = ((UINT) TX_MAX_PRIORITIES); + } + + /* Pickup the mutex owner. */ + owner_ptr = mutex_ptr -> tx_mutex_owner; + +#ifndef TX_NOT_INTERRUPTABLE + + /* Restore interrupts. */ + TX_RESTORE +#endif + + /* The owner is not NULL. A thread could only suspend on this mutex while a + thread owned it, since tx_mutex_get raises the owner's priority when it does, + and the owner keeps the mutex for as long as this processing can run. */ + + /* Default the inheritance priority to disabled. */ + inheritance_priority = ((UINT) TX_MAX_PRIORITIES); + + /* Search the mutexes owned by this thread to determine the highest priority + it still inherits. */ + next_mutex = owner_ptr -> tx_thread_owned_mutex_list; + while (next_mutex != TX_NULL) + { + + /* Does this mutex support priority inheritance? */ + if (next_mutex -> tx_mutex_inherit == TX_TRUE) + { + + /* Determine if highest priority field of the mutex is higher than the + inheritance priority so far. */ + if (next_mutex -> tx_mutex_highest_priority_waiting < inheritance_priority) + { + + /* Use this priority. */ + inheritance_priority = next_mutex -> tx_mutex_highest_priority_waiting; + } + } + + /* Move mutex pointer to the next mutex in the list. */ + next_mutex = next_mutex -> tx_mutex_owned_next; + + /* Are we at the end of the list? */ + if (next_mutex == owner_ptr -> tx_thread_owned_mutex_list) + { + + /* Yes, set the next mutex to NULL. */ + next_mutex = TX_NULL; + } + } + +#ifndef TX_NOT_INTERRUPTABLE + + /* Disable interrupts. */ + TX_DISABLE +#endif + + /* Set the inherit priority to that of the highest priority thread waiting on + any mutex the owner holds. */ + owner_ptr -> tx_thread_inherit_priority = inheritance_priority; + + /* The owner runs at the higher of its own priority and the inherited one. */ + new_priority = owner_ptr -> tx_thread_user_priority; + if (inheritance_priority < new_priority) + { + + /* Use the inherited priority. */ + new_priority = inheritance_priority; + } + +#ifndef TX_NOT_INTERRUPTABLE + + /* Restore interrupts. */ + TX_RESTORE +#endif + + /* Determine if the owner's priority has to change. */ + if (owner_ptr -> tx_thread_priority != new_priority) + { + + /* Yes, change the priority of the owner. */ + _tx_mutex_priority_change(owner_ptr, new_priority); + } +} diff --git a/common_smp/inc/tx_mutex.h b/common_smp/inc/tx_mutex.h index b0b46c2f9..f062cc4a5 100644 --- a/common_smp/inc/tx_mutex.h +++ b/common_smp/inc/tx_mutex.h @@ -84,6 +84,7 @@ VOID _tx_mutex_initialize(VOID); VOID _tx_mutex_cleanup(TX_THREAD *thread_ptr, ULONG suspension_sequence); VOID _tx_mutex_thread_release(TX_THREAD *thread_ptr); VOID _tx_mutex_priority_change(TX_THREAD *thread_ptr, UINT new_priority); +VOID _tx_mutex_inherit_priority_update(TX_MUTEX *mutex_ptr); /* Mutex management component data declarations follow. */ diff --git a/common_smp/src/tx_mutex_cleanup.c b/common_smp/src/tx_mutex_cleanup.c index d32f69fc0..3eba0ac1f 100644 --- a/common_smp/src/tx_mutex_cleanup.c +++ b/common_smp/src/tx_mutex_cleanup.c @@ -59,6 +59,7 @@ /* */ /* _tx_thread_system_resume Resume thread service */ /* _tx_thread_system_ni_resume Non-interruptable resume thread */ +/* _tx_mutex_inherit_priority_update Update priority inheritance */ /* */ /* CALLED BY */ /* */ @@ -155,6 +156,37 @@ TX_THREAD *previous_thread; } } + /* Determine if priority inheritance is in effect. */ + if (mutex_ptr -> tx_mutex_inherit == TX_TRUE) + { + + /* The departing thread may be the one whose priority the owner + inherited, and it has not obtained the mutex, so nothing else + will undo that inheritance until the owner releases the mutex. + Recompute the owner's priority from the remaining waiters. */ + +#ifndef TX_NOT_INTERRUPTABLE + + /* Temporarily disable preemption. */ + _tx_thread_preempt_disable++; + + /* Restore interrupts. */ + TX_RESTORE +#endif + + /* Update the priority inheritance of the mutex owner. */ + _tx_mutex_inherit_priority_update(mutex_ptr); + +#ifndef TX_NOT_INTERRUPTABLE + + /* Disable interrupts. */ + TX_DISABLE + + /* Back off the preemption disable. */ + _tx_thread_preempt_disable--; +#endif + } + /* Now we need to determine if this cleanup is from a terminate, timeout, or from a wait abort. */ if (thread_ptr -> tx_thread_state == TX_MUTEX_SUSP) @@ -307,3 +339,182 @@ UINT status; TX_RESTORE } + + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _tx_mutex_inherit_priority_update PORTABLE C */ +/* */ +/* DESCRIPTION */ +/* */ +/* This function brings the priority inheritance of a mutex and of */ +/* its owner up to date after the set of threads waiting on the mutex, */ +/* or the priority of one of them, has changed other than through */ +/* tx_mutex_get or tx_mutex_put. The highest priority waiting on the */ +/* mutex is recomputed from its suspension list, the owner's inherited */ +/* priority is recomputed from every priority inheritance mutex it */ +/* owns, and the owner's priority is changed if it differs from the */ +/* higher of that and its own user priority. */ +/* */ +/* Unless TX_NOT_INTERRUPTABLE is defined, the caller must have */ +/* preemption disabled and interrupts enabled, as for */ +/* _tx_mutex_priority_change. Otherwise interrupts must be disabled. */ +/* */ +/* INPUT */ +/* */ +/* mutex_ptr Pointer to mutex control block */ +/* */ +/* OUTPUT */ +/* */ +/* None */ +/* */ +/* CALLS */ +/* */ +/* _tx_mutex_prioritize Move highest priority waiter to */ +/* the front of the list */ +/* _tx_mutex_priority_change Change owner's priority */ +/* */ +/* CALLED BY */ +/* */ +/* ThreadX internal code */ +/* */ +/**************************************************************************/ +VOID _tx_mutex_inherit_priority_update(TX_MUTEX *mutex_ptr) +{ + +#ifndef TX_NOT_INTERRUPTABLE +TX_INTERRUPT_SAVE_AREA +#endif + +TX_THREAD *owner_ptr; +TX_THREAD *suspended_thread; +TX_MUTEX *next_mutex; +UINT inheritance_priority; +UINT new_priority; +#ifdef TX_MISRA_ENABLE +UINT status; +#endif + + + /* Determine if there is more than one thread suspended on the mutex. */ + if (mutex_ptr -> tx_mutex_suspended_count > ((UINT) 1)) + { + + /* Yes, prioritize the list so the highest priority thread is placed at the + front of the suspension list. */ +#ifdef TX_MISRA_ENABLE + do + { + status = _tx_mutex_prioritize(mutex_ptr); + } while (status != TX_SUCCESS); +#else + _tx_mutex_prioritize(mutex_ptr); +#endif + } + +#ifndef TX_NOT_INTERRUPTABLE + + /* Disable interrupts. */ + TX_DISABLE +#endif + + /* Pickup the thread at the front of the suspension list. */ + suspended_thread = mutex_ptr -> tx_mutex_suspension_list; + + /* Determine if there still are threads suspended for this mutex. */ + if (suspended_thread != TX_NULL) + { + + /* Setup the highest priority thread waiting on this mutex. */ + mutex_ptr -> tx_mutex_highest_priority_waiting = suspended_thread -> tx_thread_priority; + } + else + { + + /* No thread is waiting on this mutex. */ + mutex_ptr -> tx_mutex_highest_priority_waiting = ((UINT) TX_MAX_PRIORITIES); + } + + /* Pickup the mutex owner. */ + owner_ptr = mutex_ptr -> tx_mutex_owner; + +#ifndef TX_NOT_INTERRUPTABLE + + /* Restore interrupts. */ + TX_RESTORE +#endif + + /* The owner is not NULL. A thread could only suspend on this mutex while a + thread owned it, since tx_mutex_get raises the owner's priority when it does, + and the owner keeps the mutex for as long as this processing can run. */ + + /* Default the inheritance priority to disabled. */ + inheritance_priority = ((UINT) TX_MAX_PRIORITIES); + + /* Search the mutexes owned by this thread to determine the highest priority + it still inherits. */ + next_mutex = owner_ptr -> tx_thread_owned_mutex_list; + while (next_mutex != TX_NULL) + { + + /* Does this mutex support priority inheritance? */ + if (next_mutex -> tx_mutex_inherit == TX_TRUE) + { + + /* Determine if highest priority field of the mutex is higher than the + inheritance priority so far. */ + if (next_mutex -> tx_mutex_highest_priority_waiting < inheritance_priority) + { + + /* Use this priority. */ + inheritance_priority = next_mutex -> tx_mutex_highest_priority_waiting; + } + } + + /* Move mutex pointer to the next mutex in the list. */ + next_mutex = next_mutex -> tx_mutex_owned_next; + + /* Are we at the end of the list? */ + if (next_mutex == owner_ptr -> tx_thread_owned_mutex_list) + { + + /* Yes, set the next mutex to NULL. */ + next_mutex = TX_NULL; + } + } + +#ifndef TX_NOT_INTERRUPTABLE + + /* Disable interrupts. */ + TX_DISABLE +#endif + + /* Set the inherit priority to that of the highest priority thread waiting on + any mutex the owner holds. */ + owner_ptr -> tx_thread_inherit_priority = inheritance_priority; + + /* The owner runs at the higher of its own priority and the inherited one. */ + new_priority = owner_ptr -> tx_thread_user_priority; + if (inheritance_priority < new_priority) + { + + /* Use the inherited priority. */ + new_priority = inheritance_priority; + } + +#ifndef TX_NOT_INTERRUPTABLE + + /* Restore interrupts. */ + TX_RESTORE +#endif + + /* Determine if the owner's priority has to change. */ + if (owner_ptr -> tx_thread_priority != new_priority) + { + + /* Yes, change the priority of the owner. */ + _tx_mutex_priority_change(owner_ptr, new_priority); + } +} diff --git a/test/smp/cmake/regression/CMakeLists.txt b/test/smp/cmake/regression/CMakeLists.txt index cdf0c5859..a567621dc 100644 --- a/test/smp/cmake/regression/CMakeLists.txt +++ b/test/smp/cmake/regression/CMakeLists.txt @@ -45,6 +45,7 @@ set(regression_test_cases ${SOURCE_DIR}/threadx_interrupt_control_test.c ${SOURCE_DIR}/threadx_mutex_basic_test.c ${SOURCE_DIR}/threadx_mutex_delete_test.c + ${SOURCE_DIR}/threadx_mutex_inherit_wait_exit_test.c ${SOURCE_DIR}/threadx_mutex_information_test.c ${SOURCE_DIR}/threadx_mutex_nested_priority_inheritance_test.c ${SOURCE_DIR}/threadx_mutex_no_preemption_test.c diff --git a/test/smp/regression/testcontrol.c b/test/smp/regression/testcontrol.c index c964dc03a..2f1547457 100644 --- a/test/smp/regression/testcontrol.c +++ b/test/smp/regression/testcontrol.c @@ -204,6 +204,7 @@ void threadx_mutex_thread_terminate_application_define(void *); void threadx_mutex_priority_inheritance_application_define(void *); void threadx_mutex_nested_priority_inheritance_application_define(void *); void threadx_mutex_prioritize_application_define(void *); +void threadx_mutex_inherit_wait_exit_application_define(void *); void threadx_mutex_information_application_define(void *); void threadx_queue_basic_application_define(void *); @@ -351,6 +352,7 @@ TEST_ENTRY test_control_tests[] = threadx_mutex_priority_inheritance_application_define, threadx_mutex_prioritize_application_define, threadx_mutex_nested_priority_inheritance_application_define, + threadx_mutex_inherit_wait_exit_application_define, threadx_mutex_information_application_define, threadx_queue_basic_application_define, diff --git a/test/smp/regression/threadx_mutex_inherit_wait_exit_test.c b/test/smp/regression/threadx_mutex_inherit_wait_exit_test.c new file mode 100644 index 000000000..d0d7c26e8 --- /dev/null +++ b/test/smp/regression/threadx_mutex_inherit_wait_exit_test.c @@ -0,0 +1,232 @@ +/*************************************************************************** + * 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. + * + * SPDX-License-Identifier: MIT + **************************************************************************/ + +/* This test checks that a priority-inheritance mutex owner gives up an inherited + priority when the thread it inherited it from stops waiting without obtaining the + mutex: through a wait timeout, tx_thread_wait_abort or tx_thread_terminate. The + owner must drop to the highest priority still waiting on any mutex it owns, or to + its own priority if none is. */ + +#include +#include "tx_api.h" + + +/* Wait a tick at a time, for at most 200 ticks, until a condition holds. A thread the + control thread starts can take more than a tick to get there on an SMP host. */ + +#define WAIT_UNTIL(condition) { UINT ticks = 0; while ((!(condition)) && (ticks < 200)) { tx_thread_sleep(1); ticks++; } } + +#define WAITERS 4 + + +static TX_THREAD control_thread; +static TX_THREAD owner_thread; +static TX_THREAD waiter[WAITERS]; +static CHAR *thread_stack[WAITERS + 1]; +static TX_MUTEX mutex_0; +static TX_MUTEX mutex_1; +static ULONG waiter_wait_option; +static volatile UINT waiter_status; + + +/* Define thread prototypes. */ + +static void control_thread_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_mutex_inherit_wait_exit_application_define(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; + + status = tx_thread_create(&control_thread, "control thread", control_thread_entry, 0, + pointer, TEST_STACK_SIZE_PRINTF, + 1, 1, TX_NO_TIME_SLICE, TX_AUTO_START); + pointer = pointer + TEST_STACK_SIZE_PRINTF; + + /* The remaining threads are created and deleted by each scenario. */ + for (i = 0; i <= WAITERS; i++) + { + thread_stack[i] = pointer; + pointer = pointer + TEST_STACK_SIZE_PRINTF; + } + + status += tx_mutex_create(&mutex_0, "mutex 0", TX_INHERIT); + status += tx_mutex_create(&mutex_1, "mutex 1", TX_INHERIT); + + /* Check status. */ + if (status != TX_SUCCESS) + { + + printf("Running Mutex Inherit Wait Exit Test................................ ERROR #1\n"); + test_control_return(1); + } +} + + +/* The owner takes both mutexes and parks. */ + +static void owner_thread_entry(ULONG thread_input) +{ + + tx_mutex_get(&mutex_0, TX_WAIT_FOREVER); + tx_mutex_get(&mutex_1, TX_WAIT_FOREVER); + tx_thread_suspend(&owner_thread); +} + + +/* A waiter blocks on mutex 0, or on mutex 1 if its input is 1. */ + +static void waiter_entry(ULONG thread_input) +{ + + waiter_status = tx_mutex_get((thread_input == 0) ? &mutex_0 : &mutex_1, waiter_wait_option); + tx_thread_suspend(tx_thread_identify()); +} + + +static void check(UINT condition, UINT error) +{ + + if (condition == TX_FALSE) + { + + printf("ERROR #%u\n", error); + test_control_return(1); + } +} + + +static void start_thread(TX_THREAD *thread_ptr, VOID (*entry)(ULONG), ULONG input, UINT stack, UINT priority) +{ + + check(tx_thread_create(thread_ptr, "test thread", entry, input, thread_stack[stack], TEST_STACK_SIZE_PRINTF, + priority, priority, TX_NO_TIME_SLICE, TX_AUTO_START) == TX_SUCCESS, 2); +} + + +static void start_owner(void) +{ + + start_thread(&owner_thread, owner_thread_entry, 0, WAITERS, 15); + WAIT_UNTIL((mutex_1.tx_mutex_owner == &owner_thread) && (owner_thread.tx_thread_state == TX_SUSPENDED)) +} + + +/* The owner at 15 holds mutex 0, and waiter 0 at 5 blocks on it and then leaves + without it. how: 0 is a wait timeout, 1 is tx_thread_wait_abort, 2 is + tx_thread_terminate. */ + +static void single_waiter_leaves(UINT how, UINT error) +{ + + start_owner(); + waiter_wait_option = (how == 0) ? 50 : TX_WAIT_FOREVER; + waiter_status = 0xFFFF; + start_thread(&waiter[0], waiter_entry, 0, 0, 5); + WAIT_UNTIL(waiter[0].tx_thread_state == TX_MUTEX_SUSP) + check(owner_thread.tx_thread_priority == 5, error); + + if (how == 1) + { + tx_thread_wait_abort(&waiter[0]); + } + else if (how == 2) + { + tx_thread_terminate(&waiter[0]); + } + + if (how != 2) + { + WAIT_UNTIL(waiter_status != 0xFFFF) + check(waiter_status == ((how == 0) ? TX_NOT_AVAILABLE : TX_WAIT_ABORTED), error + 1); + } + + /* Nothing waits on the mutex any more, so the owner is back at its own priority. */ + check(mutex_0.tx_mutex_highest_priority_waiting == TX_MAX_PRIORITIES, error + 2); + check(owner_thread.tx_thread_priority == 15, error + 3); + + tx_thread_terminate(&waiter[0]); + tx_thread_delete(&waiter[0]); + tx_thread_terminate(&owner_thread); + tx_thread_delete(&owner_thread); +} + + +/* The owner at 15 holds both mutexes. Waiters at 5, 8 and 20 block on mutex 0 and + one at 12 on mutex 1. As they are aborted in turn, the owner keeps the highest + priority still waiting on either mutex. */ + +static void waiters_leave_in_turn(UINT error) +{ + +static const UINT priority[WAITERS] = {5, 8, 20, 12}; +static const UINT abort_order[WAITERS] = {2, 0, 1, 3}; +static const UINT owner_priority[WAITERS] = {5, 8, 12, 15}; +UINT i; + + + start_owner(); + waiter_wait_option = TX_WAIT_FOREVER; + for (i = 0; i < WAITERS; i++) + { + start_thread(&waiter[i], waiter_entry, (i == 3) ? 1 : 0, i, priority[i]); + } + WAIT_UNTIL((mutex_0.tx_mutex_suspended_count == 3) && (mutex_1.tx_mutex_suspended_count == 1)) + check(owner_thread.tx_thread_priority == 5, error); + + for (i = 0; i < WAITERS; i++) + { + tx_thread_wait_abort(&waiter[abort_order[i]]); + check(owner_thread.tx_thread_priority == owner_priority[i], error + 1 + i); + } + + for (i = 0; i < WAITERS; i++) + { + tx_thread_terminate(&waiter[i]); + tx_thread_delete(&waiter[i]); + } + tx_thread_terminate(&owner_thread); + tx_thread_delete(&owner_thread); +} + + +static void control_thread_entry(ULONG thread_input) +{ + + /* Inform user. */ + printf("Running Mutex Inherit Wait Exit Test................................ "); + + single_waiter_leaves(0, 10); + single_waiter_leaves(1, 20); + single_waiter_leaves(2, 30); + waiters_leave_in_turn(40); + + /* Successful test. */ + printf("SUCCESS!\n"); + test_control_return(0); +} diff --git a/test/tx/cmake/regression/CMakeLists.txt b/test/tx/cmake/regression/CMakeLists.txt index 6b94a8576..5ad11380d 100644 --- a/test/tx/cmake/regression/CMakeLists.txt +++ b/test/tx/cmake/regression/CMakeLists.txt @@ -42,6 +42,7 @@ set(regression_test_cases ${SOURCE_DIR}/threadx_interrupt_control_test.c ${SOURCE_DIR}/threadx_mutex_basic_test.c ${SOURCE_DIR}/threadx_mutex_delete_test.c + ${SOURCE_DIR}/threadx_mutex_inherit_wait_exit_test.c ${SOURCE_DIR}/threadx_mutex_information_test.c ${SOURCE_DIR}/threadx_mutex_nested_priority_inheritance_test.c ${SOURCE_DIR}/threadx_mutex_no_preemption_test.c diff --git a/test/tx/regression/testcontrol.c b/test/tx/regression/testcontrol.c index bec50ee76..a7e20ceb1 100644 --- a/test/tx/regression/testcontrol.c +++ b/test/tx/regression/testcontrol.c @@ -176,6 +176,7 @@ void threadx_mutex_thread_terminate_application_define(void *); void threadx_mutex_priority_inheritance_application_define(void *); void threadx_mutex_nested_priority_inheritance_application_define(void *); void threadx_mutex_prioritize_application_define(void *); +void threadx_mutex_inherit_wait_exit_application_define(void *); void threadx_mutex_information_application_define(void *); void threadx_queue_basic_application_define(void *); @@ -295,6 +296,7 @@ TEST_ENTRY test_control_tests[] = threadx_mutex_priority_inheritance_application_define, threadx_mutex_prioritize_application_define, threadx_mutex_nested_priority_inheritance_application_define, + threadx_mutex_inherit_wait_exit_application_define, threadx_mutex_information_application_define, threadx_queue_basic_application_define, diff --git a/test/tx/regression/threadx_mutex_inherit_wait_exit_test.c b/test/tx/regression/threadx_mutex_inherit_wait_exit_test.c new file mode 100644 index 000000000..d0d7c26e8 --- /dev/null +++ b/test/tx/regression/threadx_mutex_inherit_wait_exit_test.c @@ -0,0 +1,232 @@ +/*************************************************************************** + * 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. + * + * SPDX-License-Identifier: MIT + **************************************************************************/ + +/* This test checks that a priority-inheritance mutex owner gives up an inherited + priority when the thread it inherited it from stops waiting without obtaining the + mutex: through a wait timeout, tx_thread_wait_abort or tx_thread_terminate. The + owner must drop to the highest priority still waiting on any mutex it owns, or to + its own priority if none is. */ + +#include +#include "tx_api.h" + + +/* Wait a tick at a time, for at most 200 ticks, until a condition holds. A thread the + control thread starts can take more than a tick to get there on an SMP host. */ + +#define WAIT_UNTIL(condition) { UINT ticks = 0; while ((!(condition)) && (ticks < 200)) { tx_thread_sleep(1); ticks++; } } + +#define WAITERS 4 + + +static TX_THREAD control_thread; +static TX_THREAD owner_thread; +static TX_THREAD waiter[WAITERS]; +static CHAR *thread_stack[WAITERS + 1]; +static TX_MUTEX mutex_0; +static TX_MUTEX mutex_1; +static ULONG waiter_wait_option; +static volatile UINT waiter_status; + + +/* Define thread prototypes. */ + +static void control_thread_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_mutex_inherit_wait_exit_application_define(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; + + status = tx_thread_create(&control_thread, "control thread", control_thread_entry, 0, + pointer, TEST_STACK_SIZE_PRINTF, + 1, 1, TX_NO_TIME_SLICE, TX_AUTO_START); + pointer = pointer + TEST_STACK_SIZE_PRINTF; + + /* The remaining threads are created and deleted by each scenario. */ + for (i = 0; i <= WAITERS; i++) + { + thread_stack[i] = pointer; + pointer = pointer + TEST_STACK_SIZE_PRINTF; + } + + status += tx_mutex_create(&mutex_0, "mutex 0", TX_INHERIT); + status += tx_mutex_create(&mutex_1, "mutex 1", TX_INHERIT); + + /* Check status. */ + if (status != TX_SUCCESS) + { + + printf("Running Mutex Inherit Wait Exit Test................................ ERROR #1\n"); + test_control_return(1); + } +} + + +/* The owner takes both mutexes and parks. */ + +static void owner_thread_entry(ULONG thread_input) +{ + + tx_mutex_get(&mutex_0, TX_WAIT_FOREVER); + tx_mutex_get(&mutex_1, TX_WAIT_FOREVER); + tx_thread_suspend(&owner_thread); +} + + +/* A waiter blocks on mutex 0, or on mutex 1 if its input is 1. */ + +static void waiter_entry(ULONG thread_input) +{ + + waiter_status = tx_mutex_get((thread_input == 0) ? &mutex_0 : &mutex_1, waiter_wait_option); + tx_thread_suspend(tx_thread_identify()); +} + + +static void check(UINT condition, UINT error) +{ + + if (condition == TX_FALSE) + { + + printf("ERROR #%u\n", error); + test_control_return(1); + } +} + + +static void start_thread(TX_THREAD *thread_ptr, VOID (*entry)(ULONG), ULONG input, UINT stack, UINT priority) +{ + + check(tx_thread_create(thread_ptr, "test thread", entry, input, thread_stack[stack], TEST_STACK_SIZE_PRINTF, + priority, priority, TX_NO_TIME_SLICE, TX_AUTO_START) == TX_SUCCESS, 2); +} + + +static void start_owner(void) +{ + + start_thread(&owner_thread, owner_thread_entry, 0, WAITERS, 15); + WAIT_UNTIL((mutex_1.tx_mutex_owner == &owner_thread) && (owner_thread.tx_thread_state == TX_SUSPENDED)) +} + + +/* The owner at 15 holds mutex 0, and waiter 0 at 5 blocks on it and then leaves + without it. how: 0 is a wait timeout, 1 is tx_thread_wait_abort, 2 is + tx_thread_terminate. */ + +static void single_waiter_leaves(UINT how, UINT error) +{ + + start_owner(); + waiter_wait_option = (how == 0) ? 50 : TX_WAIT_FOREVER; + waiter_status = 0xFFFF; + start_thread(&waiter[0], waiter_entry, 0, 0, 5); + WAIT_UNTIL(waiter[0].tx_thread_state == TX_MUTEX_SUSP) + check(owner_thread.tx_thread_priority == 5, error); + + if (how == 1) + { + tx_thread_wait_abort(&waiter[0]); + } + else if (how == 2) + { + tx_thread_terminate(&waiter[0]); + } + + if (how != 2) + { + WAIT_UNTIL(waiter_status != 0xFFFF) + check(waiter_status == ((how == 0) ? TX_NOT_AVAILABLE : TX_WAIT_ABORTED), error + 1); + } + + /* Nothing waits on the mutex any more, so the owner is back at its own priority. */ + check(mutex_0.tx_mutex_highest_priority_waiting == TX_MAX_PRIORITIES, error + 2); + check(owner_thread.tx_thread_priority == 15, error + 3); + + tx_thread_terminate(&waiter[0]); + tx_thread_delete(&waiter[0]); + tx_thread_terminate(&owner_thread); + tx_thread_delete(&owner_thread); +} + + +/* The owner at 15 holds both mutexes. Waiters at 5, 8 and 20 block on mutex 0 and + one at 12 on mutex 1. As they are aborted in turn, the owner keeps the highest + priority still waiting on either mutex. */ + +static void waiters_leave_in_turn(UINT error) +{ + +static const UINT priority[WAITERS] = {5, 8, 20, 12}; +static const UINT abort_order[WAITERS] = {2, 0, 1, 3}; +static const UINT owner_priority[WAITERS] = {5, 8, 12, 15}; +UINT i; + + + start_owner(); + waiter_wait_option = TX_WAIT_FOREVER; + for (i = 0; i < WAITERS; i++) + { + start_thread(&waiter[i], waiter_entry, (i == 3) ? 1 : 0, i, priority[i]); + } + WAIT_UNTIL((mutex_0.tx_mutex_suspended_count == 3) && (mutex_1.tx_mutex_suspended_count == 1)) + check(owner_thread.tx_thread_priority == 5, error); + + for (i = 0; i < WAITERS; i++) + { + tx_thread_wait_abort(&waiter[abort_order[i]]); + check(owner_thread.tx_thread_priority == owner_priority[i], error + 1 + i); + } + + for (i = 0; i < WAITERS; i++) + { + tx_thread_terminate(&waiter[i]); + tx_thread_delete(&waiter[i]); + } + tx_thread_terminate(&owner_thread); + tx_thread_delete(&owner_thread); +} + + +static void control_thread_entry(ULONG thread_input) +{ + + /* Inform user. */ + printf("Running Mutex Inherit Wait Exit Test................................ "); + + single_waiter_leaves(0, 10); + single_waiter_leaves(1, 20); + single_waiter_leaves(2, 30); + waiters_leave_in_turn(40); + + /* Successful test. */ + printf("SUCCESS!\n"); + test_control_return(0); +}