[konsole]: proper fix for a crash-on-exit
AcceptedPublic

Authored by rjvbb on Sep 5 2018, 2:11 PM.

Details

Reviewers
hindenburg
sandsmark
Group Reviewers
Konsole
Summary

An old commit (dd1b2b4d) addressed a crash-on-exit (not well understood) by reverting to the old-style signal/slot connection syntax.

In practice, this prevented simply because the slot was never called, something I got wind when after installed the current tip of the master branch I started seeing this:

QObject::connect: No such signal Konsole::TabbedViewContainer::destroyed(TabbedViewContainer*)

Moving back to the new style syntax (after changing the signature of ViewSplitter::containerDestroyed to avoid having to use a complicated cast in the connect() call) the crash was indeed there anew because containerDestroyed was being called again.
Analysing the backtrace showed that the containerDestroyed slot was called under the QSplitter::~QSplitter dtor, while deleting the splitter's child widgets.

In other words, the crash (inside QList::removeAll) seemed due to removing an item from a stale QList object. Vieplitter::containerDestroyed only serves to unregister a TabbedViewContainer that is about to be deleted; indeed, unregistering all registered containers in the new ViewSplitter::~ViewSplitter dtor fixes the crash.

My patch reverts both connections in ViewSplitter::registerContainer() to the new-style connection syntax.

Test Plan

Without the patch: no crash on exit because TabbedViewContainers aren't being unregistered at all
With the patch: no crash on exit because they are unregistered while that is still possible.

I have not been able to test runtime invocation of ViewSplitter::containerDestroyed. I did notice that the qobject_cast in the patched slot fails if I do NOT emit the destroyed signal explicitly from the TabbedViewContainer dtor.

I do wonder about the remainder of the containerDestroyed function: will count() already be 0 when the last container is *about* to be deleted?

Diff Detail

Lint
Lint Skipped
Unit
Unit Tests Skipped
Build Status
Buildable 2532
Build 2550: arc lint + arc unit
rjvbb created this revision.Sep 5 2018, 2:11 PM
Restricted Application added a subscriber: konsole-devel. · View Herald TranscriptSep 5 2018, 2:11 PM
rjvbb requested review of this revision.Sep 5 2018, 2:11 PM
rjvbb updated this revision to Diff 41053.Sep 5 2018, 2:12 PM

No need to specify Qt::DirectConnection explicitly.

rjvbb set the repository for this revision to R319 Konsole.Sep 5 2018, 2:13 PM

I belive that the containerDestroyed shouldn't exist as there's also a containerEmpty method that will be called.
I belive both functions could be merged.

rjvbb added a comment.Sep 5 2018, 2:46 PM

I think these do different things; containerEmpty is called when a container (Konsole window, presumably) closed its last tab but doesn't unregister the container. I'd guess that containerEmpty will be called first, and then containerDestroyed when the container is being disposed off (because it's empty).
There may not be an explicit current need to handle these separately, but I don't see why one should NOT do that.

Note that handling them separately will be necessary if container (Konsole window) is deleted via deleteLater() or otherwise not immediately after closing the last tab.

I think these do different things; containerEmpty is called when a container (Konsole window, presumably) closed its last tab but doesn't unregister the container. I'd guess that containerEmpty will be called first, and then containerDestroyed when the container is being disposed off (because it's empty).
There may not be an explicit current need to handle these separately, but I don't see why one should NOT do that.

Note that handling them separately will be necessary if container (Konsole window) is deleted via deleteLater() or otherwise not immediately after closing the last tab.

It will not need to be handled differently, if in the containerEmpty() you do everything that's needed for the cleanup.
I say that because in one of my konsole branches I removed that and it indeed worked.
but I had so many stale konsole branches that I removed a few on a cleanup.

rjvbb added a comment.Sep 5 2018, 3:07 PM
I say that because in one of my konsole branches I removed that and it indeed worked.

"it worked" to prevent crashing on exit? If so your approach would be an alternative fix.

But it really depends on whether or not containers are deleted immediately after the last tab is closed and/or on whether they need to remain registered until the last moment. That's something that the people working on tab/window management can say (they should also have an idea how this might evolve).

As long as I'm not 100% certain of this aspect of the design I prefer to consider merging the functions as a separate change.

It looks good fix to me, on containerEmpty should not unregister it, *logically*, furthermore it can be destroyed when it's not empty, it should be connected destroyed to containerEmpty too.

rjvbb added a comment.Sep 5 2018, 9:11 PM

it should be connected destroyed to containerEmpty too.

I'm not sure I see why TabbedViewContainer::destroyed should be connected to SplitterView::containerEmpty; I think that would lead to calling containerEmpty too often.

What I can imagine is this: in containerDestroyed, check if the container being deleted is empty. If not, call containerEmpty directly *after* removing the container from _containers.

However, I notice these warnings when I close a Konsole window with multiple tabs open (here, 3):

shell did not close, sending SIGHUP
shell did not close, sending SIGHUP
shell did not close, sending SIGHUP

That could mean that the TabbedViewContainer dtor empties the container, and that could well mean that the empty signal is sent and containerEmpty is thus called?

gateau removed a reviewer: gateau.Sep 10 2018, 8:06 PM
sandsmark added inline comments.
src/ViewSplitter.cpp
46

why a foreach instead of a normal for (foo : bar) {}?

and I'm a bit allergic to auto, I don't know what container is without opening viewsplitter.h. but that's a personal preference.

src/ViewSplitter.h
185

change this to a QPointer while you're at it, and maybe add some null checks, so we can avoid similar issues in the future.

rjvbb added a comment.Oct 18 2018, 9:05 AM

Sorry for the delay; I thought this would be straightforward and then ran into questions...

src/ViewSplitter.cpp
46

I simply haven't adapted yet to the fact that there's now a "normal for" equivalent of Qt's equally "normal foreach"...

If you prefer I can use an explicit type but I think this is as good a place to be lazy as any. Besides, in any self-respecting IDE you can hover the mouse cursor over either the variable or unregisterContainer() and a popup will tell you what type the variable (must) be ;)

src/ViewSplitter.h
185

I'm not certain I get this. Put _containers in a QPointer or make it a QList< QPointer<TabbedViewContainer *> >?

The former is impossible AFAIK (QList doesn't inherit QObject), while the latter isn't relevant because the crash was caused by accessing a stale _containers instance (i.e. a member variable of a stale ViewSplitter instance).

sandsmark accepted this revision.Feb 8 2019, 5:18 PM

The issues are just cosmetic, didn't intend to block this.

But Hindenburg should probably give a go-ahead as well.

src/ViewSplitter.cpp
46

I don't always use an IDE when reading code, and I (almost) never use the mouse when writing code. But again, it's my own personal preference, Hindenburg uses auto a lot all over the place.

And looking around, seems like foreach isn't used extensively either, so they all can probably be converted automatically en masse later.

src/ViewSplitter.h
185

wasn't thinking about it being an issue now, but rather in the future.

a QPointer here shouldn't impact performance anyways.

This revision is now accepted and ready to land.Feb 8 2019, 5:18 PM