KDirWatch: fix memory leak on destruction.
ClosedPublic

Authored by dfaure on Feb 5 2017, 10:53 AM.

Details

Summary

The Entry class owns the Client instances, so it should delete the
remaining instances in its destructor, for the case where they haven't
been removed one by one. The line of code removeEntries(nullptr) was
probably means to remove them one by one, but it was a no-op (the code
for that method doesn't expect nullptr as argument) and it would be
slow anyway. We don't need to call inotify_remove for every path,
when we're just cleaning up in a global static after qApp destruction.

Detected by a clang-sanitizer build on http://ci-logs.kde.flaska.net
and reproduced locally with valgrind.

Test Plan

./kdirwatch_*_unittest now passes in valgrind without memory
leaks being reported

Diff Detail

Repository
R244 KCoreAddons
Branch
master
Lint
No Linters Available
Unit
No Unit Test Coverage
dfaure updated this revision to Diff 10926.Feb 5 2017, 10:53 AM
dfaure retitled this revision from to KDirWatch: fix memory leak on destruction..
dfaure updated this object.
dfaure edited the test plan for this revision. (Show Details)
dfaure added reviewers: aacid, mpyne.
dfaure added a subscriber: Frameworks.
Restricted Application added a project: Frameworks. · View Herald TranscriptFeb 5 2017, 10:53 AM
aacid accepted this revision.Feb 5 2017, 11:48 AM
aacid edited edge metadata.

Looks good to me, we could also go the crazy way and hold the data in m_clients instead of holding the ptr to the data
http://paste.ubuntu.com/23933091/

But I'm pretty sure i did some porting mistake in there :D

This revision is now accepted and ready to land.Feb 5 2017, 11:48 AM

It's not crazy, but

  • then it should use QVector instead of QList (Client is a "big" struct, bigger than a pointer)
  • I would be worried about copies happening unexpectedly (can this code compile with forbidden copy ctor for Client? I guess not as is due to insertion into the vector.... but maybe std::move can be used there, or simply setting the members directly onto a ref for vector[i]).
markg added a subscriber: markg.Feb 5 2017, 12:18 PM

Hmm, this is exactly the reason why i always go for either smart pointers or stack objects. Both prevent this issue from occurring in the first place.

It's probably a bit much to make the Entry class own the objects (basically the diff of Albert). But there i would be a bit worried about needless copies.
Which you can then prohibit by not allowing copies thus forcing move semantics, but then QList/QVector become unusable and you'd have to switch to std::vector. That is probably one step too far as well.

So, the last possible solution that might work is:
QList<QScopedPointer<Client>> m_clients;

But i don't know enough about the Qt smart pointers to say for sure if that works as intended (aka, no leaks and no needless copies).

dfaure added a comment.Feb 5 2017, 1:11 PM

QScopedPointer wouldn't work here (this isn't about a scope). std::unique_ptr would most certainly work, but then again, why use pointers where values can work. I like Albert's approach overall, with only the two concerns I listed.

aacid added a comment.Feb 5 2017, 7:36 PM
In D4439#83166, @dfaure wrote:

It's not crazy, but

  • then it should use QVector instead of QList (Client is a "big" struct, bigger than a pointer)

The problem with QVector is that it doesn't have erase(iterator) built in like QList has.

  • I would be worried about copies happening unexpectedly (can this code compile with forbidden copy ctor for Client? I guess not as is due to insertion into the vector.... but maybe std::move can be used there, or simply setting the members directly onto a ref for vector[i]).

Without the copy constructor there's quite a lot of things that don't work. OTOH all the data in Client is basiclaly POD, but i guess at some point it could be "a lot of copying", if you think it's worth it i can investigate some "less Q and more C++11-y stuff" and see if std::move or something works

markg added a comment.Feb 5 2017, 8:33 PM
In D4439#83310, @aacid wrote:
In D4439#83166, @dfaure wrote:

It's not crazy, but

  • then it should use QVector instead of QList (Client is a "big" struct, bigger than a pointer)

