[import] Fix leaking AbstractFileFilters
ClosedPublic

Authored by croick on Nov 4 2018, 2:06 PM.

Details

Summary

Fix memory leaks due to recreation of AbstractFileFilter for each call
of ImportFileWidget::currentFileFilter(). The filter now is cached
as a member of ImportFileWidget and reused if the file type does not change.
A new method AbstractFileFilter::type() returns the type of the
current filter.

Test Plan
  • import different files
  • output (during debugging) the number of newly created filter instances

Diff Detail

Repository
R262 LabPlot
Lint
Automatic diff as part of commit; lint not applicable.
Unit
Automatic diff as part of commit; unit tests not applicable.
croick created this revision.Nov 4 2018, 2:06 PM
Restricted Application added a project: KDE Edu. · View Herald TranscriptNov 4 2018, 2:06 PM
Restricted Application added a subscriber: kde-edu. · View Herald Transcript
croick requested review of this revision.Nov 4 2018, 2:06 PM
croick updated this revision to Diff 45728.Nov 18 2018, 3:29 PM

Rebase to master

asemke requested changes to this revision.EditedNov 28 2018, 9:32 PM
asemke added a subscriber: asemke.

@croick thanks for addressing this. The memory leaks ImportFileWidget::currentFilter() are known for long time already but we didn't manage to clean up here yet... Thanks for addressing this now!

Couple of comments

  • QSharedPointer has additional overhead for reference counting which we don't need in this case. Can we use std::unique_ptr here?
  • The new function AbstractFileFilter::type() can be implemented in the base class only with
FileType AbstractFileFilter::type() {
    return m_type;
}

where m_type is a protected member in AbstractFileFilter which is initialized in the constructors of the different file filter classes. With this we don't need to implement this function in every filter class and to touch so many header files.

  • In LabPlot we very frequently use raw pointers as function parameters since the ownership and the life cycle of them is very clear. For objects that only read the content of such pointers there is no need to use smart pointers to transfer the ownership or to do the reference counting. updateContent() in the options widgets is also such a case where we don't need any ref-counting and where a raw pointer is totally fine.
This revision now requires changes to proceed.Nov 28 2018, 9:32 PM
croick updated this revision to Diff 46689.Dec 2 2018, 11:41 AM
  • set filter type in constructor, avoid function in derived classes
  • QSharedPointer has additional overhead for reference counting which we don't need in this case. Can we use std::unique_ptr here?

Whenever LiveDataSourcce::setFilter(currentFileFilter()) is called, we cannot know whether the filter object will exist, when it's used in a LiveDataSource.
For example, in MainWin::newLiveDataSourceActionTriggered() a filter is set by ImportFileWidget, which then is deleted as a child of ImportFileDialog:

	...
	else {
		LiveDataSource* dataSource = new LiveDataSource(i18n("Live data source%1", 1), false);
		dlg->importToLiveDataSource(dataSource, statusBar());
		this->addAspectToProject(dataSource);
	}
}
delete dlg;

So for the code as it is, I think reference counting is required. But sure, its use can be limited.

croick updated this revision to Diff 46698.Dec 2 2018, 12:47 PM
  • avoid copying of QSharedPointers if possible
  • QSharedPointer has additional overhead for reference counting which we don't need in this case. Can we use std::unique_ptr here?

Whenever LiveDataSourcce::setFilter(currentFileFilter()) is called, we cannot know whether the filter object will exist, when it's used in a LiveDataSource.

Usually there is always a filter which is created for the preview of the selected file. And when we call LiveDataSource::setFilter(currentFileFilter()) we create a new filter in currentFileFilter() anyway if no filter is available yet. So, there will always be an filter object when we close the dialog with Ok.

My point here is, there is actually never a moment where we have more than one reference to this filter - we either use it in ImportFileDialog or later in LiveDataSource. With this we can transfer the ownership when we close the import dialog/widget (use the raw point as "usuall" in LabPlot or make it more explicit via unique_ptr) and there is actually no need for ref-counting. But you have a valid point with the destruction of ImportFileWidget. When destroying this widget we cannot know whether the created filter is going to be used (Ok button clicked vs. Cancel button clicked) without adding additional logic. You propose to use the shared pointer to implement this logic. This is ok, I think. The only not so nice thing about this is that we "pollute" LiveDataSource and MQTTClient because of some internal details of ImportFileWidget... So, if you don't have any other ideas here, submitt your patch. Please also check my minor comment in the code.

src/kdefrontend/datasources/ImportFileWidget.h
102

m_currentFilter instead of currentFilter for the private member? Why mutable here?

croick updated this revision to Diff 46877.Dec 4 2018, 11:57 PM
croick retitled this revision from [import] Use QSharedPointer for file filters to [import] Fix leaking AbstractFileFilters.
croick edited the summary of this revision. (Show Details)
  • rename currentFilter to m_currentFilter and don't use QSharedPointer at all
croick marked an inline comment as done.Dec 5 2018, 12:19 AM

Thanks for looking into this!
I think you have a point about consistency with raw pointers. If we make sure that any use of setFilter(filter) is followed by releasing ownership of the filter, then that should be fine.
I will add a line in the function documentation to make it clear.

Changes to MQTT I will submit in a separate commit and remove some whitespace-only changes that I just discovered.

Would you be fine with this solution?

src/kdefrontend/datasources/ImportFileWidget.cpp
98–99

Will do this in a separate commit

src/kdefrontend/datasources/ImportFileWidget.h
102

currentFileFilter() is marked const, but m_currentFilter is changed therein.

asemke added inline comments.Dec 5 2018, 7:24 AM
src/backend/datasources/LiveDataSource.cpp
252

this needs only be done if m_filter != nulltpr,

src/backend/datasources/MQTTClient.cpp
104 ↗(On Diff #46877)

same here. let's keep this safety check if (m_filter) for the case when MQTTClient is initialized and destroyed without the filter being set in-between.

153 ↗(On Diff #46877)

same here.

src/kdefrontend/datasources/ImportFileWidget.cpp
595–597

I think the following would also to the job completely without any smart pointers and static casts:

if (m_currentFilter)
    delete m_currentFilter;

auto filter = new AsciiFilter();
filter->set*();
m_currentFilter = filter;
src/kdefrontend/datasources/ImportFileWidget.h
102

Ok.

croick marked an inline comment as done.Dec 5 2018, 11:16 PM
croick added inline comments.
src/backend/datasources/LiveDataSource.cpp
252

Actually nothing happens when deleting a nullptr (https://en.cppreference.com/w/cpp/language/delete). The check would be redundant. For newer versions of C++ this assumption seems to change a little, in case a non-standard deallocator is used – which we don't, the standard deallocator just does nothing with a nullptr.

src/kdefrontend/datasources/ImportFileWidget.cpp
595–597

The point of keeping the filter is not just a correct cleanup, but also to omit reparsing the file that is to be imported. currentFileFilter() gets called a lot and makes the UI unresponsive otherwise. (Which was my initial reason for this patch.)
I could switch to a raw pointer still, but that would be inconsistent with the pointers for the options widgets.

croick updated this revision to Diff 46925.Dec 5 2018, 11:21 PM
  • revert whitespace changes, add a comment to setFilter()
asemke added inline comments.Dec 7 2018, 8:12 AM
src/kdefrontend/datasources/ImportFileWidget.cpp
595–597

Right, I overlooked this important part with omitting the reparsing of the file. But also this can be done with the raw pointers. Which inconsistency with the options widgets do you mean?

asemke added inline comments.Dec 7 2018, 8:24 AM
src/backend/datasources/LiveDataSource.cpp
252

According to the c++-11 standard (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf, 5.3.5/7), it's an unspecified behavior for the null pointer case.

croick added inline comments.Dec 7 2018, 3:37 PM
src/backend/datasources/LiveDataSource.cpp
252

Which means, that operator delete(void* ptr) either is called or not. And if it is called, then 18.6.1.1/14 applies, which states, that it does nothing if ptr is a null pointer.

src/kdefrontend/datasources/ImportFileWidget.cpp
595–597

They are all unique_ptrs and there is no explicit delete in ~ImportFilterWidget().

asemke accepted this revision.Dec 8 2018, 4:42 PM
asemke added inline comments.
src/backend/datasources/LiveDataSource.cpp
252

It's never too late to learn.. Thanks.

src/kdefrontend/datasources/ImportFileWidget.cpp
595–597

I see. Those unique pointers introduced already some inconsistency when compared to the other code in LabPlot :-) But ok. Let's go for this solution.

This revision is now accepted and ready to land.Dec 8 2018, 4:42 PM
This revision was automatically updated to reflect the committed changes.