[add][thread]Add api:rt_thread_suspend_force allow suspend other threads.

This commit is contained in:
Rbb666
2025-09-01 22:47:28 +08:00
committed by R b b666
parent afb3f22973
commit e1c1a07c48
3 changed files with 510 additions and 3 deletions

View File

@@ -44,6 +44,7 @@ if GetDepend(['UTEST_MAILBOX_TC']):
if GetDepend(['UTEST_THREAD_TC']):
src += ['thread_tc.c']
src += ['thread_overflow_tc.c']
src += ['thread_suspend_tc.c']
if GetDepend(['UTEST_DEVICE_TC']):
src += ['device_tc.c']

View File

@@ -0,0 +1,478 @@
/*
* Copyright (c) 2025, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2025-09-02 Rbb666 utest case for rt_thread_suspend comprehensive tests
*/
#include <rtthread.h>
#include <rtdevice.h>
#include "utest.h"
#define THREAD_STACK_SIZE 1024
#define THREAD_TIMESLICE 5
#define TEST_THREAD_PRIORITY 25
/* Global variables for normal usage test */
static rt_thread_t target_thread = RT_NULL;
static rt_thread_t monitor_thread = RT_NULL;
static rt_sem_t sync_sem = RT_NULL;
static volatile rt_uint32_t work_counter = 0;
static volatile rt_bool_t suspend_test_done = RT_FALSE;
static volatile rt_bool_t suspend_success = RT_FALSE;
static volatile rt_bool_t resume_success = RT_FALSE;
/* Global variables for deadlock test */
static rt_mutex_t test_mutex = RT_NULL;
static rt_thread_t holder_thread = RT_NULL;
static rt_thread_t waiter_thread = RT_NULL;
static volatile rt_uint32_t shared_counter = 0;
static volatile rt_bool_t holder_got_mutex = RT_FALSE;
static volatile rt_bool_t deadlock_detected = RT_FALSE;
static volatile rt_bool_t test_completed = RT_FALSE;
static volatile rt_bool_t thread_started = RT_FALSE;
static volatile rt_bool_t thread_should_exit = RT_FALSE;
/* Target work thread - the thread to be suspended */
static void target_work_thread(void *parameter)
{
while (1)
{
if (!suspend_test_done)
{
work_counter++;
}
/* Yield CPU appropriately to simulate normal work */
rt_thread_mdelay(10);
}
}
/* Monitor thread - responsible for suspending and resuming target thread */
static void monitor_control_thread(void *parameter)
{
rt_uint32_t counter_before, counter_after;
/* Wait for target thread to start working */
rt_thread_mdelay(300);
/* Record counter value before suspend */
counter_before = work_counter;
/* Use rt_thread_suspend to suspend target thread */
if (rt_thread_suspend(target_thread) == RT_EOK)
{
suspend_success = RT_TRUE;
/* Trigger scheduling to ensure thread is suspended */
rt_schedule();
/* Wait for a while to verify thread is indeed suspended */
rt_thread_mdelay(500);
counter_after = work_counter;
/* Verify thread is indeed suspended (counter should stop changing) */
if (counter_after == counter_before)
{
/* Resume target thread */
if (rt_thread_resume(target_thread) == RT_EOK)
{
resume_success = RT_TRUE;
/* Wait for a while to verify thread resumes work */
rt_thread_mdelay(200);
}
}
}
/* End test */
suspend_test_done = RT_TRUE;
/* Send semaphore to notify test completion */
rt_sem_release(sync_sem);
/* Keep running until deleted */
while (1)
{
rt_thread_mdelay(100);
}
}
/* Thread that holds the mutex */
static void mutex_holder_thread(void *parameter)
{
if (rt_mutex_take(test_mutex, RT_WAITING_FOREVER) == RT_EOK)
{
holder_got_mutex = RT_TRUE;
/* Simulate critical section work */
for (int i = 0; i < 1000 && !test_completed; i++)
{
shared_counter++;
if (i % 200 == 0)
{
rt_thread_mdelay(10);
}
}
if (!test_completed)
{
rt_mutex_release(test_mutex);
}
}
rt_kprintf("Holder thread exiting\n");
/* Keep running until deleted */
while (1)
{
rt_thread_mdelay(100);
}
}
/* Thread that waits for the mutex */
static void mutex_waiter_thread(void *parameter)
{
/* Wait a bit to ensure holder gets mutex first */
rt_thread_mdelay(50);
rt_err_t result = rt_mutex_take(test_mutex, rt_tick_from_millisecond(1500));
if (result == RT_EOK)
{
shared_counter += 1000;
rt_mutex_release(test_mutex);
}
else
{
/* Timeout indicates deadlock - holder is suspended and cannot release lock */
deadlock_detected = RT_TRUE;
rt_kprintf("Deadlock detected: waiter timeout (holder suspended with mutex)\n");
}
/* Keep running until deleted */
while (1)
{
rt_thread_mdelay(100);
}
}
void simple_thread_entry(void *param)
{
volatile rt_bool_t *flag = (volatile rt_bool_t *)param;
*flag = RT_TRUE;
/* Keep the thread running until it's suspended and deleted */
while (1)
{
rt_thread_mdelay(100);
}
}
/* Test normal usage of rt_thread_suspend function */
static void test_suspend_force_normal_usage(void)
{
/* Reset global variables */
work_counter = 0;
suspend_test_done = RT_FALSE;
suspend_success = RT_FALSE;
resume_success = RT_FALSE;
/* Create synchronization semaphore */
sync_sem = rt_sem_create("sync", 0, RT_IPC_FLAG_FIFO);
uassert_not_null(sync_sem);
/* Create target work thread */
target_thread = rt_thread_create("target",
target_work_thread,
RT_NULL,
THREAD_STACK_SIZE,
TEST_THREAD_PRIORITY,
THREAD_TIMESLICE);
uassert_not_null(target_thread);
/* Create monitor thread */
monitor_thread = rt_thread_create("monitor",
monitor_control_thread,
RT_NULL,
THREAD_STACK_SIZE,
UTEST_THR_PRIORITY,
THREAD_TIMESLICE);
uassert_not_null(monitor_thread);
/* Start threads */
rt_thread_startup(target_thread);
rt_thread_startup(monitor_thread);
/* Wait for test completion */
rt_sem_take(sync_sem, RT_WAITING_FOREVER);
/* Wait for a while to ensure threads exit normally */
rt_thread_mdelay(100);
/* Verify test results */
uassert_true(suspend_success);
uassert_true(resume_success);
uassert_true(work_counter > 0);
/* Clean up resources */
if (sync_sem)
{
rt_sem_delete(sync_sem);
sync_sem = RT_NULL;
}
/* Delete threads */
if (target_thread != RT_NULL)
{
rt_thread_delete(target_thread);
target_thread = RT_NULL;
}
if (monitor_thread != RT_NULL)
{
rt_thread_delete(monitor_thread);
monitor_thread = RT_NULL;
}
}
/* Basic API test */
static void test_suspend_force_api_basic(void)
{
rt_thread_t api_thread;
/* Reset global variables */
thread_started = RT_FALSE;
thread_should_exit = RT_FALSE;
/* Create a simple test thread */
api_thread = rt_thread_create("api_test",
simple_thread_entry,
(void *)&thread_started,
THREAD_STACK_SIZE,
UTEST_THR_PRIORITY,
THREAD_TIMESLICE);
uassert_not_null(api_thread);
rt_thread_startup(api_thread);
rt_thread_mdelay(50); /* Wait for thread to start */
uassert_true(thread_started);
/* Test basic suspend functionality */
rt_err_t result = rt_thread_suspend(api_thread);
uassert_true(result == RT_EOK);
rt_schedule();
rt_thread_mdelay(100);
/* Resume thread */
result = rt_thread_resume(api_thread);
uassert_true(result == RT_EOK);
rt_thread_mdelay(50);
/* Clean up - delete the thread directly */
if (api_thread != RT_NULL)
{
rt_thread_delete(api_thread);
api_thread = RT_NULL;
}
/* Reset global variables for next test */
thread_started = RT_FALSE;
thread_should_exit = RT_FALSE;
}
/* Test suspend on thread that is created but not started */
static void test_suspend_force_not_started_thread(void)
{
rt_thread_t not_started_thread;
/* Create a thread but don't start it */
not_started_thread = rt_thread_create("not_started",
simple_thread_entry,
(void *)&thread_started,
THREAD_STACK_SIZE,
UTEST_THR_PRIORITY,
THREAD_TIMESLICE);
uassert_not_null(not_started_thread);
/* Verify thread is in INIT state */
uassert_true((RT_SCHED_CTX(not_started_thread).stat & RT_THREAD_STAT_MASK) == RT_THREAD_INIT);
/* Try to suspend a thread that hasn't been started yet */
rt_err_t suspend_result = rt_thread_suspend(not_started_thread);
rt_schedule();
uassert_true(suspend_result == -RT_ERROR);
/* Try to resume the not-started thread */
rt_err_t resume_result = rt_thread_resume(not_started_thread);
uassert_true(resume_result == -RT_EINVAL);
/* Now start the thread to see if it works normally */
rt_err_t startup_result = rt_thread_startup(not_started_thread);
uassert_true(startup_result == RT_EOK);
/* Wait a bit to see if thread starts normally */
rt_thread_mdelay(100);
/* The thread should have started successfully despite previous suspend/resume calls */
uassert_true(thread_started == RT_TRUE);
/* Clean up */
if (not_started_thread != RT_NULL)
{
rt_thread_delete(not_started_thread);
not_started_thread = RT_NULL;
}
/* Reset flag for next test */
thread_started = RT_FALSE;
}
/* Test deadlock risk */
static void test_suspend_force_deadlock_risk(void)
{
/* Reset global variables */
shared_counter = 0;
holder_got_mutex = RT_FALSE;
deadlock_detected = RT_FALSE;
test_completed = RT_FALSE;
/* Create mutex */
test_mutex = rt_mutex_create("test_mutex", RT_IPC_FLAG_PRIO);
uassert_not_null(test_mutex);
/* Create and start holder thread */
holder_thread = rt_thread_create("holder", mutex_holder_thread, RT_NULL,
THREAD_STACK_SIZE, UTEST_THR_PRIORITY, THREAD_TIMESLICE);
uassert_not_null(holder_thread);
rt_thread_startup(holder_thread);
/* Create and start waiter thread */
waiter_thread = rt_thread_create("waiter", mutex_waiter_thread, RT_NULL,
THREAD_STACK_SIZE, UTEST_THR_PRIORITY + 1, THREAD_TIMESLICE);
uassert_not_null(waiter_thread);
/* Now start waiter thread, it will try to acquire mutex held by suspended thread */
rt_thread_startup(waiter_thread);
/* Wait for holder to get mutex */
int timeout = 100; /* 1 second timeout */
while (!holder_got_mutex && timeout-- > 0)
{
rt_thread_mdelay(10);
}
uassert_true(holder_got_mutex);
/* This is the critical test! Suspend thread that holds the mutex */
rt_err_t suspend_result = rt_thread_suspend(holder_thread);
uassert_true(suspend_result == RT_EOK);
rt_kprintf("Suspended holder thread (which holds the mutex)\n");
rt_schedule();
/* Wait for waiter thread to try acquiring lock */
rt_thread_mdelay(2000);
uassert_true(deadlock_detected == RT_TRUE);
/* Resume thread */
rt_err_t resume_result = rt_thread_resume(holder_thread);
uassert_true(resume_result == RT_EOK);
rt_kprintf("Resumed holder thread\n");
test_completed = RT_TRUE;
/* Wait for threads to complete */
rt_thread_mdelay(1000);
/* Verify rt_thread_suspend and rt_thread_resume executed successfully */
uassert_true(suspend_result == RT_EOK);
uassert_true(resume_result == RT_EOK);
/* Verify system didn't crash, threads can work normally */
uassert_true(shared_counter > 0);
/* Clean up resources */
if (test_mutex)
{
rt_mutex_delete(test_mutex);
test_mutex = RT_NULL;
}
/* Delete threads */
if (holder_thread != RT_NULL)
{
rt_thread_delete(holder_thread);
holder_thread = RT_NULL;
}
if (waiter_thread != RT_NULL)
{
rt_thread_delete(waiter_thread);
waiter_thread = RT_NULL;
}
/* Wait again to ensure threads are cleaned up */
rt_thread_mdelay(200);
}
static rt_err_t utest_tc_init(void)
{
return RT_EOK;
}
static rt_err_t utest_tc_cleanup(void)
{
/* Reset all global variables to ensure clean state between tests */
work_counter = 0;
suspend_test_done = RT_FALSE;
suspend_success = RT_FALSE;
resume_success = RT_FALSE;
shared_counter = 0;
holder_got_mutex = RT_FALSE;
deadlock_detected = RT_FALSE;
test_completed = RT_FALSE;
thread_started = RT_FALSE;
thread_should_exit = RT_FALSE;
/* Clean up any remaining resources - safety check */
if (sync_sem != RT_NULL)
{
rt_sem_delete(sync_sem);
sync_sem = RT_NULL;
}
if (test_mutex != RT_NULL)
{
rt_mutex_delete(test_mutex);
test_mutex = RT_NULL;
}
/* Ensure all thread pointers are NULL */
target_thread = RT_NULL;
monitor_thread = RT_NULL;
holder_thread = RT_NULL;
waiter_thread = RT_NULL;
/* Give system time to complete cleanup */
rt_thread_mdelay(50);
return RT_EOK;
}
static void testcase(void)
{
UTEST_UNIT_RUN(test_suspend_force_api_basic);
UTEST_UNIT_RUN(test_suspend_force_not_started_thread);
UTEST_UNIT_RUN(test_suspend_force_normal_usage);
UTEST_UNIT_RUN(test_suspend_force_deadlock_risk);
}
UTEST_TC_EXPORT(testcase, "testcases.kernel.thread_suspend", utest_tc_init, utest_tc_cleanup, 30);

