mainthread: avoid deadlock when Wait is called from the main thread#333
Merged
Conversation
mainthread.Wait queues the callback onto the Qt main thread's event loop and blocks on a sync.WaitGroup. If the caller is already the main thread, it blocks itself: the queued event never runs, and Wait hangs forever. This can happen when a function that uses Wait is reachable from both goroutines and Qt slots (e.g. a helper called both from a worker and from a button-click handler). The caller often can't easily know which thread they're on. Fix: check whether the current thread is the Qt main thread, and if so, invoke the callback directly. The check is exposed as a new IsCurrent() helper for users who want to make the same decision in their own code. The check is implemented in C++ using QThread::currentThread() against QCoreApplication::instance()->thread() — the standard Qt idiom. Both qt5 and qt6 mainthread packages get the same fix.
Owner
|
LGTM 🚢 Thanks again for the nice contribution! I remember another user has definitely encountered this before, but in that case they switched to I think it would be possible to implement |
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.
mainthread: avoid deadlock when
Waitis called from the main threadProblem
mainthread.Waitqueues the callback onto the Qt main thread's event loop and blocks on async.WaitGroup. If the caller is already the main thread, it blocks itself: the queued event never runs (the main thread is stuck inwg.Wait()and never returns to its event loop), andWaithangs forever.This is easy to hit in practice. Consider a helper function that touches GUI state:
If
updateLabelis called from a background goroutine, this works correctly. If the same helper is later reused from a Qt slot (e.g. a button-click handler) — perhaps via several layers of indirection — the app silently deadlocks. Callers often can't easily know which thread they're on, especially in deep call chains.Reproduction
Click the button → app hangs.
Fix
Check whether the current thread is the Qt main thread, and if so, invoke the callback directly without going through the event loop.
The check is implemented in C++ using
QThread::currentThread() == QCoreApplication::instance()->thread()— the standard Qt idiom — and exposed to Go asmainthread.IsCurrent().After the fix, the example above works: clicking the button updates its label, because
Waitnotices it's already on the main thread and calls the function directly.Public API
A new
mainthread.IsCurrent() boolis added for users who want to make the same decision in their own code (e.g. picking between sync and async paths, or asserting thread invariants).Performance
IsCurrent()adds one CGo call + aQThread::currentThread()lookup + a pointer comparison to everyWait. In context this is negligible:Waitalready does a CGo call, aQMetaObject::invokeMethodqueued dispatch (event allocation, event-loop mutex, append, futex wait/wake, context switch through the event loop, second CGo bridge for the callback). The extra check is well under 1% of the existing cost.The only scenario where the overhead would matter is calling
Waitfrom a tight loop on a goroutine — but that's misuse (you'd batch instead).Scope
Both
qt/mainthreadandqt6/mainthreadpackages get the same fix.