From a5483f0773d5b2a15b5247b4f0fbd72a0c7a8750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Thu, 20 Aug 2026 08:50:15 -0400 Subject: [PATCH] Bounded the wait for the delayed suspension window (#645) threadx_thread_delayed_suspension_test waits for an interrupt to land while thread 2 is part way through suspending, and waits for it with no bound: while(delayed_suspend_set == 0) { tx_thread_wait_abort(&thread_2); tx_thread_relinquish(); } How long that takes depends on the build to a degree that is easy to miss. The loop finishes in between a tenth of a second and three seconds in four of the five ThreadX configurations. In trace_build it took 490 seconds, which was 40 percent of the whole ThreadX suite and more than every other test in that configuration put together. This is the third test in these suites built the same way, after threadx_thread_priority_change and threadx_thread_wait_abort_and_isr_test: spin until an interrupt happens to land in a narrow window, with nothing to stop the spin if it does not. The other two have been given bounds already. Give this one a wall clock budget too, for the same reason as the last: a tick is delivered only when the port's timer thread runs, so the tick clock falls behind real time under load or instrumentation, and instrumentation is exactly what trace_build turns on. The check after the loop needs care that the other two did not. It compares thread_2_counter against thread_2_counter_capture, and the capture is taken inside the interrupt handler at the moment the window is hit. Leaving that check in place after a run that never reached the window would compare a live counter against the zero it was initialised to and report a defect that is not there. So the check is skipped when the window was not reached, and the run says so. Reaching the window still exercises it exactly as before. Verified both ways in trace_build, which is the configuration that was slow: the window is reached in 8 seconds here and the test passes as it always did, and with the budget forced to zero the test reports that the window was not reached and passes without the dependent check firing. Co-authored-by: Claude Opus 5 (1M context) --- .../threadx_thread_delayed_suspension_test.c | 39 +++++++++++++++++-- .../threadx_thread_delayed_suspension_test.c | 39 +++++++++++++++++-- 2 files changed, 72 insertions(+), 6 deletions(-) diff --git a/test/smp/regression/threadx_thread_delayed_suspension_test.c b/test/smp/regression/threadx_thread_delayed_suspension_test.c index fe0a82f8..17a89d03 100644 --- a/test/smp/regression/threadx_thread_delayed_suspension_test.c +++ b/test/smp/regression/threadx_thread_delayed_suspension_test.c @@ -12,6 +12,7 @@ /* This test checks out the delayed suspension clear from tx_thread_resume. */ #include +#include #include "tx_api.h" #include "tx_thread.h" #include "tx_timer.h" @@ -235,6 +236,12 @@ static void thread_0_entry(ULONG thread_input) UINT status; +#ifndef TX_NOT_INTERRUPTABLE +#define DELAYED_SUSPENSION_SECOND_BUDGET ((ULONG) 120) + +time_t start_wall; +#endif + /* Inform user. */ printf("Running Thread Delayed Suspension Clearing Test..................... "); @@ -277,8 +284,23 @@ UINT status; /* Resume the test thread. */ tx_thread_resume(&thread_2); - /* Wait until we see the delayed suspension set flag. */ - while(delayed_suspend_set == 0) + /* Wait until we see the delayed suspension set flag. + + The flag is set by the interrupt above, and only when that interrupt lands + while thread 2 is part way through suspending. That is a narrow window, + and waiting for it without a bound leaves the test no way out when it is + not reached. How narrow it is depends on the build: in the ThreadX suite + the same loop finishes in between a tenth of a second and three seconds in + four configurations, and took 490 seconds in trace_build, where it was the + most expensive thing in the suite by a wide margin. + + The budget is in wall clock seconds rather than ticks. A tick arrives only + when the port's timer thread runs, so the tick clock falls behind real + time under load or instrumentation, which is exactly the configuration + that needs bounding here. */ + start_wall = time(TX_NULL); + while ((delayed_suspend_set == 0) && + (((ULONG) (time(TX_NULL) - start_wall)) <= DELAYED_SUSPENSION_SECOND_BUDGET)) { /* Abort the suspension for thread 2. */ tx_thread_wait_abort(&thread_2); @@ -291,7 +313,18 @@ UINT status; tx_thread_relinquish(); /* At this point, check for an error. */ - if (thread_2_counter != thread_2_counter_capture) + if (delayed_suspend_set == 0) + { + + /* The window was never reached, so thread_2_counter_capture was never + taken and the comparison below would be against a value that means + nothing. Not reaching the window is a gap in what this run covered + rather than a fault in the code under test, so say so and skip the + check that depends on it. */ + printf("(delayed suspension window not reached in %lu seconds) ", + (ULONG) (time(TX_NULL) - start_wall)); + } + else if (thread_2_counter != thread_2_counter_capture) { /* Delayed suspension error... thread kept running! */ diff --git a/test/tx/regression/threadx_thread_delayed_suspension_test.c b/test/tx/regression/threadx_thread_delayed_suspension_test.c index 2bf26421..1841458a 100644 --- a/test/tx/regression/threadx_thread_delayed_suspension_test.c +++ b/test/tx/regression/threadx_thread_delayed_suspension_test.c @@ -12,6 +12,7 @@ /* This test checks out the delayed suspension clear from tx_thread_resume. */ #include +#include #include "tx_api.h" #include "tx_thread.h" #include "tx_timer.h" @@ -235,6 +236,12 @@ static void thread_0_entry(ULONG thread_input) UINT status; +#ifndef TX_NOT_INTERRUPTABLE +#define DELAYED_SUSPENSION_SECOND_BUDGET ((ULONG) 120) + +time_t start_wall; +#endif + /* Inform user. */ printf("Running Thread Delayed Suspension Clearing Test..................... "); @@ -278,8 +285,23 @@ UINT status; /* Resume the test thread. */ tx_thread_resume(&thread_2); - /* Wait until we see the delayed suspension set flag. */ - while(delayed_suspend_set == 0) + /* Wait until we see the delayed suspension set flag. + + The flag is set by the interrupt above, and only when that interrupt lands + while thread 2 is part way through suspending. That is a narrow window, + and waiting for it without a bound leaves the test no way out when it is + not reached. How narrow it is depends on the build: this loop finishes in + between a tenth of a second and three seconds in four of the five ThreadX + configurations, and took 490 seconds in trace_build, where it was the most + expensive thing in the suite by a wide margin. + + The budget is in wall clock seconds rather than ticks. A tick arrives only + when the port's timer thread runs, so the tick clock falls behind real + time under load or instrumentation, which is exactly the configuration + that needs bounding here. */ + start_wall = time(TX_NULL); + while ((delayed_suspend_set == 0) && + (((ULONG) (time(TX_NULL) - start_wall)) <= DELAYED_SUSPENSION_SECOND_BUDGET)) { /* Abort the suspension for thread 2. */ @@ -293,7 +315,18 @@ UINT status; tx_thread_relinquish(); /* At this point, check for an error. */ - if (thread_2_counter != thread_2_counter_capture) + if (delayed_suspend_set == 0) + { + + /* The window was never reached, so thread_2_counter_capture was never + taken and the comparison below would be against a value that means + nothing. Not reaching the window is a gap in what this run covered + rather than a fault in the code under test, so say so and skip the + check that depends on it. */ + printf("(delayed suspension window not reached in %lu seconds) ", + (ULONG) (time(TX_NULL) - start_wall)); + } + else if (thread_2_counter != thread_2_counter_capture) { /* Delayed suspension error... thread kept running! */