|
7 | 7 | * Date Author Notes |
8 | 8 | * 2021-09.01 luckyzjq the first version |
9 | 9 | * 2023-09-15 xqyjlj change stack size in cpu64 |
| 10 | + * 2026-07-15 meng-plus add cross-thread delete owner regression (#11619) |
10 | 11 | */ |
11 | 12 |
|
12 | 13 | /** |
|
22 | 23 | * - Priority inheritance when high-priority threads are blocked by lower-priority holders |
23 | 24 | * - Behavior differences between static and dynamic mutexes |
24 | 25 | * - Mutex release error handling, invalid release, and cleanup |
| 26 | + * - Cross-thread delete of a mutex owner must release the lock and wake waiters |
25 | 27 | * Verification Metrics: |
26 | 28 | * - Correct return codes for all mutex operations (RT_EOK, timeouts, error states) |
27 | 29 | * - Proper priority inheritance and restoration during contention |
@@ -790,6 +792,118 @@ static void test_recurse_lock(void) |
790 | 792 | uassert_true(result == RT_EOK); |
791 | 793 | } |
792 | 794 |
|
| 795 | +#ifdef RT_USING_HEAP |
| 796 | +/* |
| 797 | + * Issue #11619: deleting a thread that still holds a mutex must not leave an |
| 798 | + * orphan lock; waiters blocked on that mutex must be woken and acquire ownership. |
| 799 | + */ |
| 800 | +static volatile int _holder_ready; |
| 801 | +static volatile int _waiter_got; |
| 802 | + |
| 803 | +static void orphan_mutex_holder_entry(void *param) |
| 804 | +{ |
| 805 | + rt_mutex_t mutex = (rt_mutex_t)param; |
| 806 | + |
| 807 | + if (rt_mutex_take(mutex, RT_WAITING_FOREVER) != RT_EOK) |
| 808 | + { |
| 809 | + uassert_true(RT_FALSE); |
| 810 | + return; |
| 811 | + } |
| 812 | + |
| 813 | + _holder_ready = 1; |
| 814 | + |
| 815 | + /* Hold forever until deleted by another thread */ |
| 816 | + while (1) |
| 817 | + { |
| 818 | + rt_thread_mdelay(1000); |
| 819 | + } |
| 820 | +} |
| 821 | + |
| 822 | +static void orphan_mutex_waiter_entry(void *param) |
| 823 | +{ |
| 824 | + rt_err_t result; |
| 825 | + rt_mutex_t mutex = (rt_mutex_t)param; |
| 826 | + |
| 827 | + result = rt_mutex_take(mutex, RT_WAITING_FOREVER); |
| 828 | + uassert_true(result == RT_EOK); |
| 829 | + |
| 830 | + result = rt_mutex_release(mutex); |
| 831 | + uassert_true(result == RT_EOK); |
| 832 | + |
| 833 | + /* Signal after release so the main thread can take without racing */ |
| 834 | + _waiter_got = 1; |
| 835 | +} |
| 836 | + |
| 837 | +static void test_cross_thread_delete_mutex_owner(void) |
| 838 | +{ |
| 839 | + rt_err_t result; |
| 840 | + rt_thread_t holder; |
| 841 | + rt_thread_t waiter; |
| 842 | + int timeout; |
| 843 | + |
| 844 | + _holder_ready = 0; |
| 845 | + _waiter_got = 0; |
| 846 | + |
| 847 | + result = rt_mutex_init(&static_mutex, "orphan_mtx", RT_IPC_FLAG_PRIO); |
| 848 | + uassert_true(result == RT_EOK); |
| 849 | + |
| 850 | + /* Lower priority holder takes the mutex and keeps it */ |
| 851 | + holder = rt_thread_create("mtx_hold", |
| 852 | + orphan_mutex_holder_entry, |
| 853 | + &static_mutex, |
| 854 | + THREAD_STACKSIZE, |
| 855 | + 12, |
| 856 | + 10); |
| 857 | + uassert_true(holder != RT_NULL); |
| 858 | + rt_thread_startup(holder); |
| 859 | + |
| 860 | + timeout = 100; |
| 861 | + while ((_holder_ready == 0) && (timeout-- > 0)) |
| 862 | + { |
| 863 | + rt_thread_mdelay(10); |
| 864 | + } |
| 865 | + uassert_true(_holder_ready == 1); |
| 866 | + |
| 867 | + /* Higher priority waiter blocks on the held mutex */ |
| 868 | + waiter = rt_thread_create("mtx_wait", |
| 869 | + orphan_mutex_waiter_entry, |
| 870 | + &static_mutex, |
| 871 | + THREAD_STACKSIZE, |
| 872 | + 10, |
| 873 | + 10); |
| 874 | + uassert_true(waiter != RT_NULL); |
| 875 | + rt_thread_startup(waiter); |
| 876 | + |
| 877 | + /* Ensure waiter is pending before cross-thread delete */ |
| 878 | + rt_thread_mdelay(50); |
| 879 | + uassert_true(_waiter_got == 0); |
| 880 | + |
| 881 | + result = rt_thread_delete(holder); |
| 882 | + uassert_true(result == RT_EOK); |
| 883 | + |
| 884 | + timeout = 100; |
| 885 | + while ((_waiter_got == 0) && (timeout-- > 0)) |
| 886 | + { |
| 887 | + rt_thread_mdelay(10); |
| 888 | + } |
| 889 | + uassert_true(_waiter_got == 1); |
| 890 | + |
| 891 | + /* After waiter releases, mutex must be acquirable again */ |
| 892 | + result = rt_mutex_take(&static_mutex, rt_tick_from_millisecond(500)); |
| 893 | + uassert_true(result == RT_EOK); |
| 894 | + uassert_true(rt_mutex_get_hold(&static_mutex) == 1); |
| 895 | + |
| 896 | + result = rt_mutex_release(&static_mutex); |
| 897 | + uassert_true(result == RT_EOK); |
| 898 | + |
| 899 | + result = rt_mutex_detach(&static_mutex); |
| 900 | + uassert_true(result == RT_EOK); |
| 901 | + |
| 902 | + /* Let waiter thread exit cleanly */ |
| 903 | + rt_thread_mdelay(50); |
| 904 | +} |
| 905 | +#endif /* RT_USING_HEAP */ |
| 906 | + |
793 | 907 | static rt_err_t utest_tc_init(void) |
794 | 908 | { |
795 | 909 | #ifdef RT_USING_HEAP |
@@ -821,6 +935,7 @@ static void testcase(void) |
821 | 935 | UTEST_UNIT_RUN(test_dynamic_mutex_release); |
822 | 936 | UTEST_UNIT_RUN(test_dynamic_mutex_trytake); |
823 | 937 | UTEST_UNIT_RUN(test_dynamic_pri_reverse); |
| 938 | + UTEST_UNIT_RUN(test_cross_thread_delete_mutex_owner); |
824 | 939 | #endif |
825 | 940 | UTEST_UNIT_RUN(test_recurse_lock); |
826 | 941 | } |
|
0 commit comments