Always delay stage 2 shutdown, not only with prompts

If we shut down qutebrowser directly, in some rare situations, we seem
to get into some trouble related to a QWidget trying to call into a
Q(Common|StyleSheet)Style at shutdown (checking the style hint
SH_Widget_ShareActivation, to figure out the palette to use when the
window is hidden, or somesuch?).

With the recent changes for #7504, those crashes suddenly started
happening much more often. After staring at the related changes and gdb
for over a day, I still have no idea what actually causes them and why
it's crashing - it smells like some kind of PyQt/Qt cleanup order bug...

However, what I *did* find out (after a lot of painful debugging and
messing around with things) is that delaying the shutdown done from
:quit seems to reliably get rid of the issue. I don't know *why*, but
I haven't seen it happen anymore with the latest changes...

Fixes #5385 and #5124, both speculatively.

Closes #7504 since this was the last change required to make it all
work.
This commit is contained in:
Florian Bruhin 2022-12-06 18:44:01 +01:00
parent 7fb15312d0
commit 6373959824
2 changed files with 15 additions and 26 deletions

View File

@ -131,20 +131,12 @@ class PromptQueue(QObject):
"""Cancel all blocking questions.
Quits and removes all running event loops.
Return:
True if loops needed to be aborted,
False otherwise.
"""
log.prompt.debug("Shutting down with loops {}".format(self._loops))
log.prompt.debug(f"Shutting down with loops {self._loops}")
self._shutting_down = True
if self._loops:
for loop in self._loops:
loop.quit()
loop.deleteLater()
return True
else:
return False
for loop in self._loops:
loop.quit()
loop.deleteLater()
@pyqtSlot(usertypes.Question, bool)
def ask_question(self, question, blocking):

View File

@ -221,21 +221,18 @@ class Quitter(QObject):
status, session))
sessions.shutdown(session, last_window=last_window)
prompt.prompt_queue.shutdown()
if prompt.prompt_queue.shutdown():
# If shutdown was called while we were asking a question, we're in
# a still sub-eventloop (which gets quit now) and not in the main
# one.
# This means we need to defer the real shutdown to when we're back
# in the real main event loop, or we'll get a segfault.
log.destroy.debug("Deferring real shutdown because question was "
"active.")
QTimer.singleShot(0, functools.partial(self._shutdown_2, status,
is_restart=is_restart))
else:
# If we have no questions to shut down, we are already in the real
# event loop, so we can shut down immediately.
self._shutdown_2(status, is_restart=is_restart)
# If shutdown was called while we were asking a question, we're in
# a still sub-eventloop (which gets quit now) and not in the main
# one.
# But there's also other situations where it's problematic to shut down
# immediately (e.g. when we're just starting up).
# This means we need to defer the real shutdown to when we're back
# in the real main event loop, or we'll get a segfault.
log.destroy.debug("Deferring shutdown stage 2")
QTimer.singleShot(
0, functools.partial(self._shutdown_2, status, is_restart=is_restart))
def _shutdown_2(self, status: int, is_restart: bool) -> None:
"""Second stage of shutdown."""