summaryrefslogtreecommitdiff
path: root/test/smp/regression
AgeCommit message (Collapse)Author
2026-09-28Made ThreadX object names const-qualifiable behind an option (#761)Frédéric Desbiens
Fixes #61 Object names are exposed as writable pointers throughout the kernel API, which rejects string literals in C++ and lets a caller modify a name an object still holds. Information services return those names through writable double pointers. Create services, control blocks, information services, the module manager and trace registration now preserve const qualification, behind TX_ENABLE_CONST_NAMES. The option defaults to off, so a build that says nothing gets exactly the types it got before. It is opt-in rather than opt-out because it changes the type of a public struct field: application code that copies a name into a writable CHAR * stops compiling, which is a reasonable thing to ask of a minor release and not of a patch one. Issue #780 tracks making it the default in 6.6. Two things the option reaches that its own call sites do not. TX_CHAR_TO_UCHAR_POINTER_CONVERT has exactly two users, both of them reading an object name in _tx_trace_object_register, and every form of that macro but the MISRA one casts the qualifier away without saying so; the conversion is now const in and const out, so nothing launders const to make the build pass. The FreeRTOS adapter holds the name pcTaskGetName retrieves in a TX_NAME_CONST pointer so that it tracks whichever declaration tx_thread_info_get has, and keeps its writable return type through an explicit MISRA C:2012 Rule 11.8 cast, because that signature is part of the FreeRTOS API. Default build: all seven host configurations and all five SMP configurations build with zero warnings and pass -- 113/113 on five host configurations, 100/100 on the two MISRA builds, 118/118 on SMP, 3/3 FreeRTOS. With TX_ENABLE_CONST_NAMES set, the host default and both MISRA configurations, the SMP trace configuration and the FreeRTOS adapter build with zero warnings and pass. Co-authored-by: Tilen Majerle <[email protected]> Assisted-by: Codex (gpt-6-astra) <[email protected]> Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-28Made the SMP pt test's randomised phase set preemption thresholds (#760)Frédéric Desbiens
The randomised phase of threadx_smp_random_resume_suspend_exclusion_pt_test guards its preemption-threshold setup with tx_thread_priority > 40, but the test creates its 1024 source threads with priority = i reset whenever it reaches TX_MAX_PRIORITIES, which is 32 on the Linux port the regression suite runs. The guard is therefore never true. The one thing that distinguishes this test from its non-pt twin in that phase never executes, so the randomised phase exercises no preemption threshold at all and the test duplicates its twin there. The threshold the earlier rebalance section sets is unaffected. Both the guard and the threshold gap are now derived from TX_MAX_PRIORITIES, so the phase sets a threshold whatever the port's priority count is. On a 32-priority port that is priority > 16 with a threshold eight levels better, which keeps the original shape of a threshold set on the worse half of the priority range. Verified under gdb that the block is now reached, at priority 26 with a threshold of 18, and that thread_entry's preemption-threshold invariant is entered for a randomised-phase thread; neither happened before the change. 40/40 passes, 20 runs each in disable_notify_callbacks_build and stack_checking_build. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-28Stopped tx_thread_relinquish discarding the rebalance it requests (#758)Frédéric Desbiens
_tx_thread_relinquish walks the ready list at the relinquishing thread's priority for a thread to hand its core to. When the walk reaches one it cannot place, carrying preemption-threshold or excluded from that thread's mapped core, it sets the rebalance flag and breaks. That exit falls into the block concluding no other thread is ready, which is not what the walk found, and that block sets the finished flag. The rebalance at the end of the function is guarded on that flag, so it never runs. Every rebalance requested from inside the walk is discarded, because that exit always reaches the block first. A ready thread behind the obstacle then never reaches a core while the others hold them and relinquish to each other. The tick keeps arriving, so it is a livelock rather than a deadlock. The early return now applies only when no rebalance was requested, which lets control reach the rebalance path the other three request sites already use. threadx_smp_relinquish_rebalance_test fills the cores with threads of one priority relinquishing in a loop, places a thread excluded from every core behind them and a runnable thread behind that, and fails if the runnable one has not run within 100 ticks. It fails 3 of 3 before this change and passes 5 of 5 after. Instrumenting a passing run of threadx_thread_relinquish_test counted 12 rebalance requests from the walk against 4 performed, all 4 from sites outside it. 109/109 in all eight configurations, twice from clean. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Normalized the AI disclosure comment to one fixed line per file (#740)Frédéric Desbiens
The per-edit disclosure named the product and model, so every agent and every model version appended another line. 74 files carried two to four of them, and the same five products had accumulated 13 spellings -- Copilot against GitHub Copilot, Claude Sonnet 4.6 against claude-sonnet-4.6, four spellings of Codex. Twenty assembly lines carried a doubled comment marker, `; //` or `@ //`. Every file now carries exactly one line, fixed text naming no product: Portions of this file were generated with AI assistance. It is written with the comment character that file already uses, so the `;` and `@` assembly files keep theirs and the doubled markers are gone. Precise attribution stays on the commit, where the Assisted-by trailer is per-change, dated and attached to the diff it describes. A header line cannot hold that record honestly, because the code it names gets rewritten and the line stays. A file-level flag answers whether; the history answers who. Comment-only. 455 files, 455 insertions and 574 deletions: every removed line was a disclosure line, every added line is the fixed text, and no file is left with zero or with more than one. `scripts/check_ports.sh` passes, including the reproducibility check that would catch a ports_arch master and its generated copies drifting apart. Recompiled against dev, every file that builds without a vendor toolchain gives a byte-identical object: 19 of 19 C files under common, 100 of 100 GNU assembly files, and all 16 assemblable files whose comment marker changed. The 10 remaining marker changes are ac5 and IAR sources where `;` already started the comment and only the redundant `//` was removed. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Completed Windows simulator support and regression coverage (#736)Frédéric Desbiens
Completed and stabilized the Win32 and Win64 MSVC simulator ports. - Replaced high-latency host synchronization with bounded scheduler handoffs and critical sections. - Added a high-resolution timer, tick batching, idle fast-forward, shutdown coordination and Win64 extension-pointer support. - Brought the Windows regression tooling and the new thread-transition tests up on CMake, Ninja and the Visual Studio Build Tools. - Extended the SMP teardown diagnostics and corrected 64-bit trace-test handling. This supersedes the historical `win64`, `win32-perf`, `windows-sim-ports` and `windows-sim-ports-completion` branches; no unmerged change from them is missing here. The original Win64 port landed in #529. 1,610 of 1,610 tests pass, across five configurations each: Win32 515, Win64 515, Win64 SMP 580. No external dependency was added, and the existing MSVC warnings in the trace configuration are unchanged. Hardware validation does not apply to host simulator ports. Assisted-by: Codex (gpt-5.6-sol) <[email protected]>
2026-09-15Restored the random stack fill value cleared during thread creation (#732)Frédéric Desbiens
Fixes #723 With `TX_ENABLE_STACK_CHECKING` and `TX_ENABLE_RANDOM_NUMBER_STACK_FILLING` both enabled, `_tx_thread_create` picked a random byte, stored it in `tx_thread_stack_fill_value`, filled the stack with it -- and then cleared the whole control block with `TX_MEMSET`. The stack held the pattern while the control block claimed zero, so `TX_THREAD_STACK_CHECK` and `_tx_thread_stack_analyze` compared against the wrong value for the entire life of the thread. The value is now computed into a local and written back after the clear. The clear stays where it is, because the module manager's error checking walks the created list before the control block may be touched. Fixed in `common/src/tx_thread_create.c`, `common_smp/src/tx_thread_create.c` and `txm_module_manager_thread_create.c`, where it additionally left a user-mode module thread's kernel stack filled with zeros. `threadx_thread_stack_fill_value_test` creates sixteen unstarted threads and checks each control block against the pattern in its stack, tolerating a random zero byte without letting that hide the defect. Added to both suites: `ERROR #3` on `dev` in `stack_checking_rand_fill_build`, green with the fix. Five tx configurations at 104 tests, five SMP at 117, and all three sources clean under `-Wall -Wextra` across every combination of the four stack-filling switches. Assisted-by: Copilot (Opus 5) <[email protected]>
2026-09-15Allowed tx_timer_change to be called from tx_application_define (#730)Frédéric Desbiens
Fixes #224 `_txe_timer_change` returned `TX_CALLER_ERROR` for any call at or above `TX_INITIALIZE_IN_PROGRESS`, making `tx_timer_change` the only timer service that could not be called from `tx_application_define` -- while still being allowed from an ISR. The check has no technical basis: `_tx_timer_change` only writes the expiration fields of a timer that is not on an active list, with interrupts disabled. Removed it from both the `common` and `common_smp` copies of `txe_timer_change.c`, with the now-unused includes and the `TX_CALLER_ERROR` line in the header comment. Relaxing an error check is backward compatible. `testcontrol.c` in both suites now calls `tx_timer_change` at initialization, so the timer simple test covers it: `ERROR #30` on `dev`, green with the fix. 116/116 SMP, 103/103 non-SMP. Assisted-by: Copilot (Opus 5) <[email protected]>
2026-09-15Guarded _tx_thread_stack_analyze against inverted stack pointers (#727)Frédéric Desbiens
Fixes #460 `TX_ULONG_POINTER_DIF` casts the pointer difference to `ULONG`, so when `tx_thread_stack_highest_ptr` sits below `tx_thread_stack_start` the midpoint wraps to a huge value, the probe lands outside the stack, and the search never converges: the caller hangs or faults. `_tx_thread_stack_analyze` now requires the highest pointer to be strictly above the start of the stack, and bounds the final scan by it. #464 covered the `TX_THREAD_STACK_CHECK` path; this covers direct callers too, as @billlamiework suggested on the issue. Inconsistent pointers still mean a real overflow or a corrupted control block, which remains the application's problem. What changes is that ThreadX reports it through the stack error handler instead of hanging. New cases for an inverted and an equal pointer pair crash the suite without the fix and pass with it. Assisted-by: Copilot (Opus 5) <[email protected]>
2026-09-10Refused thread delete and reset while an exit transition is in progress (#724)Frédéric Desbiens
_tx_thread_shell_entry and _tx_thread_terminate both publish a thread's terminal state -- TX_COMPLETED or TX_TERMINATED -- and then call that thread's exit notification callback, before the thread has been detached from the ready list and before either service has finished with the pointer it holds to the control block. That terminal state is exactly the state _tx_thread_delete and _tx_thread_reset accept as authorization to invalidate or rebuild the control block, and neither service tested whether the transition producing it had finished. A callback could therefore delete the thread it was called for -- and then lawfully recreate it over the same memory, since delete exists to permit that -- while the scheduler was still linked to the old incarnation. tx_thread_create zeroes the whole control block and can auto-start the new one, so the old priority list is left heading at a block whose own priority field names a different list, with the old priority's map bit set behind nothing. Alternatively a callback could reset a terminated thread, which moves it out of the terminal state, and then resume it: the interrupted- suspension logic in _tx_thread_system_resume refuses to void a suspension only while the state is still terminal, so with the reset allowed first the resume clears the suspending flag and restores TX_READY, the outer service then finds the flag clear and skips the removal, and tx_thread_terminate returns TX_SUCCESS for a thread that is runnable again. Interrupt masking does not close the window, because the kernel restores the prior posture before invoking the callback deliberately. On SMP TX_RESTORE also releases the global protection, so the target can be executing on another core while its callback runs -- and a reset there memsets the stack a live core is running on. On the Linux, Win32 and Win64 host simulation ports the consequence is more immediate than corruption: TX_THREAD_DELETE_PORT_COMPLETION cancels and joins the host thread backing the deleted thread, so a callback-side delete of the completing thread destroys the host thread the callback is running on. The fix marks the transition and has the two services refuse a marked target, returning the errors they already document, TX_DELETE_ERROR and TX_NOT_DONE. The refusal is transient and the same call succeeds once the transition has completed, so no documented lifecycle is lost; and it is in the core services rather than the _txe_ wrappers, so disabling error checking cannot disable it. Refusing the reset is also what closes the resume path, without touching _tx_thread_system_resume: its existing terminal-state test is sufficient once nothing can turn the terminal state into TX_SUSPENDED from inside the window. tx_thread_suspending is the marker, rather than a new control-block field. It already means "a suspension is in progress" and is already true across the callback in the two interruptable paths, so no field is added, the public structure is unchanged, and sizeof(TX_THREAD) is unchanged -- which matters, because the Module Manager's object handling depends on the sizes of the control blocks. Widening its lifetime was checked against every reader rather than assumed. There are four: two in _tx_thread_system_suspend and two in _tx_thread_system_resume. In every window this change widens, the state is TX_COMPLETED or TX_TERMINATED, and both resume readers already refuse to void a suspension for exactly those two states, so their behaviour is unchanged; and no suspension routine is called on the target in those windows, so the suspend readers never see them. No suspension-initiating service can set the marker again inside a window either: every one of them acts on a thread that is ready or suspended. Three sites needed changing beyond the two refusals, and the shape of each was decided by where the marker can safely be cleared: - The non-ready branch of _tx_thread_terminate cleared the marker before the terminated extension and the callback, which is what left them free to act on a control block the service still had mutex-release processing to do against. The clear moves to the common tail, after the last dereference of the target, and becomes the single clear site for the whole service. In the interruptable ready branch the flag is already false there, because _tx_thread_system_suspend cleared it when it detached the thread, so the tail store is a second store of a value the flag already holds -- cheaper than testing for it, and it keeps one clear site. - Under TX_NOT_INTERRUPTABLE neither path set the marker at all, because that configuration does not use the interruptable suspension path that sets it. Both now set it before the callback. Interrupts being disabled there does not help: the callback is reached by a direct call. - In the TX_NOT_INTERRUPTABLE completion path the marker is cleared before _tx_thread_system_ni_suspend rather than after it. That call returns to the scheduler for a thread that is the current thread, which a completing thread is, and does not come back; clearing afterwards would leave a normally completed thread marked for ever and therefore permanently undeletable. Nothing is lost by clearing early there, because everything from that point to the detachment runs with interrupts disabled and calls no application code. The change is the same change twice. All four files are byte-for-byte identical between common and common_smp at this commit and stay so after it, so common_smp was written by copying rather than by repeating the edits. tx_thread_system_suspend.c and tx_thread_system_resume.c, which do differ between the kernels, are deliberately untouched. Tests. The in-tree regression test goes to both trees and is byte-for-byte identical between them. It drives seven scenarios: terminating a ready non-current target with two peers ready at the same priority, with the callback attempting the delete and recreating the block if it succeeded; the same with the callback attempting the reset and then the resume; terminating a target suspended on a semaphore while owning a mutex, which is the non-ready branch; natural completion alone at its priority, including the safe post-completion reset, terminate, delete and recreate at another priority; self termination; a benign callback, whose notification count and ordering are unchanged; and the state and boundary cases, where the new refusal must not fire. It measures rather than describes. The callback records the published state, the marker, and the status of every lifecycle service it can reach, calling the core service as well as the wrapper wherever a refusal is expected. A snapshot taken under interrupt lockout -- which is the global SMP protection on an SMP port -- checks that every ready list agrees with the control blocks it heads, that the priority map agrees with the lists, and that each execute pointer is a member of the list its own priority field names. Every walk is bounded, so a corrupted ring costs an assertion and not a hang, and no test in the suite can hang. Expectations are counted inside a scenario and gated between scenarios, so a failing kernel reports how much it failed by without being driven further into its own corruption. The consequences are demonstrated from the terminator's context rather than the completing thread's, which is what makes the pre-fix behaviour an assertion instead of a wedged simulator. Compiled against the unfixed sources the test fails 9 of the 24 expectations it reaches in the uniprocessor tree and 10 of 24 in the SMP tree, and the failures are the finding: the callback-side delete succeeds, the recreate succeeds, the consistency snapshot disagrees, the target is neither terminal nor detached when the service returns, and a peer has left the ready ring the recreated block hijacked. The SMP tree gets a second test for the case that needs concurrency. The victim is excluded to core 1 and spins there without relinquishing while the controller, excluded to core 0, terminates it, so the callback runs on one core while the target executes on another. The callback-side reset and delete must both be refused, and a sentinel written into the unused low end of the victim's stack must survive -- a reset would have memset the whole stack before rebuilding the frame. Every wait is bounded, and if the remote precondition cannot be established the test says so and drops only the assertions that depend on it rather than reporting a pass it did not earn; measured over twenty consecutive runs it established the precondition every time. TX_NOT_INTERRUPTABLE and TX_DISABLE_ERROR_CHECKING are not among the five build configurations either tree compiles, and each tree builds the whole library once per configuration, so neither can be reached from inside the suites. The lines this change adds under TX_NOT_INTERRUPTABLE are therefore in no configuration the trees build, and they are where the permanent- undeletability failure mode lives, so they get their own harness rather than a compile check: the four sources plus the two error wrappers are compiled directly into a test executable, once per combination, with recorders standing behind the scheduler services they call. That is what makes the marker's value at the moment of detachment directly observable. It runs 66 expectations under TX_NOT_INTERRUPTABLE, 66 under that with error checking disabled, 45 under that with notification disabled, and 63 under error checking disabled alone; against the unfixed sources those fail 25, 22, 6 and 20 respectively. The harness lives in the uniprocessor tree only, because the four sources are identical between the kernels and the shim replaces the very primitive the SMP port differs in, so a second copy would compile the same text under the same macros. It is deliberately left out of the coverage instrumentation, since the same source under different feature macros has a different line set and merging those would confuse the union rather than add to it. Results. Both suites pass in all five configurations with GCC 14: 103 of 103 in the uniprocessor tree, up from 98, and 116 of 116 in the SMP tree, up from 114. Merged line coverage is 100% in the uniprocessor tree and 5172 of 5183 in the SMP tree, whose eleven uncovered lines are the same eleven that were uncovered before this change and are in tx_byte_pool_search and tx_thread_smp_utilities; all four changed files are at 100% line coverage in both trees, and SMP branch coverage rises from 2819 of 3548 to 2831 of 3556. Cross-compiled with arm-none-eabi-gcc at -Wall -Wextra for Cortex-M4 against common and for Cortex-A7 SMP against common_smp, all four files produce no diagnostics at all and an identical warning set to before the change, under -std=gnu99 and -std=c99 alike -- unlike the module ports, -std=c99 does not fail on these base ports, and even -Wconversion is clean. No MISRA deviation is required: explicit comparisons to TX_TRUE, existing ThreadX types, single-entry and single-exit control flow, no goto, and two added constant-time tests that change no real-time complexity. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-08-28Covered the SMP suspension teardown and the long byte pool search (#677)Frédéric Desbiens
Against the merged SMP coverage report -- every build configuration instrumented and unioned -- sixty-four lines of common_smp/src were uncovered, 5114 of 5178. Fifty-three of them are closed here and the report reads 5167 of 5178. The SMP coverage floor goes from 98 to 99 with it. Thirty-six of the sixty-four were one loop repeated four times: the walk in tx_block_pool_delete, tx_byte_pool_delete, tx_event_flags_delete and tx_queue_delete that releases every thread suspended on the object with TX_DELETED. The suite deletes all four object types after every single test, and that is exactly why the loop never ran. test_control_cleanup in the ThreadX suite deletes the application's objects first and its threads last, so a test that ends with a thread parked on a queue has that thread walked out of it by tx_queue_delete. The SMP suite's cleanup deletes the threads first, deliberately -- it was changed so that no application-owned object is still referenced when the object loops run, which is what stopped a class of teardown hang. The side effect is that all four deletes now run against an empty suspension list. tx_semaphore_delete is the one member of the family that was already covered, because threadx_semaphore_delete_test deletes a busy semaphore on purpose. threadx_object_delete_suspension_test is the same idea for the other four. Two threads suspend on each of a block pool, a byte pool, an event flags group and a queue; the control thread waits on the object's own suspended count through tx_*_info_get rather than on an ordering it cannot guarantee across four cores, deletes the object, and checks both waiters came out with TX_DELETED. Two waiters rather than one so the loop takes its back edge as well as its body, and every wait is bounded in ticks so a suspension that never arrives fails the test instead of hanging it. threadx_trace_entry_update_test and threadx_thread_misaligned_stack_test are ports of the two tests that closed the equivalent gaps in common/src, and close fourteen more lines here: tx_block_allocate 123, 175, 182, 319 and 326, tx_byte_allocate 130, 210, 217, 359 and 366, tx_thread_system_suspend 504 and 560, tx_trace_object_register 221, and tx_thread_create 133. The one substantive change is core confinement. The trace test needs thread 0 to suspend and thread 1 to then release what it waits for; on four cores thread 1 gives the block back before thread 0 has suspended and the update block behind the suspension is never reached, so both threads are excluded from cores 1 to 3. The misaligned stack test needed no such change. threadx_byte_memory_long_search_test closes three of the eleven in tx_byte_pool_search. Lines 264, 267 and 270 are the TX_BYTE_POOL_MULTIPLE_BLOCK_SEARCH limit -- twenty on this port -- where a long search drops and retakes protection so that it cannot lock the other cores out for the whole walk. No byte pool in the suite ever had twenty fragments. This one is filled with small chunks until it refuses another and then has every second chunk released, so the free fragments are never adjacent and cannot be merged, and the request is larger than any of them but smaller than the pool's theoretical total, which is what makes _tx_byte_pool_search walk rather than refuse at the door. The layout is asserted rather than assumed: the test checks the fragment count and checks the probe request really does fail before the workers start, because either would otherwise turn it into a silent no-op. Eleven lines remain and they are not a to-do list. Eight are the delay loop in tx_byte_pool_search that fires when another thread claims the pool inside the window the search opens. The Linux SMP port serialises all four cores on one pthread mutex, so that window is an unlock immediately followed by a lock on that mutex, and glibc hands an uncontended mutex straight back to the thread that just released it: measured over 180,003 windows across three cores, with zero handovers. The shipped test therefore does 250 searches per worker rather than the sixty thousand that probe used, because the twenty-block threshold is crossed by the first search. The other three are in tx_thread_smp_utilities. Line 149 is a range guard placed after the shift it is meant to guard, so reaching it needs a shift by the width of the type; the fix is to move the check above the shift, matching the TX_MAX_PRIORITIES > 32 variant of the same function, and that belongs in its own change. Lines 1073 and 1074 need a mutex owner that is genuinely executing on another core when a waiter suspends, and three shapes were tried without producing one on this port. Measured twice before and twice after, every gcda deleted between runs and 570 of 570 tests passing each time: 5114 of 5178 both times before, 5167 of 5178 both times after. Branch coverage goes from 2768 to 2821 and 2823 of 3548. A floor of 99 needs 5127, so the ratchet lands with forty lines of headroom against a numerator that has been seen moving by two between runs. Assisted-by: Claude Opus 5 <[email protected]>
2026-08-20Asked the wait abort test for three windows, and failed a run that reached ↵Frédéric Desbiens
none (#649) Four CI runs of the same tree, twenty configuration-runs in total, show this test's budget being reached far more often than the first green run suggested, and a pass being reported every time it was: trace_build 3 of 10 windows in 121 seconds disable_notify 3 of 10 windows in 121 seconds default_coverage 4 of 10 windows in 121 seconds stack_checking 7 of 10 windows in 121 seconds trace_build 0 of 10 windows in 121 seconds disable_notify 7 of 10 windows in 121 seconds stack_checking 3 of 10 windows in 121 seconds Seven of twenty, and the shortfall message only ever reaches an artifact: ctest is run with --output-on-failure, so a passing test's output is not in the job log at all. The suite has been quietly losing most of this test's coverage in whole configurations and reporting green. The loop runs in two modes, not one. A window arrives in milliseconds in the fast mode, and costs between 17 and 40 seconds in the slow one, with nothing in between across those twenty runs. Ten windows are therefore unreachable inside any budget worth having: at 40 seconds each that is 400 seconds, and the unbounded runs measured before any of this took up to 726. Raising the budget to cover the slow mode would trade a quiet loss of coverage for five configurations approaching the sixty minute step timeout. So ask for what a run can reach. Three windows cost 51 to 120 seconds in the slow mode and under a second in the fast one, and the later hits repeat what the first ones establish, so what is given up is small. The budget goes to 180 seconds because three windows at the worst rate measured is exactly the 120 it was, which would have truncated at two. The count is printed on every run rather than only on a short one. A number that appears only on shortfall cannot be told apart from a number nobody recorded. Reaching the window no times at all is a different matter, and was the worst of the seven. The check after the loop compares semaphore bookkeeping that a window has to have touched to mean anything, so a run that reached none of them compares a counter against the value it was initialised to and reports a pass having verified nothing. That run now keeps trying to a 300 second ceiling, and fails if it still has not reached the window. A genuine resonance that holds for five minutes is worth a failure; the old behaviour was worth nothing. The SMP copy keeps its count of twenty. It reaches them in under half a second in all five of its configurations, in all four runs, so the slow mode has never been observed there and the coverage is free. Both copies get the ceiling and the unconditional report, so the logic stays identical between them. Verified locally on all five configurations: the test reaches 3 of 3 in 5 to 14 seconds, and the full suites pass 96 of 96 and 110 of 110 run one test at a time. With the handler's window made unreachable and the ceiling lowered to 5 seconds, the test stops after 6 seconds, prints the count it reached, and reports ERROR #8 with the harness recording a failure rather than a pass. With the count raised past what the budget allows, a run that reaches two windows still passes, so falling short and reaching nothing stay distinct. The TX_NOT_INTERRUPTABLE branch, which no configuration in either suite builds, was compile-checked in both copies with the configurations' own compile commands. Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
2026-08-20Stopped the thread priority change test leaving core 0 to a finished thread ↵Frédéric Desbiens
(#647) The SMP suite has been failing on threadx_thread_priority_change since 30 June. The test reports SUCCESS and the process then never exits, so ctest kills it at the thousand second timeout, twice, and the log carries nothing past the result line. Instrumenting the harness teardown produced the state at the hang: last stage reached: test_control_return: control thread resume returned _tx_thread_preempt_disable: 0 core 0: current=thread 0 execute=thread 0 thread test control thread state=0 priority=0 threshold=0 core_control=1 thread thread 0 state=1 priority=0 threshold=0 inherit=0 Core 0 is held by a thread in state 1, TX_COMPLETED, while the control thread sits in state 0, TX_READY, at priority 0. The Linux SMP scheduler fills a core only when _tx_thread_current_ptr for it is null, and clears that pointer only for a thread carrying a deferred preemption, which thread 0 is not. So core 0 can never be handed on, and with TX_THREAD_SMP_ONLY_CORE_0_DEFAULT and TX_SMP_NOT_POSSIBLE the control thread has nowhere else to go. The scheduler re-reads the same state every two milliseconds for as long as it is allowed to. What put thread 0 at priority 0 is the last thing this test does: thread_0.tx_thread_inherit_priority = 0; _tx_thread_smp_simple_priority_change(&thread_0, 16); with the stated intent of reaching the branch where the new priority is below the inheritance priority. Zero cannot reach that branch, because 16 is not less than 0. The other branch runs instead, and that branch assigns the inheritance priority as the thread's priority while the code after it links the thread into the list for the new priority regardless. Thread 0 therefore came away claiming priority 0 while living in the priority 16 list. Both halves of that hurt. Priority 0 ties with the control thread, so resuming the control thread raised no preemption and left the execute pointer alone. The mismatch between the recorded priority and the list the thread is linked into then means that completing thread 0 removes it from a list it was never in, leaving core 0 pointing at it for good. Give the inheritance priority a value above the new one, which is what the branch the comment names actually requires, and put it back to TX_MAX_PRIORITIES afterwards so nothing downstream reasons about an inheritance that is not there. Hold protection across the call as well: this is an internal routine that expects it, and it was being called in the open. Measured before and after by printing the thread's state at the point the test reports success. With the inheritance priority at 0 it is priority 0 threshold 0, matching the hang above, on every run. With it above the new priority it is priority 16 threshold 16, which agrees with the list the thread is linked into, and resuming the control thread preempts core 0 the ordinary way. All five SMP configurations pass 110 of 110 at the parallelism CI uses. The comparison in _tx_thread_smp_simple_priority_change deserves a second look on its own account. When its else branch runs, the thread's recorded priority and the list it is linked into disagree by construction. Only this test is known to reach that branch, by supplying an inheritance priority that cannot arise in ordinary operation, so nothing here claims a defect in shipped paths. Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
2026-08-20Made a teardown hang in the SMP suite say where it stopped (#646)Frédéric Desbiens
The SMP regression suite times out in CI on threadx_thread_priority_change and the log carries nothing that says why. The reason the log is empty is mechanical: test_control_return() opens with fflush(stdout), and that is the last flush before either the test finishes or it wedges. Everything printed after it sits in stdout's block buffer, and ctest discards that buffer when it kills the process at the timeout. So the log ends at the test's own result line no matter where the process actually stopped. That is enough to place the hang, if not to explain it. The failing runs print "SUCCESS!" and then stop, which means every check in the test body ran and passed, and the wedge is somewhere between that flush and exit(). The run of 30 June shows the same signature before any of this test's waits were bounded, so the hang is not the unbounded wait removed earlier, and the message added then for an exhausted cap never appears. Retrying tells us nothing new either: the suite spends 1000 seconds per attempt, twice, to reproduce the same silent timeout. Record how far teardown gets, and bound it. A stage variable is updated at each step from test_control_return() through test_control_cleanup() to exit(), and a watchdog thread armed on entry to test_control_return() reports the last stage reached, the per-core scheduler state, and every thread on the created list, then exits 99. The watchdog covers teardown and not the test body, because the test body has no bounded runtime to hold it to. Several tests here wait on a probabilistic interrupt window: threadx_thread_wait_abort_and_isr_test has been measured between 0.34 and 439 seconds while passing. Teardown is a fixed amount of work that takes milliseconds, so a bound on it cannot turn a slow pass into a failure. The default is 60 seconds, which also means a wedged run now reports in one minute rather than burning the 2000 seconds two 1000-second attempts cost today. The report is written with write() rather than printf() because a wedged thread may be holding the stdio lock, and a watchdog that blocked on that lock would reproduce the silent timeout it exists to replace. For the same reason it reads the ThreadX globals directly and takes no kernel lock; the values may be torn, which is acceptable for a post-mortem and cannot deadlock. One walk in test_control_cleanup() is bounded as well. The loop that steps past the timer thread and the control thread has no terminating condition of its own and spins for good if _tx_thread_created_count and the created list ever disagree, which is one of the shapes the timeout could be taking. It now reports and stops instead. Off by default in the sense that matters: stderr stays empty and stdout keeps its buffering, so output is unchanged on a passing run. TX_TEST_TEARDOWN_TIMEOUT overrides the bound in seconds and zero disables the watchdog; TX_TEST_TEARDOWN_TRACE echoes each stage as it is reached and line-buffers stdout so the surrounding output survives a kill too. Verified against an injected hang at the point the failing runs stop: the watchdog fires, names the stage, and exits 99. The dump is already informative, showing thread 0 left at priority 0 with threshold 0 and inherit 0, the same priority as the control thread, with core 0's execute pointer still on it. All five SMP configurations pass 110 of 110 at the parallelism CI uses, in both quiet and trace modes, and the suite runtime is unchanged. Only the SMP harness is instrumented. The non-SMP suite has not shown this hang. Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
2026-08-20Bounded the wait for the delayed suspension window (#645)Frédéric Desbiens
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) <[email protected]>
2026-08-20Gave the wait abort ISR test a budget instead of an open-ended wait (#644)Frédéric Desbiens
threadx_thread_wait_abort_and_isr_test waits for an interrupt to land while the preempt disable flag is set, and waits for it ten times, twenty in the SMP copy, with no bound on how long that takes. The handler in the same file already says what can go wrong: It is possible for this test to get into a resonance condition in which the ISR never occurs while preemption is disabled and perturbs its own duration to break out of it. That helps but guarantees nothing, and if the resonance holds, the loop does not end. It is also, by a wide margin, the most expensive thing in either suite. Run one test at a time in CI it took between 148 and 726 seconds per configuration: 1936 seconds of the ThreadX suite's 2209, against 273 seconds for the other ninety five tests together. Nothing else in the suite is within two orders of magnitude of it. The budget is in wall clock seconds, not ticks. That distinction turned out to matter. A tick is delivered only when the port's timer thread gets to run, so the simulated clock falls behind real time under load or under coverage instrumentation, and never makes the loss up. A first attempt bounded the wait at 20000 ticks, nominally 200 seconds, and failed to stop a run that took 726 seconds, because fewer than 20000 ticks had gone by. tx_time_get() cannot bound elapsed time here; time() can. A run that falls short says how many windows it reached rather than going quiet, and the check after the loop is untouched. That check compares semaphore bookkeeping which holds whatever number of windows were hit, so it still means exactly what it did before. Hitting the race a few times rather than ten is a smaller loss than it looks: the value is in reaching the window at all, and the later hits repeat what the first ones established. Verified by forcing the budget to 3 seconds, where the test stops after 3.14 seconds of wall clock and reports reaching 0 of 10 windows, with the following check intact. At 120 seconds both suites pass every configuration run serially. Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
2026-08-18Bounded the wait in the thread priority change regression tests (#640)Frédéric Desbiens
Both copies of this test, the SMP one and the non-SMP one, install an interrupt handler and then spin until it clears a flag: test_isr_dispatch = test_isr; do { ... } while (test_isr_dispatch); The handler clears that flag only on a narrow window: thread 3 at priority 6, ready, and not yet at the head of its priority list, which exists only part way through a priority change. If an interrupt never lands inside that window, the loop never ends. That is what has been failing in CI. The SMP suite has been red since 30 June, and this test times out in three of the last four failing runs, always in a stack-checking configuration. The evidence that it is a hang rather than slow work: the test carries no per-test timeout property, so ctest's --timeout 1000 applies, and locally the test finishes in 0.12 seconds with a worst case of 0.29 over thirty runs. Nothing turns that into more than a thousand seconds. It also survived --repeat until-pass:2, so it hung twice in succession. The TX_NOT_INTERRUPTABLE path in the same handler already stops after a fixed amount of work. Only the interruptable path, which is the one the failing configuration uses, had no protection. Cap the loop and clear the handler on the way out. When the window is not reached the test says so and still passes: not reaching it is a gap in what this run covered, not a fault in the code under test, and failing would report a defect that does not exist. The counters are left alone so the checks that follow keep their previous meaning. The cap is 100000 attempts. An exhausted cap takes about 60 seconds, measured, and a successful run takes 0.12 seconds, which puts the usual cost around two hundred attempts and leaves the cap roughly two orders of magnitude clear of it. That is wide enough not to lose coverage on a slower machine, while replacing a timeout that says nothing with a message that says what happened. Not reproducible here, which fits the diagnosis rather than contradicting it: on sixteen cores the window is hit almost at once. Thirty sequential runs, two hundred at parallelism thirty-two, sixty pinned to two CPUs, forty pinned to one, and three full-suite passes at the parallelism CI uses all came back clean. The defect is the reliance on the window, not any particular machine. Verified with the window deliberately made unreachable: before this change the test runs until it is killed, and after it exits in about a minute reporting that the window was not reached. The full SMP suite passes 110 of 110 at CI's parallelism.
2026-06-22Fixed SMP execution profile total getters (#553)Frédéric Desbiens
Updated the SMP execution profile aggregate getters to copy each core's total into the matching output array element. Added a focused regression test for thread, ISR, and idle total getters. Co-authored-by: Codex <[email protected]>
2026-06-06Added copyright headers to files missing themFrédéric Desbiens
Applied the standard MIT license header to all project-owned C, header, assembly, shell, and Python files that were missing a copyright notice. Third-party, toolchain startup, and auto-generated files were excluded. Co-authored-by: Copilot <[email protected]>
2026-05-26Added win64 ports of ThreadX and ThreadX SMP (#529)Frédéric Desbiens
Windows x64 port and regression suite This PR adds the Windows x64 (Win64) simulation port for both the standalone and SMP variants of ThreadX, along with the full CMake build and test infrastructure needed to run the regression suite on Windows. New ports Win64 standalone (ports/win64/vs_2022): self-contained Windows simulation port using Win32 threading primitives as virtual cores. Includes CMake integration, build/test scripts, and MSVC project files. Win64 SMP (ports/win64_smp/vs_2022): multi-core Windows simulation port. Supports up to 4 virtual cores backed by Windows host threads. Scheduler and timer improvements The initial port used coarse polling and synchronous SuspendThread/ResumeThread pairs throughout the scheduler hot path. Several rounds of optimization reduced the SMP regression suite runtime from ~150 s to ~78 s (-48%), with no regressions: - Replaced scheduler polling with an event-driven wake path; switched the simulated timer to one-shot rearming to eliminate catch-up ticks. - Skip SuspendThread when _tx_thread_preempt_disable != 0 (new suspension type 3) -- the primary optimization, yielding up to 7.9x speedup on preemption-heavy tests. - Skip SuspendThread when a thread is spinning on the Win32 critical section (suspension type 4), and fix a stale-TLS bug in _tx_win32_critical_section_obtain that could stamp mutex_access on the wrong virtual core. - Added a 2 ms scheduler event timeout (matching the Linux SMP port) to prevent stalls on any missed SetEvent. - Enabled high-resolution waitable timers (SetWaitableTimerEx) for accurate 100 Hz tick cadence. - Increased TX_WIN32_CONTENTION_PAUSE_COUNT from 64 to 256 to reduce SwitchToThread overhead under heavy CS contention. Build and test infrastructure - Hardened the Windows build wrapper (scripts/build_tx.ps1): invoke Ninja directly for Ninja build trees, fix timeout detection, add a default build timeout, and limit fallback replay to real timeout cases. - Added -Clean support to Windows test scripts to remove stale CTest state before each run. - Skip Visual Studio DevShell re-entry when the active MSVC environment already matches the requested architecture. - Fixed scripts/build_tx.sh (Linux) regression source generation: replaced brittle exact-string insertion with line-based matching so the interrupt dispatcher hook is inserted reliably for both simulator ports. Test suite updates - Introduced test/tx/regression/threadx_test_port.h with portable macros (TX_TEST_POINTER_WORD, TX_TEST_STORE_POINTER) for storing pointers in test arrays on 64-bit targets where ULONG remains 32-bit. - Adjusted pool-capacity and pointer-storage patterns in regression tests to use ALIGN_TYPE-sized slots, making the suite correct on 64-bit hosts. - Restored stricter event flag, sleep, and timer expectations now that port-level fixes make prior Windows accommodations unnecessary. - Tightened SMP watchdog and clean-build timeout defaults. Version metadata Updated Win32, Win64, and Win64 SMP port version strings to 6.5.1.202602. Co-authored-by: Copilot <[email protected]> Co-authored-by: Codex (gpt 5.5) <[email protected]>
2026-04-29Corrected typos in two test filenames (#527)Frédéric Desbiens
2026-04-15Merge branch 'dev' into typoFrédéric Desbiens
2026-03-05Updated copyright headers and version number constants (#509)Frédéric Desbiens
* Updated version number constants * Removed revision history from all files * Added Eclipse ThreadX contributors' copyright header
2025-09-28Added missing QUEUE_MESSAGE_MAX_SIZE test for SMP.Frédéric Desbiens
2025-04-02Make queue max message size configurableHaithem Rahmani
Summary ------- This commit fixes the issue #424 Details -------- - Add a the configuration option TX_QUEUE_MESSAGE_MAX_SIZE in the tx_api.h with default value set to TX_ULONG_16 to keep backword compatibility. - Update the txe_queue_create() to check on TX_QUEUE_MESSAGE_MAX_SIZE rather than TX_ULONG_16 as max message size. - Add a new unitary test to cover the new change.
2024-02-24fix the typosYang Hau
2023-05-12Add random number stack filling option. (#257)Xiuwen Cai
Co-authored-by: TiejunZhou <[email protected]>
2023-04-04Release ThreadX regression systemTiejun Zhou