The problem with QVector is that it doesn't have erase(iterator) built in like QList has.

  • I would be worried about copies happening unexpectedly (can this code compile with forbidden copy ctor for Client? I guess not as is due to insertion into the vector.... but maybe std::move can be used there, or simply setting the members directly onto a ref for vector[i]).

Without the copy constructor there's quite a lot of things that don't work. OTOH all the data in Client is basiclaly POD, but i guess at some point it could be "a lot of copying", if you think it's worth it i can investigate some "less Q and more C++11-y stuff" and see if std::move or something works

std::move and std::vector will work :)
You "just" have to implement the move semantics for the Client class.
Follow this: http://en.cppreference.com/w/cpp/language/move_assignment
Look closely at the example and in there at "struct A" at these two snippets: (first one is the move constructor, second one is the move assignment operator)

A(A&& o) : s(std::move(o.s)) { }
A& operator=(A&& other)

Also, don't forget to explicitly delete the copy operations (in the public section of your class):
A(A const &) = delete;
void operator=(A const &x) = delete;

Good luck :)

dfaure added a comment.Feb 5 2017, 8:54 PM
In D4439#83310, @aacid wrote:
In D4439#83166, @dfaure wrote:

It's not crazy, but

  • then it should use QVector instead of QList (Client is a "big" struct, bigger than a pointer)

The problem with QVector is that it doesn't have erase(iterator) built in like QList has.

I see it in the docu:

iterator QVector::erase(iterator pos)
Removes the item pointed to by the iterator pos from the vector, and returns an iterator to the next item in the vector (which may be end()).

  • I would be worried about copies happening unexpectedly (can this code compile with forbidden copy ctor for Client? I guess not as is due to insertion into the vector.... but maybe std::move can be used there, or simply setting the members directly onto a ref for vector[i]).

Without the copy constructor there's quite a lot of things that don't work. OTOH all the data in Client is basiclaly POD, but i guess at some point it could be "a lot of copying", if you think it's worth it i can investigate some "less Q and more C++11-y stuff" and see if std::move or something works

If you fancy looking into it, go ahead. Otherwise tell me and I will.

aacid added a comment.Feb 5 2017, 9:41 PM
In D4439#83326, @dfaure wrote:
In D4439#83310, @aacid wrote:
In D4439#83166, @dfaure wrote:

It's not crazy, but

  • then it should use QVector instead of QList (Client is a "big" struct, bigger than a pointer)

The problem with QVector is that it doesn't have erase(iterator) built in like QList has.

I see it in the docu:

iterator QVector::erase(iterator pos)
Removes the item pointed to by the iterator pos from the vector, and returns an iterator to the next item in the vector (which may be end()).

  • I would be worried about copies happening unexpectedly (can this code compile with forbidden copy ctor for Client? I guess not as is due to insertion into the vector.... but maybe std::move can be used there, or simply setting the members directly onto a ref for vector[i]).

Without the copy constructor there's quite a lot of things that don't work. OTOH all the data in Client is basiclaly POD, but i guess at some point it could be "a lot of copying", if you think it's worth it i can investigate some "less Q and more C++11-y stuff" and see if std::move or something works

If you fancy looking into it, go ahead. Otherwise tell me and I will.

You take it, had a look and it's not as easy as i'd like, m_mapEntries makes it more difficult, and also the code kind of confuses me by m_entries containing pointers that seem to be extracted from the map itself? Maybe let's not touch this and just go with your simpler solution :D

mpyne accepted this revision.Feb 7 2017, 3:09 AM
mpyne edited edge metadata.

The diff as proposed is just fine. I've been bitten by qDeleteAll at proc exit before when called on objects that have complex destructors, but that's not the case here.

It would probably be a good idea to try to streamline the code structurally so that we don't have to hold pointers. But given the difficulty that it would entail, I think it would deserve either a separate Differential review, or to "upgrade" this review to focus on the structural change for its own sake, instead of just working on the memleak at process exit.

dfaure closed this revision.Feb 8 2017, 8:17 AM