diff options
| author | GuEe-GUI <[email protected]> | 2026-06-16 14:20:20 +0800 |
|---|---|---|
| committer | Rbb666 <[email protected]> | 2026-06-17 09:51:13 +0800 |
| commit | 6fc3e18912e60505fa6e2a6a4d444ab84e326e9b (patch) | |
| tree | a4d06dbaf406cff353aefbff5b004769e9fad679 /src | |
| parent | 72a09a6aca696d2c2c847d09cd6ded196177dd01 (diff) | |
fix: stabilize smp_bind_affinity_tc on SMP CI
This change fixes intermittent failures of the `core.smp_bind_affinity`
utest across SMP CI targets. The original failure was a flaky assertion on
unbound threads (`thread_inc[x] != thread_tic[x]` in thread_entry), not
incorrect bind_cpu behavior. CI logs already showed T0 binding worked
(thread_inc[0] == thread_tic[0] == 100) while the not_equal check failed.
The root issue is that unbound-thread CPU placement is platform-dependent
and outside the bind_cpu contract. On dual-core RISC-V/ARMv7 CI, an
unbound thread may legitimately run all iterations on core 0 while T0
delays on the same core, so any assertion requiring cross-core migration
(thread_inc != thread_tic, or core_mask with multiple bits) remains flaky.
This patch narrows the test to what bind_affinity actually verifies: T0
bound to core 0 must always execute on core 0. Unbound threads only add
scheduling pressure; their core_mask is printed for observation but not
asserted.
Changes:
- Sample `rt_hw_cpu_id()` and update counters under `rt_sched_lock` to
avoid migration skew between counting and CPU sampling.
- Track `thread_core_mask` for diagnostics; assert only T0 binding via
`thread_inc[0] == thread_tic[0]` and `thread_core_mask[0] == 1`.
- Remove flaky unbound-thread assertions (`thread_inc != thread_tic` and
multi-core core_mask checks).
- Move T0 assertions to the main test thread after all workers finish.
- Use atomic `finsh_flag`; reset counters and `threads[]` in `utest_tc_init`.
- Spin in worker loops after `run_num` until cleanup deletes them (do not
return from thread_entry or use `RT_WAITING_FOREVER` with `rt_thread_delay`).
- Guard `rt_thread_delete` in cleanup with non-NULL checks.
Signed-off-by: GuEe-GUI <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/utest/smp/smp_bind_affinity_tc.c | 63 |
1 files changed, 46 insertions, 17 deletions
diff --git a/src/utest/smp/smp_bind_affinity_tc.c b/src/utest/smp/smp_bind_affinity_tc.c index 8247e3dbfd..5a54a2722f 100644 --- a/src/utest/smp/smp_bind_affinity_tc.c +++ b/src/utest/smp/smp_bind_affinity_tc.c @@ -24,11 +24,10 @@ * - Verify that threads bound to specific cores run on those cores. * * Test Scenarios: - * - RT_CPUS_NR threads (T0~T(RT_CPUS_NR-1)) are created, with only T0 bound to core 0. When thread Tx is running, - * - thread_tic[x] increments by 1; if x is equal to the core ID, thread_inc[x] also increments by 1. After the - * - program runs for a period of time, observe the value relationship between the thread_inc and thread_tic arrays. - * - If thread_inc[x] is equal to thread_tic[x], it indicates that thread Tx is correctly bound to core x. In this test case, - * - only thread_inc[0] will be equal to thread_tic[0]. + * - RT_CPUS_NR threads (T0~T(RT_CPUS_NR-1)) are created, with only T0 bound to core 0. + * - Each thread samples rt_hw_cpu_id() while running; T0 must stay on core 0. + * - Other threads are unbound and only used to add scheduling pressure; their CPU + * - placement is printed for observation but not asserted (platform-dependent). * * Verification Metrics: * - Output message: [I/utest] [ PASSED ] [ result ] testcase (core.smp_bind_affinity) @@ -50,29 +49,39 @@ static rt_thread_t threads[RT_CPUS_NR]; static struct rt_spinlock lock; static int thread_inc[RT_CPUS_NR] = {0}; static int thread_tic[RT_CPUS_NR] = {0}; -static int finsh_flag = 0; +static rt_uint32_t thread_core_mask[RT_CPUS_NR] = {0}; +static rt_atomic_t finsh_flag; static int num = 0; static void thread_entry(void *parameter) { - int id = 0; + int id; int para = *(int *)parameter; + while (1) { - thread_tic[para]++; + if (thread_tic[para] >= run_num) + { + /* Spin until cleanup deletes this thread (same as other smp utests) */ + while (1); + } + + rt_sched_lock_level_t slvl; + + /* Sample CPU and counters atomically to avoid migration skew */ + rt_sched_lock(&slvl); id = rt_hw_cpu_id(); + thread_tic[para]++; + thread_core_mask[para] |= (1u << id); if (para == id) { thread_inc[para]++; } + rt_sched_unlock(slvl); if (thread_tic[para] == run_num) { - if (para == 0) - uassert_int_equal(thread_inc[para], thread_tic[para]); - else - uassert_int_not_equal(thread_inc[para], thread_tic[para]); - finsh_flag ++; + rt_atomic_add(&finsh_flag, 1); } rt_thread_delay(5); } @@ -104,20 +113,36 @@ static void thread_bind_affinity_tc(void) } } - while (finsh_flag != RT_CPUS_NR); + while (rt_atomic_load(&finsh_flag) != RT_CPUS_NR); - /* Displays the number of times a thread was executed on the relevant core */ + /* Bound thread must always run on core 0 */ + uassert_int_equal(thread_inc[0], thread_tic[0]); + uassert_int_equal(thread_core_mask[0], 1u); + + /* Displays per-thread CPU statistics (unbound threads: observe only) */ for (j = 0; j < RT_CPUS_NR; j++) { rt_spin_lock(&lock); - rt_kprintf("Total runs[%d], Number of times thread[%d] run on [core%d]: [%4d], always run at core%d ? %s \r\n", run_num, j, j, thread_inc[j], j, (thread_inc[j] == run_num) ? "yes" : "no"); + rt_kprintf("Total runs[%d], Number of times thread[%d] run on [core%d]: [%4d], core mask: 0x%x, always run at core%d ? %s \r\n", + run_num, j, j, thread_inc[j], thread_core_mask[j], j, + (thread_inc[j] == run_num) ? "yes" : "no"); rt_spin_unlock(&lock); } } static rt_err_t utest_tc_init(void) { + int i; + rt_spin_lock_init(&lock); + rt_atomic_store(&finsh_flag, 0); + for (i = 0; i < RT_CPUS_NR; i++) + { + threads[i] = RT_NULL; + thread_inc[i] = 0; + thread_tic[i] = 0; + thread_core_mask[i] = 0; + } return RT_EOK; } @@ -125,7 +150,11 @@ static rt_err_t utest_tc_cleanup(void) { for (num = 0; num < RT_CPUS_NR; num++) { - rt_thread_delete(threads[num]); + if (threads[num] != RT_NULL) + { + rt_thread_delete(threads[num]); + threads[num] = RT_NULL; + } } return RT_EOK; } |
