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); +}