Conversation
_tx_thread_smp_unprotect takes the Linux mutex on entry and releases it twice when the protection structure names this core, but only once when it does not, while the matching _tx_thread_smp_protect took it once either way. _tx_thread_system_return clears the protection outright, so an unprotect can find it no longer naming its core and return having released one level fewer than were taken. A thread that later reaches _tx_linux_mutex_release_all drains that level. The timer interrupt thread has none on its tick path unless a thread was preempted on core 0, so a level leaked there while core 0 is idle is never recovered: the nesting count never reaches zero, the mutex is never handed back to Linux, and every other thread waits on it for the life of the process while the tick keeps arriving. The release of the level the matching protect took now happens whether or not the protection still names this core. Protection bookkeeping and scheduling are unchanged, and releasing beyond what a thread holds was already a no-op. Two hung processes captured untraced, in different tests, show the timer thread owning the mutex with a nesting count of one while parked in its tick wait, the scheduler blocked in pthread_mutex_timedlock, and the clock advancing 99 ticks a second across a ten second sample while nothing else changes. On a healthy run that path is taken 0 times in 26,546 unprotect calls. Forcing it on the timer thread leaks one level before this change and balances after it. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #757
_tx_thread_smp_unprotecttakes_tx_linux_mutexon entry and releases it twice when the protection structure names this core, but only once when it does not, while the matching_tx_thread_smp_protecttook it once either way._tx_thread_system_returnclears the protection outright, so an unprotect can find it no longer naming its core and return one release short._tx_linux_mutex_release_alldrains such a level, but the timer interrupt thread reaches one on its tick path only when a thread was preempted on core 0. A level leaked there while core 0 is idle is never recovered: the count never reaches zero,pthread_mutex_unlockis never called again, and every other thread waits on the mutex for the life of the process.The release now happens whether or not the protection still names this core. Bookkeeping and scheduling are unchanged, and over-releasing was already a no-op.
Two hung processes captured untraced, in different tests, show the timer thread owning the mutex with a nesting count of one while parked in its tick wait and the scheduler blocked in
pthread_mutex_timedlock, with the clock still advancing and every other value byte-identical across a ten second sample. On a healthy run that path is taken 0 times in 26,546 unprotect calls, and forcing it leaks one level before this change and balances after.No regression test ships with this: the trigger is a window inside the port's own critical-section primitive, which no ThreadX API can construct.