View File

@@ -947,14 +947,19 @@ rt_err_t rt_thread_suspend_to_list(rt_thread_t thread, rt_list_t *susp_list, int
/* parameter check */
RT_ASSERT(thread != RT_NULL);
RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);
RT_ASSERT(thread == rt_thread_self());
LOG_D("thread suspend: %s", thread->parent.name);
rt_sched_lock(&slvl);
stat = rt_sched_thread_get_stat(thread);
if ((stat != RT_THREAD_READY) && (stat != RT_THREAD_RUNNING))
if (stat == RT_THREAD_SUSPEND)
{
rt_sched_unlock(slvl);
/* Already suspended, just set status to success. */
return RT_EOK;
}
else if ((stat != RT_THREAD_READY) && (stat != RT_THREAD_RUNNING))
{
LOG_D("thread suspend: thread disorder, 0x%2x", RT_SCHED_CTX(thread).stat);
rt_sched_unlock(slvl);
@@ -1042,7 +1047,30 @@ RTM_EXPORT(rt_thread_suspend_with_flag);
/**
* @brief This function will suspend the specified thread and change it to suspend state.
*
* @param thread Handle of the thread to be suspended.
* @note This function can suspend both the current thread itself and other threads.
* Please use this API with extreme caution when suspending other threads.
*
* When suspending the current thread:
* rt_thread_suspend(rt_thread_self());
* This is generally safe as the thread voluntarily suspends itself.
*
* When suspending other threads:
* You have no way of knowing what code another thread is executing when you suspend it.
* If you suspend a thread while it is sharing a resource with other threads and occupying
* this resource (such as holding a mutex, semaphore, or other synchronization objects),
* deadlock or resource starvation can occur very easily.
*
* @warning Suspending other threads arbitrarily can lead to:
* - Deadlock situations
* - Resource starvation
* - System instability
* - Unpredictable behavior
*
* Only suspend other threads when you have full control over the system state
* and understand the implications.
*
* @param thread Handle of the thread to be suspended. Can be the current thread
* (rt_thread_self()) or any other thread.
*
* @return Return the operation status. If the return value is `RT_EOK`, the
* function is successfully executed.