| Age | Commit message (Collapse) | Author |
|
_tx_trace_user_event_insert tests the trace buffer pointer and returns
TX_NOT_DONE if it is null; the insert sits in the else, and the macro reads the
same variable and tests it again a few instructions later, inside one
protection with nothing between. The second test's false arm had no route on one
core and was carried as unreachable.
It is an ordinary state of the running kernel on SMP, and no fault has to be
invented for it. _tx_trace_disable clears that pointer and takes no lock of any
kind, so a thread disabling tracing on one core while another is inside the
insert clears it under that core. A new test drives 300,000 inserts against a
second thread on a core of its own writing the same store the disable service
writes. No kernel symbol is replaced and both services run their own code from
end to end; the test asserts that the two threads held different cores and that
the writer's stores reached the service, and says in the file that the arm
itself is evidenced by the coverage report because nothing outside the macro can
observe it.
The pass count is sized from measurement on the slower of two machines. The arm
is taken 49 to 132 times per run on this workstation over three runs and 521 to
1298 times per run on a CI runner over seven.
136/136 in each of eight configurations, twice from clean, 3588/3612 branch
outcomes on the union basis against 3587/3612 before. Fifty standalone repeats
in each of the eight, no failures.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
TX_LOOP_FOREVER appears twice in common_smp/src. Under TX_MISRA_ENABLE both
expand to a call on _tx_misra_always_true rather than to a literal, so each
carries an exit arm the kernel never takes. The timer thread's loop was already
covered by replacing that shim; the per-core state initialisation loop in
_tx_thread_smp_current_state_set was not, because the kernel reaches it only
from the three initialisation paths that run before the scheduler starts a
thread.
The existing test drives it directly instead, with the shim already armed and
the timer thread already ended, so nothing else in the run can reach the shim
while it is refusing. The service is handed back the state its highest core
already holds, so the one assignment a shortened loop performs writes the value
that was there; the test asserts afterwards that every core's state is the one
it saw before the call, and that the shim refused exactly twice. No second
symbol is replaced and no test is added to the list of those that replace one.
2/2 at tx_thread_smp_current_state_set.c:97, seven executions of the loop and
one exit, measured with the coverage isolated to this test. 135/135 in each of
eight configurations and 3587/3612 branch outcomes on the union basis. Fifty
standalone repeats in each of the eight, no failures.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
The byte allocate records a trace event on the way in, saves the entry's time
stamp, and comes back to write the address it returned into that same entry --
but only if the time stamp says nothing overwrote the entry meanwhile. Nothing
in the kernel writes a trace event inside the window, so the other arm had no
agent and no test.
The Linux SMP port's TX_BYTE_ALLOCATE_EXTENSION gains a second clause, guarded
by its own flag and calling a routine the harness declares weakly, in the shape
TX_BYTE_RELEASE_EXTENSION already uses. The hook runs between the restore and
the disable that bracket the pool search, which on SMP is a real release of the
protection rather than an interrupt gap. A new test filters the byte pool event
class so the allocate's own insert writes nothing and leaves the buffer parked
on the saved entry, then lets the hook write a user event onto it; neither lever
reaches the arm without the other. No kernel symbol is replaced and the allocate
runs its own code end to end. The macro expands inside tx_byte_allocate.c, so
the new clause's if is certified-source branch: the union branch denominator
moves from 3610 to 3612 and both new outcomes are covered.
The arm closes in trace_build alone. Under TX_MISRA_ENABLE the port's trace time
source returns a constant, so the comparison reads 0 == 0 whatever the hook
writes, and the test asserts the consequence only where the time source is real.
135/135 in each of eight configurations, 3586/3612 branch outcomes on the union
basis against 3583/3610 before. Fifty standalone repeats in each of the eight,
no failures.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
The two inserts the trace enable writes read an event class the same call sets
to all ones a few hundred lines above them, so from inside the enabling thread
neither can find it disabled. A buffer-full notification reaches the second one,
because it is called from inside the first; the first has no lever of that kind
and sat at 10 of 12.
SMP has one the monoprocessor does not, and it is an arrangement rather than a
race because the enable blocks. tx_trace_event_filter is an unprotected
read-modify-write of the enable bits, while the enable takes the SMP protection
after setting them and long before either insert, so a thread already holding
the protection on another core parks the enable there for as long as it likes. A
new test takes the protection from a core of its own, waits for the bits, clears
every class and releases; the enable runs on and both inserts find the class
gone. It asserts the consequence rather than the timing - neither insert wrote
an entry, so the buffer is where the enable left it.
A first version that waited for the window instead of holding it open was
measured and discarded: it failed 11 runs of 400, and widening the window
eightfold only took that to 3 of 100. tx_trace_enable.c:391 goes to 11 of 12 and
:392 stays at 12. 133/133 in each of the eight configurations, and 50 standalone
repeats of the new test green in every one of them.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
The trace object registry carried one uncovered line and three uncovered branch
outcomes. The name copy stops at the string's terminator, so it never left the
loop by its own count and never met a caller passing no name at all. The search
that reuses an entry prefers a later free one unless the earlier one has never
held a valid object, and nothing the kernel registers produces an entry that is
both in use and of no valid type.
A new test registers an object whose name is longer than a registry entry keeps
room for, one with no name, and one of the type the registry treats as not
valid. It then fills the registry, frees the not-valid entry last so the search
starts there, registers again, and asserts the new object landed in the entry
the search kept rather than in the later free one. The registry is rearranged
under one SMP protection, because every service that creates an object writes
it.
tx_trace_object_register.c goes to 66/66 lines and 42/42 branch outcomes on the
union of the eight configurations, from 65/66 and 39/42. 133/133 in each of the
eight, and 50 standalone repeats of the new test green in every one of them.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
The enable records two running events so that a buffer handed to an analysis
tool is never empty, and those two inserts are the only ones in the kernel that
run inside the service that turns tracing on. The buffer pointer and the event
enable bits are both set a few lines above, so neither can be found in any other
state on the way in, and both inserts sat at 4 of 12.
A new test enables with a buffer sized from the kernel's own structures to hold
exactly one event, which puts both inserts on the wrap boundary, and with three
notifications: one that counts, one that filters the running event's own class,
and one that turns tracing off. The last two are what reach the second insert's
filtered and no-buffer arms, and both are ordinary application behaviour rather
than a contrivance - a notification that stops tracing is what an application
capturing a single bufferful of history does. The forced-state passes hold one
SMP protection across the write, the enable and the restore.
tx_trace_enable.c:392 goes to 12 of 12 and :391 to 10 of 12 on the union of the
eight configurations, from 4 each. The two arms left are the first insert's own
copies of the filtered and no-buffer tests, which nothing can reach between the
assignments above it and the insert. 133/133 in each of the eight, and 50
standalone repeats of the new test green in every one of them.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
The interrupt control, the two ISR inserts and the user event insert are part of
the tracing machinery rather than users of it, and nothing drove their inserts
beyond the conditions testcontrol.c and threadx_trace_basic_test.c happen to
present. The two ISR inserts were the whole of this suite's unstable set: their
buffer-wrap arms were reached when the buffer happened to wrap under an insert,
which is why they moved between runs.
A new test walks the services once per condition the insert distinguishes, and
positions the buffer one entry short of its end rather than waiting for it to
wrap, under one SMP protection and asserting the wrap per service. The interrupt
control is driven at the outermost level only, and is left out of the pass that
runs from interrupt context: _tx_thread_interrupt_control takes the Linux
critical section on every call and hands it back in full only on an enable, so a
pair completed where the section is already held leaves it two levels deeper and
locks the process. Its disable half runs inside the protection, where the insert
is, and its enable half once the protection is released. Its own forced-state
passes reach the two arms the interrupt pass would have covered.
All four sites go to 12 of 12 on the union of the eight configurations, from
7, 5, 5 and 6. 133/133 in each of the eight, and 50 standalone repeats of the
new test green in every one of them.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
Three regions of tx_misra.c had no caller in any of the eight build
configurations. The thread stack check is reached through TX_THREAD_STACK_CHECK,
whose call sites sit inside TX_ENABLE_STACK_CHECKING and which is redirected to
the shim only under TX_MISRA_ENABLE, and no configuration defines both. The user
timer derivation's six call sites are behind TX_TIMER_ENABLE_PERFORMANCE_INFO,
three of them behind TX_TIMER_PROCESS_IN_ISR as well. The trace entry conversion
has no caller in common_smp/src at all.
A new test drives all three through the macro the kernel itself uses, so the
shim and the in-line form the other builds compile are asserted to agree. The
stack check is the one with logic in it, and is given a scratch thread one fault
at a time: a cleared identifier, either stack fence overwritten, the recorded
high-water mark moved above the stack pointer and below the stack. That thread
is created with TX_DONT_START on a fenced buffer of the test's own and never
runs, so no faulted state is ever visible to the scheduler.
tx_misra.c goes from 138/163 lines and 16/30 branch outcomes to 163/163 and
30/30 on the union of the eight configurations. 133/133 in each of the eight,
and 50 standalone repeats of the new test green in every one of them.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
The test put one claim thread on every core the test thread was not using, and
waited for all of them to be claiming before it started searching. On a machine
with fewer processors than the port simulates cores that is not a safe wait: the
claim threads walk the pool holding the protection, so two of them can keep the
third from making any progress, and the test reports the arrangement as never
having arrived. One CI run in seven failed that way.
One claim thread is all the construct needs. It asks whether the pool can change
hands under a walk, and a single other core answers it; further claim threads
spend most of their time contending with each other rather than with the thread
under test. The walk count goes up from five to twenty to keep the margin the
extra threads were providing.
Between 360 and 1,320 changes of pool ownership per run over six runs in the two
configurations that schedule it slowest, against the one the coverage needs, in
2.5 seconds. 129/129 in each of eight configurations with retries disabled.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
Where the SMP byte pool search gives the protection up, it checks on taking it
back whether the pool is still its own, and if not it delays, resets the walk and
claims the pool again. Three branch outcomes and eight lines of that were never
executed. The pool owner is written in six places, all of them kernel services,
and none can run on the searching thread's own core while that thread is inside
the walk, so the construct needs a second core in use. The existing contention
test does not reach it because its pool is too small to open the window at all:
the protection is only given up once per multiple block search limit.
A new test keeps the test thread on its own core and puts one claim thread on
each of the others, each making the same unsatisfiable request and so walking the
same pool. A claim thread is then usually already holding the protection when the
test thread reaches a window, which is the state the construct is written
against. The test waits for every claim thread to be mapped and claiming before
it starts searching, rather than sleeping a fixed number of ticks, because how
long the rebalance takes varies by configuration. It asserts the arrangement
rather than the retry, which restores the owner the walk had already set and
leaves nothing a test can read.
Three outcomes and eight lines, measured at 48,971 changes of owner in a run of
2.6 seconds. This is the second test in the suite that needs more than one core.
129/129 in each of eight configurations with retries disabled, and sixty
standalone repeats clean in the three configurations that schedule it slowest.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
The SMP byte pool search walks the pool holding the protection and gives it up
every TX_BYTE_POOL_MULTIPLE_BLOCK_SEARCH blocks, so that a long walk does not
lock the other cores out for its whole duration. No test fragmented a pool past
that limit, so the release and the reacquire behind it were never executed and
the branch that guards them stood at one of two. The region has no monoprocessor
counterpart: that search gives the protection up on every block and has no
counter.
A new test fills a pool with small fragments until it refuses another, checks it
broke into at least twice the limit, and then asks for one more. Every block is
then in use or too small, so the search examines all of them and crosses the
limit several times. The walk length is the pool's fragment count plus one and
nothing else runs, so there is no timing in it. The limit is not a feature gate:
tx_api.h defines it to 20 unless the application overrides it, so the region is
compiled in every configuration.
One outcome and three lines, in all eight configurations. 129/129 in each with
retries disabled.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
The byte allocate keeps the same trace update in place the block allocate does,
in the same two regions, and it was uncovered for the same reason: no test had
run the service with tracing enabled, so the entry pointer checks stood at one of
two and the time stamp comparisons at zero of two.
A new test drives it with the three passes the block pool test uses. One
configuration reaches less of it than the others and that is a property of the
port rather than of the test: under TX_MISRA_ENABLE the trace time source is a
routine whose body returns a constant, so every entry carries the same time stamp
and the comparison reads equal whatever has been written over the entry. The
overwritten round drives the same arm as the intact one there, and the arm that
declines to update is reached in the plain trace configuration alone.
Seven of the eight outcomes and all seven lines. The remaining one is the false
arm of the immediate path's comparison. 129/129 in each of eight configurations
with retries disabled.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
The block allocate records its event, saves the entry and its time stamp, and
comes back to write the address it handed out into that entry if nothing has
overwritten it. There are two such regions, one on the path that finds a block
immediately and one on the path that suspends. Nothing had ever run the service
with tracing enabled, so the entry pointer checks stood at one of two with only
the false arm taken and the time stamp comparisons at zero of two.
A new test uses two threads and three passes: a plain successful allocate, a
suspending allocate handed a block with its entry intact, and the same handover
with the trace buffer written all the way round in between so the service
correctly declines to update. The helper runs at a higher priority than the test
thread, so every handover is a preemption rather than something that waits for a
tick, and both threads keep the suite's default core mapping so only one of them
runs at a time.
Seven of the eight outcomes and all seven lines. The remaining one is the false
arm of the immediate path's comparison, whose window is one unbroken SMP
protection with no trace insert in it. 129/129 in each of eight configurations
with retries disabled.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
Six byte pool services carry an in-line trace insert, and no test in the SMP
suite drove any of them with tracing enabled. Four sites stood at one of twelve
branch outcomes and the create and the delete at five of twelve, so 58 outcomes
of the macro's six conditions were never taken.
A new test walks the six services once per condition, in the shape the block
pool test uses. The two subsystems differ in what an interrupt may call and the
test reads that out of each error checking shell rather than assuming symmetry:
the byte allocate and the byte release are rejected at any non-zero system state
below initialisation, where the block pool permits both, so only the prioritize
and the information get run from the ISR pass here.
58 of 58 outcomes, every site at twelve of twelve. 129/129 in each of eight
configurations with retries disabled.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
Six block pool services carry an in-line trace insert, and no test in the SMP
suite drove any of them with tracing enabled. Four sites stood at one of twelve
branch outcomes and the create and the delete at five of twelve, so 58 outcomes
of the macro's six conditions were never taken.
A new test walks the six services once per condition: thread context, the event
class filtered out, interrupt context through the ISR dispatch, initialisation
context, the state an interrupt presents, and a buffer wrap with and without a
notification registered. The last two passes go through the internal entry
points, because the error checking shell rejects a create from an interrupt and
a delete from any caller that is not a thread. The buffer is positioned one
entry short of its end and the positioning and the call are held under one SMP
protection, so the boundary entry is the service's own by construction; the pass
asserts a wrap per service rather than in total.
58 of 58 outcomes, every site at twelve of twelve. 129/129 in each of eight
configurations with retries disabled.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
threadx_timer_multiple_test changes three timers to reschedule periods of one, two
and three ticks and then requires their counters to read exactly 103, 53 and 36
after a 200 tick sleep. The first of them changes on every tick, so that equality
has no margin at all. It failed one clean eight-configuration run and two of 220
standalone runs, both in misra_trace_build. The margin sweep that cleared this test
measured its 200 tick reschedule and not the one tick one it takes on afterwards.
The rescheduled counts are now checked against the window that actually elapsed,
which is the shape the accuracy test already uses: a timer cannot expire more often
than its period allows over that window, so that is the ceiling, and the floors
allow five expirations of lag while staying far tighter than any period error. The
earlier count of two holds anywhere between 300 and 499 ticks but was read at 301,
one tick above the expiration that produces it and before the system timer thread
need have processed it; that sleep goes to 310.
100 standalone runs in misra_trace_build, the configuration it failed in, all green.
With the first timer's reschedule changed from one tick to two it reports ERROR #16
and exits 1. 123/123 in each of eight configurations from clean, with the coverage
union unmoved at 5398/5450 lines and 3397/3610 branches.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
The SMP priority change helper places a ready thread on its new priority list
and then chooses which priority the thread should carry: the new one if it beats
the priority inherited from a mutex, and the inherited one otherwise. The second
arm and the two lines it guards had never run. Reaching it needs three things at
once — a thread holding an inheritance mutex, ready rather than running, and
being moved to a priority no higher than the one it has inherited — and nothing
in the suite arranged all three.
A new test arranges them. The owner holds two inheritance mutexes with a waiter
on each, so its inherited priority comes from the higher waiter; deleting that
waiter's mutex hands the owner back to the priority the remaining waiter calls
for, which is the inherited priority the helper then has to choose against. The
delete is issued by a thread above both waiters, so the owner is ready and not
running when the change arrives, which is what sends it down the helper rather
than down the full rebalance. Every thread in the suite is mapped to core 0
alone, so this needed no second core.
The site reads two of two and both lines are covered, in all eight
configurations. 123/123 tests pass in each of them, and the new test passes
fifty standalone repeats in every one.
The arrangement is its own test rather than part of the mutex work this batch
wrote anyway: measured with the coverage isolated to a single test, neither the
MISRA prioritize test nor the mutex trace test reaches the helper at all.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
Under TX_MISRA_ENABLE the mutex put calls the prioritize processing through a
retry loop, so that the call's result is read and the rule about discarding a
return value is met. The prioritize processing passes a success status through a
shim whose whole body returns success, so the loop's test can only be false on
the first pass and the arm that goes round again never ran. The other six
configurations compile a plain call with no branch at all.
A new test defines the shim itself, which is the technique the monoprocessor
suite already uses for the same two loops. The kernel is built shared and every
internal reference to the shim goes through the procedure linkage table, so the
definition in the test executable is the one reached, with no linker option and
no preloading. A shim that withholds success on alternate calls sends each loop
round exactly twice and leaves the mutex, the suspension list and the priorities
as the kernel expects them. The two loops sit on different paths through a
single put, and one release of an inheritance mutex with two suspended waiters
reaches both.
The shim's reach on this port was measured rather than carried over: objdump
gives one call site in the library, in the prioritize processing, because the
port overrides the other macro that could expand to it. Read the coverage with
that in mind — it is coverage of a kernel whose MISRA shim is the test's, which
the file says in its own header.
Both sites read two of two in the two MISRA configurations. 123/123 tests pass
in each of the eight configurations, and the new test passes fifty standalone
repeats in every one of them.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
The get service refuses to suspend a caller when the preemption lockout is
raised or when the request asks for no flags at all. The first arm is driven by
the suite's own preempt-disable safeguard; the second had no caller anywhere in
the suite, so one of the site's four outcomes was never taken. Measured rather
than inferred: the tracefiles put the uncovered outcome on the zero-request
operand in all eight configurations.
A new test asks the group for no flags under each of the four get options, with
a wait option, and then confirms that the flag it raised beforehand is still
set. The error checking shell places no constraint on the requested flags, so
the published service reaches the refusal with nothing forced. A request for no
flags can never be satisfied — an OR request finds none of the flags it asked
for present, and an AND request has its empty result rejected for not matching
the empty set it asked for — so a caller that suspended on one would never be
resumed, which is what the check prevents.
The site reads four of four on the union basis, which is the only genuine
control logic this group had. 123/123 tests pass in each of the eight
configurations, and the new test passes fifty standalone repeats in every one of
them.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
The six event flags services carry the same in-line trace insert as the
semaphore and mutex ones, with the same twelve outcomes from six conditions.
Four of the six sites had never run the macro body and the create and the delete
reached five outcomes of twelve, for the same reason: one test of 117 enabled
tracing.
A new test applies the pattern to the event flags services. The flag the set
raises is consumed by the get that follows it and the scratch group is created
by the first service and deleted by the last, so a pass leaves the system as it
found it. The set notification service is compiled out under
TX_DISABLE_NOTIFY_CALLBACKS and returns from above its own insert, which the
per-service expected-status function absorbs.
All six event flags trace sites read twelve of twelve, and the ten tx_event_
files are at 323/323 lines and 206/206 branch outcomes on the union basis. With
this the twenty trace inserts across the three subsystems are closed and the
thirty-three files of the group are at 1074/1074 lines. 123/123 tests pass in
each of the eight configurations, and the new test passes fifty standalone
repeats in every one of them.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
The six mutex services carry the same in-line trace insert as the semaphore
ones, with the same twelve outcomes from six conditions, and the same cause left
them uncovered: only one test in the suite enabled tracing. Four of the six
sites had never run the macro body and the create and the delete reached five
outcomes of twelve.
A new test applies the pattern to the mutex services. The subset an interrupt
may drive is narrower here and is decided by the shell rather than by the
service: the error checking shell rejects a create, a get and a put from an
interrupt and a delete from any caller that is not a thread, which leaves the
prioritize and the information service for the interrupt pass. The resident
mutex is created TX_NO_INHERIT deliberately, so a release with nothing suspended
takes the simple path and leaves the calling thread's priority alone; the
inheritance paths have tests of their own and nothing here depends on them.
All six mutex trace sites read twelve of twelve. The eleven tx_mutex_ files are
at 471/471 lines and 310/312 branch outcomes on the union basis; the two left
are the exit arms of the TX_MISRA_ENABLE release loops, which are unreachable by
construction and carry their own record. 123/123 tests pass in each of the eight
configurations, and the new test passes fifty standalone repeats in every one of
them.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
Each of the eight semaphore services carries one in-line trace insert, and the
macro scores twelve branch outcomes from six conditions: whether tracing is
enabled, whether the event class is filtered, what system state the caller is
in, whether the entry wraps the trace buffer, and whether a buffer-full
notification is registered. One test of 117 enabled tracing, so six of the eight
sites had never run the macro body at all and the create and the delete reached
five outcomes of twelve.
A new test walks the services once per condition, in an order that leaves the
system as it found it. The create and the delete are driven through their
internal entry points in the forced-state passes, because the error checking
shell rejects them from a non-thread caller and the insert sits behind it. The
positioning and the call are held under one SMP protection, so the boundary
entry is the service's own by construction rather than by being quick enough,
and the pass asserts a wrap per service so that a service whose slot was taken
by something else fails here instead of losing coverage quietly. One entry short
is the right distance: every one of these inserts sits ahead of its own counting
and suspension logic, so nothing the service goes on to do writes in front of
it.
All eight semaphore sites read twelve of twelve, and the twelve tx_semaphore_
files are at 280/280 lines and 194/194 branch outcomes on the union basis.
123/123 tests pass in each of the eight configurations, and the new test passes
fifty standalone repeats in every one of them.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
When a thread's time slice expires the kernel moves it to the end of the ready
list at its priority and walks the rest of that list looking for a thread to
hand the core to, stopping at the first one it can use. The walk only runs to
the end when every other thread at the priority is already the thread its own
core is executing. Two tests reached that by accident of scheduling -- one in
ten of twenty-five standalone runs -- so the arm read covered in twelve of
fourteen CI runs and missed in two, which is neither a gap nor coverage.
It needs more than one core in use, which is why nothing reaches it on purpose:
with every thread mapped to core 0 alone there is one executing thread, and the
walk stops at the first thread it looks at. A new test clears the core
exclusions on three workers at one priority so they run one to a core, then
asserts the arrangement instead of waiting for it. Two of the three ways out of
the walk are ruled out before it runs, so the only early exit left is the one
that hands the core over -- and that one overwrites the core's entry in the
execution list, so the entry still holding the same thread afterwards means the
walk ran to the end. The arrangement, the expiry and the check are held under
one SMP protection, and the expiry is driven by calling the routine the timer
interrupt calls, inside the protection the timer interrupt holds when it calls
it.
The arm is taken in 30 of 30 standalone runs against 10 of 25 before, and the
test is green in 50 consecutive runs in each of the eight configurations. It is
a tx_thread_ construct in a timer batch, deliberately: it belongs to a batch
that closed two steps ago and no remaining batch would reach it.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
The test checks the published tx_time_get() against _tx_time_get() once, because
the SMP header remaps the macro to a direct read of the system clock and every
pass below then drives the service rather than the macro. The two readings were
taken with the tick running between them, so a tick landing there made them
differ and failed the test. Measured at one run in two hundred.
Both readings now happen inside one SMP protection, which is what the check
always meant: the same value taken twice rather than two calls into the same
function.
1,200 standalone runs green -- fifty repeats of each of the three new timer
tests in each of the eight configurations -- against one failure in two hundred
before. 116/116 in each of eight configurations, twice from clean, with retries
disabled.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
The timer thread's body is one loop whose controlling expression is, under
TX_MISRA_ENABLE, a call to a function that returns a constant rather than the
literal the other six configurations compile. The exit arm therefore never
executes, and in the configurations without the macro gcc folds the condition
and the branch does not exist at all.
The function has no behaviour of its own -- its whole body is return(TX_TRUE)
-- so the test defines it. The kernel is built shared and every internal
reference goes through the procedure linkage table, so the definition in the
test executable is the one the loop reaches, with no linker option and no
preloading. It has exactly two callers in the library: this loop, and the
initialisation loop in _tx_thread_smp_current_state_set, which is reached only
from the three initialisation paths that run before the scheduler starts a
thread and so cannot be reached from a running test.
What that costs is the timer thread, because the loop is the whole of its entry
function. The test therefore does it deliberately and last: it proves three
expirations have been processed, arms the shim, waits for the thread to end,
and reports. Nothing after the arming depends on a tick. The test file carries
the disclosure this needs -- which symbol is replaced, what the library's
version does, why replacing it leaves the rest intact, and where the replaced
function's own coverage comes from.
tx_timer_thread_entry.c:112 reads two of two in misra_build and
misra_trace_build. The file stands at 73/73 lines and 28/30 branch outcomes;
the two left are the spin-on-error loops over _tx_thread_smp_core_exclude,
which has behaviour of its own. 116/116 tests pass in each of eight
configurations, twice from clean, with retries disabled.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
The active timer list is a ring of thirty-two slots and the current pointer
walks it one slot a tick. A timer activated for close to a full lap lands on a
slot the pointer has already passed, and the information service then measures
the time remaining in two parts rather than one. Which slot a timer lands on
depends on where the pointer happens to be when it is activated, so the suite
reached that arm by coincidence: the branch and its two lines were covered in
some runs of an unchanged tree and missed in others, which is the whole of the
line variance in this suite and one of its five unstable branch outcomes.
A new test reads the current position first and chooses the activation from it,
with the read, the activation, the interrogation and the deactivation held
under one SMP protection so the tick cannot move the pointer in between. It
asserts the arrangement rather than the answer: the two arms compute the same
number of ticks by design, so a test that checked only the returned count would
pass without having reached either one in particular. Three arrangements are
driven -- the slot ahead of the pointer, the slot behind it, and the slot
behind it on a timer carrying more ticks than the list can represent.
tx_timer_info_get.c reads 37/37 lines and 38/38 branch outcomes, in every one
of the eight configurations and in both clean runs, which is what the
coincidence never gave. 116/116 tests pass in each of eight configurations,
twice from clean, with retries disabled.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
One test of the ThreadX SMP regression suite enables event tracing, so the
in-line trace insert each timer and time service carries ran almost everywhere
with a null trace buffer pointer and scored one of its twelve branch outcomes.
Eight sites sat at one or five of twelve, 80 uncovered outcomes in all.
A new test walks the eight services once per condition the macro distinguishes:
thread context, both event classes filtered out, interrupt context,
initialization context, the state an interrupt presents, and a positioned
buffer wrap with and without a full notification registered. The timer services
and the time services score against different event classes, so the filtered
pass filters both. One entry is the right wrap distance for all eight: no timer
or time service writes an event buffer entry ahead of its own insert, and every
service in the notified pass has to report a wrap of its own, so a boundary
entry taken by something else fails the test rather than quietly losing
coverage.
One service is driven through its internal entry point in every pass rather
than only in the forced-state ones, and that is the SMP path rather than the
monoprocessor's. tx_api.h remaps tx_time_get() to a direct read of the system
clock unless TX_MISRA_ENABLE is defined, so in six of the eight configurations
the published macro never enters _tx_time_get and the insert inside it is
unreachable from above. The test checks the macro against the service and then
drives the service. The monoprocessor header has no such remapping.
All eight sites read twelve of twelve, taking the tx_timer_ and tx_time_ group
from 170 to 250 of its 252 branch outcomes on the union of eight
configurations. 116/116 tests pass in each of the eight, twice from a clean
tree, with retries disabled.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
The resume path and the preemption check each compare a core's current thread
against its execute entry and, when they differ, pick the entry up to check its
stack. Both guard that with a null test, because the entry is empty whenever
the core has nothing to run. Neither guard had ever been reached with an empty
entry: a core whose current thread is still set is normally a core that is
running something the scheduler has already chosen a successor for.
A new test constructs the state with tx_thread_smp_core_exclude, which supports
excluding a thread from every core and leaves it ready and permanently
unmapped. The resume pass holds preemption off and one SMP protection across
the whole arrangement, so nothing else observes it: the test thread excludes
itself, resumes a scratch thread that is excluded from every core as well, and
the resume reaches its own preemption check with the execute entry still empty.
The preemption check cannot be driven that way, because returning to the system
is what it is for, so that pass gives the core up and the tick puts the
exclusion mask back once the core has gone idle. A null current thread on core
0 is the signal the tick reads: the port leaves the interrupted thread in that
entry for the whole of an interrupt, so it is empty only when the core had
nothing running, which is the one moment that cannot be confused with the
window before the check runs. The arrangement is repeated, because a tick
landing in that window costs the attempt rather than the test.
Two branch outcomes close, the last uncovered logic in
tx_thread_system_preempt_check.c and tx_thread_system_resume.c. Both remain
short of 100% branch on the null arm of the stack check behind those guards,
which no valid state produces.
113/113 tests in each of eight build configurations, from clean, with retries
disabled.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
TX_THREAD_STACK_CHECK sits on nine sites in the SMP scheduler and scores seven
conditions at each of them. Ordinary use only ever takes the clean arm of five
of those, so the nine sites sat between eight and eleven of fourteen -- 44
uncovered branch outcomes.
A new test applies one fault to every thread the sites look at, drives one site
with it in place, and takes it out again: the lowest recorded stack pointer put
above the thread's own, each of the two fences around the stack overwritten,
the recorded pointer dropped below the stack, the word under it made used, and
the thread identifier cleared. The fault is applied per step rather than per
pass, because the first site to see a bad recorded pointer repairs it on the
way through. Three sites look at the thread the core is about to run rather
than the one being operated on, and only a time-slice rotation separates the
two, so those steps rotate the ready list and hold preemption off across the
rotation and the service that follows it -- except the preemption check, which
does nothing while preemption is held off. A cleared identifier is
incompatible with the published entry points, so the steps drive the internal
ones. The file also covers the two parts of the stack machinery no fault
reaches: the realignment a create performs when it is handed an odd stack
address, and the error handler's MISRA build, where there is no application
handler to call and the function exists only to keep its argument referenced.
36 of the 44 outcomes close, with one branch and one line in
tx_thread_create.c and two branches and three lines in
tx_thread_stack_error_handler.c. The eight left are the macro's own
null-thread arm. Four of the nine sites guard it in the source with a null test
of their own, so it cannot be taken there at all; four more are handed a thread
the caller has already dereferenced; the ninth, in tx_thread_relinquish,
already takes it.
113/113 tests in each of eight build configurations, from clean, with retries
disabled.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
One test of the ThreadX SMP regression suite enables event tracing, so the
in-line trace insert each thread service carries ran almost everywhere with a
null trace buffer pointer and scored one of its twelve branch outcomes.
Eighteen sites across the tx_thread_ sources sat between one and seven of
twelve, 170 uncovered outcomes in all.
A new test walks the sixteen thread services once per condition the macro
distinguishes: thread context, the thread and internal event classes filtered
out, interrupt context, initialization context, the state an interrupt
presents, and a positioned buffer wrap with and without a full notification
registered. Three things are the SMP path rather than the monoprocessor's. The
system state is a per-core array behind a macro that expands to a function
call, so the forced-state passes define TX_THREAD_SMP_SOURCE_CODE and write the
entry of the core they read back. The time-slice check reaches its insert only
while a slice is active on some core, so the test thread carries one. And the
scheduler's own inserts sit behind the published ones, so the resume and the
suspend are wrapped two entries short as well as one. Every service in the
notified wrap pass has to report a wrap of its own, so a boundary entry taken
by something else fails the test rather than quietly losing coverage.
tx_thread_sleep is the one insert that cannot reach twelve of twelve. The
service tests the system state itself, ahead of its own insert, and refuses
every caller that is not a thread, so the three outcomes that need a non-zero
state have no route through either entry point. It stops at nine of twelve and
the test drives the internal entry point at a forced state to record the
refusal.
167 of the 170 outcomes close and seventeen of the eighteen sites read twelve
of twelve. 113/113 tests in each of eight build configurations, from clean,
with retries disabled.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
Each of the nine queue services carries one in-line trace insert, and the
insert behaves differently according to the system state it runs in, whether
its event class is filtered, whether it is the entry that wraps the trace
buffer, and whether a buffer-full notification is registered. Exactly one test
in the SMP suite enabled event tracing, so at seven of the nine sites the macro
body never executed at all and the other two reached five of its twelve
outcomes: 91 uncovered branch outcomes in the queue sources, with no test
missing for anything else in the subsystem.
The new test walks the nine services once per condition, in an order that
leaves the system as it found it, so the passes compose and repeat. Two things
are specific to SMP. The system state is per core and is normally reached
through a function call, so the forced-state passes take the array itself and
write the entry of the core they are running on, read rather than assumed. The
wrap pass holds the SMP protection across both the positioning and the service
call, which makes the boundary entry the service's own by construction: every
trace insert in the kernel is written inside that same protection, so nothing
else can take the slot in between. Create and delete are driven through their
internal entry points in the forced-state passes only, because the error
checking shell rejects both from any caller that is not a thread; the shell is
covered by its own tests and what is under test here is the insert behind it.
Nothing is interposed and no certified source is touched.
The queue sources are now at 622 of 622 lines and 394 of 394 branches over the
union of the eight build configurations, from 303 of 394. Twelve further branch
outcomes and six lines outside the subsystem arrive with it, in the MISRA shim,
the trace object registry and the thread suspend and sleep paths. All eight
configurations build clean and pass 110 of 110, twice from clean with retries
disabled, and the new test is green in 75 consecutive runs across three
configurations.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
109 of the 110 tracked files in test/smp/regression carry no copyright notice
and no licence text at all. The one that does was added this month. Everything
else in the repository -- common/src, common_smp/src, the ports, the samples --
carries the standard header, so this is an oversight in one directory rather
than a deliberate exemption, and it has been there since the tests were first
published. The monoprocessor tree had the same gap and was fixed the same way.
The header added here is byte identical to the one in common_smp/src, including
its SPDX identifier. Nothing else in these files changes: every hunk is ten
added lines and none removed. There is deliberately no "Eclipse ThreadX
contributors" line and no AI disclosure paragraph -- this commit contributes no
code, it records the licence the files were always under. A later change that
does contribute code to one of these files adds the contributor line and its own
attribution at that point, which is when there is something to attribute.
Verified: all eight build configurations build clean and pass 109 of 109, and
the coverage figure is unmoved at 5386/5450 lines and 2807/3610 branches over
the union of the eight.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
_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]>
|
|
threadx_timer_multiple_accuracy_test activates three timers with periods of one,
two and three ticks, sleeps 300 and requires their counters to read exactly 300,
150 and 100. The period-one timer changes its count on every tick, so that
equality has no margin whatsoever, and it failed on a clean eight-configuration
run. This thread is woken by a host timer thread and given one of four emulated
cores, so a wake one tick late moves all three counters; expirations are also
processed by the system timer thread rather than in the interrupt, so under load
the counters lag the clock and catch up afterwards.
The clock is now noted before the timers are activated and the counters are
checked against the window that actually elapsed. A timer cannot expire more often
than its period allows over that window, which is exact and is the property being
checked, so that is the ceiling; the floors allow five ticks of lag and remain far
tighter than any period error, since a period of two ticks on the first timer
would read about 150 against a floor of 295.
The rest of the class was swept by tick margin rather than by pattern, which is
what the two earlier sweeps got wrong. threadx_timer_multiple_test is safe with
about 199 ticks of margin on a 200 tick period. The block and byte memory
suspension timeout tests have four to five ticks either side of a ten tick worker
timeout: examined, and left alone deliberately.
Verified in default_build_coverage: passes, and with the first timer's period
changed from one tick to two it reports ERROR #8 and exits 1.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
threadx_time_get_set_test clears the clock, sleeps 35 ticks and requires the clock
to read exactly 35. That is the same shape as the two sites fixed earlier in this
batch: the read happens once this thread has been given a core again, and on this
port that is a host timer thread and four emulated cores rather than hardware, so
it can land a tick late with nothing wrong.
The check now requires the clock to be no earlier than 35 and no later than 36.
The lower bound is the property -- a sleep that ends early is a real fault -- and
the upper is scheduling latency. The same band is already in
threadx_thread_simple_sleep_test and its non-clear twin, both of which accept 18
or 19 after sleeping 18, so this is the suite's own precedent.
The second comparison in the same file, tx_time_get() against 7 on the line after
tx_time_set(7), is left exact. Its window is a handful of instructions against a
ten millisecond tick, and a band there would remove the only fault it can catch.
This corrects the record as well. The commit that closed the other two sites said
this file's only exact comparison was the one read on the line after it is set.
There are two, and the one it missed is the more exposed: that sweep matched
tx_time_get() against a literal and skipped the site assigning into a variable
first. Re-swept on every form of clock read, nothing else exact remains.
Verified in default_build_coverage: passes, and with the sleep shortened to 30
ticks it reports ERROR #2 and exits 1.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
threadx_smp_time_slice_test resumes eight threads at priority 31, sleeps 20 ticks
and requires all eight to have run. One of them, thread 31h, is created with a
preemption threshold of 30 against that priority, deliberately: it is what
reaches the branch in _tx_thread_time_slice where an expired time-slice thread is
replaced by a thread with preemption-threshold enabled.
While the scheduler has 31h recorded as running under a threshold,
_tx_thread_smp_rebalance_execute_list will not place any thread at or below that
threshold on any core at all. The other seven are all at priority 31, so they can
be locked out of all four cores for the whole window. Instrumented to print the
scheduler's state at the failure, five failures out of five show the same thing:
31a, 31b and 31c with a run count of zero, four peers at about 110 each, 31h at
about 2,250 having been given a core twice, and cores 2 and 3 idle with a null
execute pointer.
The test now releases 31h's threshold after the first window and gives the others
a window of their own before checking. The branch the threshold exists for is
taken in the first window, so no coverage is given up.
Measured standalone before: 28 failures in 500 runs of default_build_coverage, 19
of trace_build, 17 of misra_build, 12 of misra_trace_build. After: 0 in 400, 200
and 800. With thread 31a left unresumed it still reports ERROR #31 and exits 1.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
threadx_timer_information_test carries the same check as threadx_timer_simple_test, character for
character -- expiration counter exactly 23 and clock exactly 120 after a sleep of 120 -- and
threadx_semaphore_timeout_test carries the same check as threadx_mutex_suspension_timeout_test,
a clock of exactly 33 after a suspension with a 33 tick timeout. Neither was in the inventory
this step started from, and the first pass fixed only the copy of each that had been seen to
fail. A campaign of ninety configuration runs then caught the timer information test twice.
Both now get the fix their twin already has. The timer counter is bounded above by the clock it
was read beside, which is exact and catches a timer expiring too often, and below by a floor of
twenty. The semaphore timeout keeps its lower bound of 33, which is the kernel property, and is
allowed two ticks above it for the latency of being given a core again.
The suite was swept for the rest of the class rather than fixed instance by instance. The only
other exact comparison against tx_time_get here is in threadx_time_get_set_test, where the clock
is read on the line after it is set: that window is a handful of instructions against a ten
millisecond tick, and a range there would remove the only thing the check asserts.
Verified in default_build_coverage: both pass, and with the timer's reschedule period changed
from one tick to two and the semaphore's requested timeout from 33 ticks to 20, they report
ERROR #9 and ERROR #7 and exit 1.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
Both ThreadX SMP event flag ISR tests suspend on the group with a four tick
timeout and treat anything but success as a failure. The ISR only acts when both
threads are suspended on the group at the same moment, so each thread's wait has
to outlast however long the other takes to come back round and suspend again. On
this port that is four emulated cores handing threads about through one scheduler
thread, and four ticks is a scheduling budget with no margin.
It runs out. threadx_event_flag_isr_set_clear_test reported ERROR #8 once in a
clean eight configuration run and once more in a thousand standalone runs, both
times with nothing wrong: the suspension simply timed out before the other thread
got back. The same construction is in the wait abort test, which has not been
seen to fail and is the same one bad round away from it.
Both suspensions in both tests now ask for a hundred ticks. What the check exists
for is a set, or a wait abort, that never reaches a suspended thread, and a
hundred ticks still catches that: with the ISR's condition forced false, each
test reports ERROR #8 and exits 1 exactly as before, one second later.
This is the last of the tick budgets on this branch that were written for
hardware and do not survive the simulation.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
threadx_timer_simple_test's ThreadX SMP copy clears the clock, activates a timer
that first expires at tick 100 and then on every tick, sleeps 120 and requires
the expiration counter to read exactly 23 and the clock exactly 120. Neither
equality survives this port. The thread can be given a core a tick or two after
its sleep expires, and expirations are processed by the system timer thread
rather than in the interrupt, so under load the counter lags the clock and
catches up afterwards.
Two expirations were counted before the clock was cleared, so the counter can
never exceed the clock less 97. That ceiling is exact, is what catches a timer
expiring too often, and is now the upper bound. The lower bound is a floor of
twenty: the timer expired at least eighteen of the twenty one times it was given,
where a period of two ticks reaches thirteen and fails. The clock must have
reached 120 and is read once into a local, because the interrupt that advances it
advances the counter too and does the clock first.
Measured over four hundred runs across the eight build configurations, half with
the machine deliberately loaded, the counter read 22 or 23 and the clock 120, 121
or 122. Before the port's mutex defect was fixed the same check saw 27 at a clock
of 127, which the new bounds also admit.
Verified in default_build_coverage: passes as before, and with the timer's
reschedule period changed from one tick to two it reports ERROR #28 and exits 1.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
threadx_mutex_suspension_timeout_test's ThreadX SMP copy clears the clock,
suspends on a mutex with a 33 tick timeout and then requires the clock to read
exactly 33. The lower bound is the kernel property being checked: the suspension
must not end early. The upper bound is not. The clock is read after this thread
has been given a core again, and on this port the tick comes from a host timer
thread rather than from hardware, so the read can land a tick or two past the
expiry with nothing wrong. The equality fails on that latency alone, once in
ninety configuration runs.
The check now requires the clock to be no earlier than 33 and no later than 35,
and reads it once into a local rather than twice, so a tick arriving between two
reads cannot decide the result.
Verified in default_build_coverage: passes as before, and with the requested
timeout cut from 33 ticks to 20 it reports ERROR #3 and exits 1.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
threadx_thread_multiple_sleep_test requires three threads that each sleep 33
ticks in a loop to have run exactly four times when the controlling thread wakes
from a sleep of 101. A thread whose first pass falls at tick s has run four times
over the window from s+99 to s+131, so reading at 101 allows a start delay of
only 0, 1 or 2 ticks, and the test's own comment shows the author bought exactly
one tick of room. On this port the start delay is decided by how quickly three
threads get one of four emulated cores, and three ticks of it was enough to fail
the test twice in ninety configuration runs.
The controlling thread now sleeps 115 instead of 101, which puts the check in the
middle of the 33 tick window rather than at its edge and allows a start delay of
up to 16 ticks. The assertion is untouched and still requires exactly four
passes: a fifth cannot arrive before tick s+132.
Verified in default_build_coverage: passes as before, and with one sleeper's
period changed from 33 ticks to 40 it reports ERROR #5 and exits 1.
The ThreadX copy of this test is identical and carries the same two tick margin.
It has not been seen to fail, which is consistent with one emulated core rather
than four, but the margin is as thin there.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
threadx_event_flag_suspension_timeout_test's ThreadX SMP copy requires its two
worker counters to read exactly 33 and 13. Thread 1 gets a two tick timeout and
thread 2 a five tick one, and both increment once per pass, so over a window of W
ticks the counters read 1 + W/2 and 1 + W/5. W is the 63 tick sleep plus however
much of a tick separates it from the moment the two threads first ran, and on
this port a tick comes from a host timer thread rather than from hardware, so
neither end is exact. It was the most frequent single failure on this branch,
three events in ninety configuration runs, spread over three unrelated build
configurations.
The two counters are now checked against ranges. The upper bounds are what the
timeouts allow, a tick of window and no more. The lower bounds carry the port's
starvation instead, because a thread that does not get a core runs fewer times.
Measured over four hundred runs across the eight configurations, half of them
with the machine deliberately loaded, thread 1 read 31 to 33 and thread 2 read 13
or 14; the bounds of 28 and 12 leave margin on that and still fail a timeout one
tick longer than it should be, which reads 22 and 11.
The ThreadX copy was given a margin upstream in #246 and #247 for the same
reason; only that copy was changed and the SMP half has been failing since.
Verified in default_build_coverage: passes as before, and with thread 1's timeout
changed from two ticks to three it reports ERROR #7 and exits 1.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
threadx_event_flag_isr_set_clear_test and threadx_event_flag_isr_wait_abort_test
are the ThreadX SMP copies of two tests bounded earlier on the ThreadX side. Both
still loop until the periodic ISR has fired forty times while the kernel happens
to hold _tx_thread_preempt_disable, wait for that window rather than forcing it,
and carry no iteration cap, no wall clock escape and no failure path. The set and
clear test has been seen to hang, and the wait abort test to fail, on this
branch.
Both are bounded the same way as their ThreadX copies. The budget is wall clock
rather than ticks: a tick arrives only when the port's timer thread runs, and on
this port four emulated cores share one host timer thread, so the tick clock
falls behind real time under exactly the load that makes the wait long. The count
asked for stays at forty, which both reach in about two seconds in every
configuration. A run that reaches the window no times at all now fails with
ERROR #11 rather than passing on bookkeeping that says nothing about the window.
Verified in default_build_coverage: both pass normally reporting 40 of 40 windows
in one to two seconds; with the count raised past reach and the budget cut to
three seconds they stop at four seconds, report 153 and 150 windows reached, and
pass; with the window made unreachable they report reaching none, print ERROR #11
and exit 1. Both pass in each of the eight build configurations.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
threadx_thread_delayed_suspension_test's ThreadX SMP copy waits without a bound
for an interrupt to land while thread 2 is part way through suspending. That
window is narrow, and a run which never reaches it has no way out: it spins until
ctest kills it. The ThreadX copy was bounded in #645 and the SMP half was left
behind.
This is that commit's SMP half, applied unchanged. The budget is wall clock
rather than ticks, for the reason the earlier bounds record: a tick arrives only
when the port's timer thread runs, so the tick clock falls behind real time under
the instrumentation that makes the wait long. The check after the loop compares a
counter captured inside the window, so it is skipped when the window was not
reached and the run says so instead of comparing against a value that means
nothing.
Verified in default_build_coverage: a normal run passes as before; with the
budget forced to zero the run reports that the window was not reached, passes,
and the dependent check does not fire. The test passes in each of the eight build
configurations.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
threadx_thread_wait_abort_and_isr_test's ThreadX SMP copy still waits for a
probabilistic window with no bound, no failure path and no record of what it
reached. A run that settles into the resonance the ISR comment describes never
ends and is killed by ctest at its timeout with nothing usable in the log. The
ThreadX copy was given a bound in #644 and #649; only that copy was changed, and
the SMP half has been waiting since.
This is those two commits' SMP half, applied unchanged. The budget is wall clock
rather than ticks, because a tick arrives only when the port's timer thread runs
and so falls behind real time under exactly the load that makes the wait long.
The count asked for stays at twenty: this copy reaches twenty windows in under
half a second in all eight configurations, so there is nothing to gain by asking
for fewer. A run reaching the window no times at all now fails rather than
passing on bookkeeping no window ever touched, and every run prints the count it
reached.
Verified in default_build_coverage: a normal run reaches 20 of 20 and passes;
with the count raised past reach and the budget cut to three seconds it stops at
four seconds, reports 114 of 4000000 and passes; with the window made
unreachable it reports 0 of 20, prints ERROR #8 and exits 1. The test passes in
each of the eight build configurations.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
The SMP suite failed this test in CI on disable_notify_callbacks_build, and it
is the same defect upstream diagnosed and fixed on its own branch.
The test ends with
thread_0.tx_thread_inherit_priority = 0;
_tx_thread_smp_simple_priority_change(&thread_0, 16);
stating the intent of reaching the branch where the new priority is below the
inheritance priority. Zero cannot reach it, because 16 is not less than 0. The
other branch runs instead, and that branch records the inheritance priority as
the thread's priority while the code after it links the thread into the list for
the new priority regardless, so thread 0 came away claiming priority 0 while
living in the priority 16 list.
Both halves hurt. Priority 0 ties with the control thread, so resuming the
control thread raised no preemption and left the execute pointer alone; and
completing thread 0 then removed it from a list it was never in, leaving core 0
owned by a completed thread with the control thread ready and unschedulable. The
test prints SUCCESS and the process never exits, so ctest kills it at the
thousand second timeout, twice, and the log carries nothing past the result line.
The inheritance priority is now above the new priority, which is what the branch
the comment names actually requires, and is put back to TX_MAX_PRIORITIES
afterwards so nothing downstream reasons about an inheritance that is not there.
Protection is held across the call, since the routine is internal and expects it
and was being called in the open.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
under GCC 14
threadx_smp_random_resume_suspend_exclusion_pt_test.c calls
_tx_thread_smp_rebalance_execute_list directly, to exercise the rebalance path
without waiting for the scheduler to reach it, but included only tx_api.h. The
declaration is in tx_thread.h, so the call was an implicit declaration. GCC 13
warns about that; GCC 14 rejects it.
The cost was the whole suite rather than the one test. The object failed to
compile in every one of the five build configurations, so ctest reported the
missing binary as a failing test and no configuration could go green:
before 434 of 435 targets built, 1 test failed in each configuration
after 435 of 435 targets built, 0 tests failed in each configuration
The test itself passes, in 5.08 seconds.
Including tx_thread.h is what the suite already does where a test reaches for an
internal declaration: 18 of the 110 SMP regression tests include it for the same
reason.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
Co-authored-by: TiejunZhou <[email protected]>
|
|
|