The ThreadX SMP regression suite hangs intermittently on the Linux simulation port. Measured with ctest's retry disabled, 17.8% of local configuration-runs and 3.6% of hosted-runner ones were not green on the first attempt, across twelve distinct tests. Six of them hang and are killed at the ctest timeout; the rest fail assertions for unrelated reasons. --repeat until-pass:2 is the only reason the suite has been reporting 108/108.
The hang is a deadlock in ports_smp/linux/gnu, not in common_smp/src. Four hung processes were captured untraced, across three tests, and all four show the same state:
- a ThreadX thread in
sigsuspend() inside _tx_linux_thread_suspend_handler, sitting on top of pthread_mutex_lock inside _tx_linux_mutex_obtain, reached from _tx_thread_smp_protect;
- the scheduler thread blocked in
pthread_mutex_lock at _tx_thread_schedule;
_tx_linux_mutex reading __lock = 0, __owner = 0, __nusers = 0, tx_linux_mutex_owner = 0, tx_linux_mutex_nested_count = 0 — free, with waiters on it;
- the protection held by the port's timer thread, which is waiting on
_tx_linux_isr_semaphore for a scheduler pass that cannot happen;
- no progress over a ten second control sample, with the simulated clock frozen.
The cause is a lost futex wake-up. The port's suspend handler calls sigsuspend() and does not return until the thread is resumed, and the signal can arrive while the thread is parked on _tx_linux_mutex. glibc waits for a contended mutex in a loop that re-arms the futex wait after a signal; this handler never returns to that loop. The next release hands its wake-up to the suspended thread, which will not act on it, and every other thread parked on the mutex stays in futex_wait on a word that will not change again. On this port that deadlocks the process rather than merely delaying it, because the only thread that can resume the suspended one is the scheduler and the scheduler takes this mutex on every pass.
The port already contains the guard that was meant to prevent this. _tx_linux_mutex_obtain sets tx_thread_linux_mutex_access immediately before the lock call and clears it immediately after, and in every capture the flag is set on precisely the thread that got suspended. Nothing in the tree reads it: three writes, no reads, the only other mention being its initialisation to TX_FALSE in tx_thread_stack_build.c.
Reading the flag in _tx_linux_thread_suspend and skipping the signal does not work, because every caller of that function holds the mutex, so a thread queued on it cannot make progress while the suspender waits for the handshake. Blocking the suspend signal around the lock call has the same problem. What does work is re-arming the wait: see the pull request below.
The monoprocessor Linux port looks to have the same defect latent in it. ports/linux/gnu takes its critical section with a plain pthread_mutex_lock through tx_linux_mutex_lock and its suspend handler is the same sigsuspend that does not return. It has not been observed — the ThreadX suite recorded no first-attempt failures over 105 configuration-runs and 8,640 further test executions — which is consistent with one emulated core and far less suspend and resume traffic rather than with the code being different.
The ThreadX SMP regression suite hangs intermittently on the Linux simulation port. Measured with ctest's retry disabled, 17.8% of local configuration-runs and 3.6% of hosted-runner ones were not green on the first attempt, across twelve distinct tests. Six of them hang and are killed at the ctest timeout; the rest fail assertions for unrelated reasons.
--repeat until-pass:2is the only reason the suite has been reporting 108/108.The hang is a deadlock in
ports_smp/linux/gnu, not incommon_smp/src. Four hung processes were captured untraced, across three tests, and all four show the same state:sigsuspend()inside_tx_linux_thread_suspend_handler, sitting on top ofpthread_mutex_lockinside_tx_linux_mutex_obtain, reached from_tx_thread_smp_protect;pthread_mutex_lockat_tx_thread_schedule;_tx_linux_mutexreading__lock = 0, __owner = 0, __nusers = 0,tx_linux_mutex_owner = 0,tx_linux_mutex_nested_count = 0— free, with waiters on it;_tx_linux_isr_semaphorefor a scheduler pass that cannot happen;The cause is a lost futex wake-up. The port's suspend handler calls
sigsuspend()and does not return until the thread is resumed, and the signal can arrive while the thread is parked on_tx_linux_mutex. glibc waits for a contended mutex in a loop that re-arms the futex wait after a signal; this handler never returns to that loop. The next release hands its wake-up to the suspended thread, which will not act on it, and every other thread parked on the mutex stays infutex_waiton a word that will not change again. On this port that deadlocks the process rather than merely delaying it, because the only thread that can resume the suspended one is the scheduler and the scheduler takes this mutex on every pass.The port already contains the guard that was meant to prevent this.
_tx_linux_mutex_obtainsetstx_thread_linux_mutex_accessimmediately before the lock call and clears it immediately after, and in every capture the flag is set on precisely the thread that got suspended. Nothing in the tree reads it: three writes, no reads, the only other mention being its initialisation toTX_FALSEintx_thread_stack_build.c.Reading the flag in
_tx_linux_thread_suspendand skipping the signal does not work, because every caller of that function holds the mutex, so a thread queued on it cannot make progress while the suspender waits for the handshake. Blocking the suspend signal around the lock call has the same problem. What does work is re-arming the wait: see the pull request below.The monoprocessor Linux port looks to have the same defect latent in it.
ports/linux/gnutakes its critical section with a plainpthread_mutex_lockthroughtx_linux_mutex_lockand its suspend handler is the samesigsuspendthat does not return. It has not been observed — the ThreadX suite recorded no first-attempt failures over 105 configuration-runs and 8,640 further test executions — which is consistent with one emulated core and far less suspend and resume traffic rather than with the code being different.