Skip to content

ThreadX SMP Linux port deadlocks on a lost mutex wake-up, and the regression suite hangs because of it #752

Description

@fdesbiens

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions