PDA

View Full Version : Qt deleteLater() force to delete



axe
18th August 2011, 09:50
Hi all,

I'm porting application from older version of Qt to 4.7.2 and I found one problem there.
deleteLater() function is supposed to schedule object for deletion and object should be deleted when control next time enters to the event processing loop, but it doesn't work for me.
Here is the code


test *t = new test();
t->show();
std::cout << "before deleteLater()" << std::endl;
t->deleteLater();
std::cout << "after deleteLater()" << std::endl;
QCoreApplication::sendPostedEvents();
QCoreApplication::processEvents();
std::cout << "after processEvents()" << std::endl;

Class test is derived from QDialog. It prints test() in constructor and ~test() in destructor.

This code gives the following output

test()
before deleteLater()
after deleteLater()
after processEvents()
~test()

According to Qt documentation it should delete the object before last cout, am I right? Looks like a bug in Qt, does anybody know anything about it? Any workaround?

Of course I could do manual delete, but application is huge and it would require too much changes, I wouldn't like to change too much things as it may cause another problems.

Thanks.

wysota
18th August 2011, 10:04
Calling processEvents() doesn't trigger deffered deletion. The application really needs to enter the event loop.

axe
18th August 2011, 10:05
How can I force it? Is there any way?
Here is the problem:
There is application that handles text commands. I have a Qt widget that is closed with some close * command. Qt::WA_DeleteOnClose attribute is set for that widget, it receives closeEvent, but destructor for that object is called later (I guess on idle).
If I have two commands
close *;
get something;
the program crashes because "get something" is called before destructor for that widget, so widget still receives events and it tries to access data deleted by close * command. I would like to force Qt to delete all pending objects after command completion.

I guess previously it was entering event processing loop, but something is changed.

Any ideas how to make it work?
Thanks.

wysota
18th August 2011, 10:46
How can I force it?
Why would you want to force it?


the program crashes because "get something" is called before destructor for that widget, so widget still receives events and it tries to access data deleted by close * command. I would like to force Qt to delete all pending objects after command completion.
Then either schedule execution of "get something" and return to the event loop or simply have the closeEvent for the widget (or some other appropriate place) contain code that will set a flag that is checked before the widget tries to access this data. Or use one of available smart pointer classes.

axe
18th August 2011, 11:16
wysota

The problem appeared after updating Qt version from 4.3.3 to 4.7.2. I wouldn't like making such changes, the code is really huge and every simple change may result in thousands of bugs. I would like to fix the problem with minimum amount of changes. I guess previously Qt was forced to delete the object by calling sendPostedEvents() (at least comment says that), but currently this doesn't work.

Here is the code


// Send queued events (including deferred deletions) to all QObjects
// unless we have to preserve view objects
if (deleteImmediately)
QCoreApplication::sendPostedEvents(NULL, -1);


This code looks strange to me, however this is what we have and this is what was working before.
Do you mean there is no way to force it?

axe
18th August 2011, 15:34
I have code that deletes deferred object by calling sendPostedEvents for Qt 4.3.3, but this code doesn't work for Qt 4.7.2. Also I tried to make a simple application that works and I couldn't make it work even with Qt 4.3.3, sendPostedEvents doesn't call any destructors. It's difficult to track everything what is going on in original application before it calls sendPostedEvents and deletes deferred object, does anybody know anything about it? In what situations it can invoke deferred delete? Any useful information that will help me to debug?
Can it depend on some project options or predefined macros? Is there any known bug? Anything...

Added after 1 40 minutes:

Looks like sendPostedEvents delete deferred objects only if it's not called from Qt event handler. Our events are coming from Tcl, so they are not called from Qt main event loop, seems that this is the reason that it works, but here is another problem.
This code


Dialog::~Dialog() {
std::cout << "~test()" << std::endl;
}

int main(int argc, char* argv[]) {
QApplication app(argc, argv);
Dialog* dlg = new Dialog();
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->show();
dlg->close();
std::cout << "before sendPostedEvents()" << std::endl;
QCoreApplication::sendPostedEvents();
std::cout << "after sendPostedEvents()" << std::endl;
return app.exec();
}

prints

before sendPostedEvents()
after sendPostedEvents()
~test()

but as soon as I add closeEvent handler and call deleteLater() in that handler function sendPostedEvents starts deleting deferred objects.

void Dialog::closeEvent(QCloseEvent* ev) {
deleteLater();
QWidget::closeEvent(ev);
}


before sendPostedEvents()
~test()
after sendPostedEvents()

Can anybody explain what the hell is going on there? I have no idea what's that. Is it just a bug? Can I use that workaround?
How does this work? Shouldn't Qt call deleteLater() automatically, after closeEvent is accepted if CloseOnDelete attribute is set?

wysota
18th August 2011, 15:52
No, it's not a bug, I think I remember this change being introduced or at least mentioned. You are abusing deleteLater() for something it wasn't meant and hence you can't expect sane response from Qt.

axe
18th August 2011, 20:09
OK. Let me go deeper into details.
There is a GUI application that handles Tcl commands. Tcl commands are handled by library written by another team, so we can't touch that. If you execute one command then library will return control to Qt event loop, but if you execute more then one command then library will return control to the main loop only when all commands are finished. Now, there is command that closes some views, and those views are receiving events (Qt events). Those views have DeleteOnClose attribute set, and after close command is executed those views are closed and closeEvent() virtual function is executed immediately, but the object is not deleted yet. The problem is that this view continues to receive Qt signals and it crashes, because some references are already deleted. Also those behaviour was causing memory issues, because if you run big script it will not clean the memory untill it will not finish the job, that is a problem and I see someone fixed this issue by adding sendPostedEvents() function call after close. Now Qt version is changed and it doesn't work anymore, what solutions do I see?
1. Disconnect all slots from widget in closeEvent function. This will solve the problem, but as destructor call for this widget is deferred, this will return us to our old memroy issue. So it's not complete solution.
2. Add deleteLater() call as described in my previous post. This will solve the problem again, it will solve the problem with memory too, but this looks unstable to me, I'm not kind of person who likes solution like that. I don't see any gurantee from Qt.
3. Remove DeleteOnClose attribute and do manual delete. In some situations it is required to leave the object alive after closing it, because it contains some data that is required by post-callbacks. If I switch to manual delete then I wouldn't be able to defer deletion of the object (I mean I wouldn't be able to do that without changing megabytes of source code).

So the best solution for me would be to find a portable way to force Qt to delete deferred objects, as it was done before (not portably).
Any suggestions?

wysota
18th August 2011, 20:30
Can't you just call "delete" on the object instead of using deleteLater()?

axe
18th August 2011, 20:38
Well, there is no deleteLater() call at all, deleteLater() is called automatically by closeEvent when DeleteOnClose attribute is set as far as I understood from Qt doc.
Actually I can't delete that simple. There is boolean flag named immediateDelete, if it is set then sendPostedEvents is called and in older Qt version it does delete the deferred object, so everything works fine. I can replace this function call with manual deletion, but I'm not sure what will happen if Qt will try to delete this deferred object. If I remove DeleteOnClose attribute then I would need to delete all objects manually, but the code is so huge that it's really impossible to do not forget about anything. I wouldn't like making such changes.

wysota
18th August 2011, 20:42
If not, then you need to rewrite a piece of code --- perform deletion (using deleteLater), schedule the next instruction (e.g. using invokeMethod() or a 0-timeout timer) and return to the event loop. You can have a look at Keeping the GUI Responsive article to learn some details.

axe
18th August 2011, 20:53
But how can I return to the main loop? This 3rd party library doesn't return control to the main loop, untill all commands are not processed. If sendPostedEvents doesn't return control to the main loop as well then how can I do that?

wysota
18th August 2011, 21:00
I don't have definite answers for your problems. Maybe you need to dig deeper into how the library works or maybe you need to replace it with a better solution. A TCL interpreter is publically available, maybe it's easier to use it instead of the offending library. Or file a bug report to the library maintainers.

axe
18th August 2011, 21:05
Am I getting you right?
1. Qt deletes deferred objects ONLY when it returns to main loop.
2. The only way to return to the main loop in Qt is to return from function that is called from that main loop.
Based on those two points we can't force Qt to delete deferred objects at any moment.

Then one more question, what if I manually delete deferred object? Does Qt delete it from deferred list or it may try to delete it as well?

wysota
19th August 2011, 00:03
1. Qt deletes deferred objects ONLY when it returns to main loop.
That's the point of deferred deletion -- make sure nobody is using the object anymore (which means all events are delivered and all signals processed). See the quote below.


2. The only way to return to the main loop in Qt is to return from function that is called from that main loop.
Based on those two points we can't force Qt to delete deferred objects at any moment.
You could try the obsolete processEvents(QEventLoop::DeferredDeletion) but I suspect it will fail. The docs say:

Note that entering and leaving a new event loop (e.g., by opening a modal dialog) will not perform the deferred deletion; for the object to be deleted, the control must return to the event loop from which deleteLater() was called.

I can only explain that Qt keeps an internal count of nesting event loop calls so the numbers have to match here (thus cheating by re-entering the same event loop will not work and in addition you'll get a warning or even a crash).


Then one more question, what if I manually delete deferred object? Does Qt delete it from deferred list or it may try to delete it as well?
You can do that. Qt will remove deferred deletion from the event queue (along with all pending events for the object).

Santosh Reddy
19th August 2011, 01:23
May be I didn't get the complete picture yet, if you want to delete immediately, can't you just call delete on the object directly, instead of calling deleteLater() on the object.

stampede
19th August 2011, 08:18
May be I didn't get the complete picture yet, if you want to delete immediately, can't you just call delete on the object directly, instead of calling deleteLater() on the object.
(few posts above)

I can replace this function call with manual deletion, but I'm not sure what will happen if Qt will try to delete this deferred object. If I remove DeleteOnClose attribute then I would need to delete all objects manually, but the code is so huge that it's really impossible to do not forget about anything. I wouldn't like making such changes.

axe
19th August 2011, 08:22
You could try the obsolete processEvents(QEventLoop::DeferredDeletion) but I suspect it will fail. The docs say:

It works, but is it guaranteed to work? Actually I agree, documentation says that it shouldn't, but on the other hand this is what it is supposed to do. Anyway, it is obsolete function and not recommended for use, I guess it will be removed in next Qt versions. So despite the fact that it works it's better to do not use QEventLoop::DeferredDeletion.

What remains? Looks like the only reasonable solution is manual deletion in this situation.

stampede
19th August 2011, 08:45
Anyway, it is obsolete function and not recommended for use, I guess it will be removed in next Qt versions. So despite the fact that it works it's better to do not use QEventLoop:eferredDeletion.
I would not rely on "not recommended" methods too, so I guess you've reached a point when you won't escape some refactoring (implement manual deletion, use of safe pointers for shared data etc).

wysota
19th August 2011, 08:46
It won't be removed until the end of days of Qt4. But it's obsolete and potentially dangerous. If you want, you can make your own mechanism for deferred deletion, it's not a big deal.

axe
19th August 2011, 09:08
I would not rely on "not recommended" methods too, so I guess you've reached a point when you won't escape some refactoring (implement manual deletion, use of safe pointers for shared data etc).
Me too. Actually manual deletion here solves the problem, I am just not sure about double delete. Can't find anything about that in Qt doc. Is it really guaranteed that Qt will not try to delete deferred object again?

wysota
19th August 2011, 09:45
Yes, it is guaranteed.

axe
19th August 2011, 09:49
Is it mentioned somewhere in docs? I wasn't able to find it.
I don't mean that I don't believe you, just want to put the link in comments :)

wysota
19th August 2011, 09:53
Is it mentioned somewhere in docs?
Feel free to look for it. There is a nice clause in QObject destructor docs.


Destroys the object, deleting all its child objects.
All signals to and from the object are automatically disconnected, and any pending posted events for the object are removed from the event queue.

axe
19th August 2011, 10:01
Thank very much for helpful posts!

Added after 4 minutes:

I didn't get this

All signals to and from the object are automatically disconnected, and any pending posted events for the object are removed from the event queue

Deleting a QObject while pending events are waiting to be delivered can cause a crash.
how should I understand that?

wysota
19th August 2011, 10:14
It's a direct cause of why deleteLater() doesn't work for you :) I guess a posted event is one that has been posted "just now", a pending event is one that is already scheduled for delivery. But it could as well be a messup in the docs.

axe
19th August 2011, 10:27
Well, now I'm confused even more.
Qt provides some sort of garbage collection, it doesn't recommend to delete QObjects manually and it doesn't provide mechanism to invoke garbage collection whenever I need it. So how should I cleanup the memory if I don't control Qt main loop?
The only way to cleanup the memory as soon as I need it is the obsolete function. Am I right?

Just to clarify once again:
There is 3rd party library that connects Tcl shell to Qt, this library doesn't return control to the main loop after each command, if commands are executed as one script.

wysota
19th August 2011, 11:15
Qt doesn't provide any garbage collection. It only assures you that when an object is deleted, it takes its children with it. Deleting QObject explicitly is not advised because it is assumed you're not the only one using the object and you could mess some things up. But when deleting it, Qt tries to clean everything up. Signals across threads are most problematic, even deleteLater() won't help here in some cases.

axe
19th August 2011, 13:04
The object is not shared between multiple threads, does it mean I can safely delete the object manually? What about pending events?

Found in Qt mailing list topic about replacement for obsolete processEvents(DeferredDelete) function. Someone recommended to use QApplication::sendPostedEvents(NULL, QEvent::DeferredDelete) instead and it works. Looks like the problem is fixed.
If nobody knows any problem related to this solution then looks like the problem is solved.
Thanks to everybody.