Re: QtWebEngine uses up all memory and will not give it back!

J Barchan <[email protected]>
Newsgroups gmane.comp.python.pyqt-pykde
Message-ID <CABz3M_-pc=_qw_LigCsyNmAyQXf2QSDroeo8FHVXaKkyfBiQZw@mail.gmail.com>
Hi Kevin,

In your example script, one potential fix is to not give MyDialog a parent,
> so change "dlg = MyDialog(self)" to "dlg = MyDialog()".  That should let
> PyQt destroy the dialog as soon as the Python reference count drops to
> zero, which would happen with "del dlg", "dlg=None",
>

Tried this carefully.  Did not make any difference, nothing released as it
goes along.  I note that where I have

self.wev = QWebEngineView(self)

if I remove *that* self parameter I do get memory back.  So something all
to do with the QWebEngineView having the QDialog as parent is
relevant/problematic?  But that's not good for my code, as elsewhere I do
display the dialog with the webview on it.

If you need to parent the dialogs, another option is to use
> "sip.delete(dlg)" instead of "dlg.deleteLater()", which will trigger
> immediate deletion of the dialog instead of scheduling it for later.
>

Much better!  sip.delete(dlg) is the one thing which *does* result in the
memory being freed as it goes along, so even after doing hundreds I see
machine memory usage staying constant & acceptable!  Thank you :)

So is it perfectly safe to use that?  It seems to delete/reclaim the
webview which the dialog owns, so is all well or any there any lurking
"gotchas"?

Thanks,
Jonathan

On Sat, 5 Oct 2019 at 10:18, J Barchan <[email protected]> wrote:

> Wow, that's very useful, thank you so much for replying.  I shall try
> these out next week.  I am aware plain processEvents() won't do the
> "deferred deletes", indeed my problem is to try to find an alternative
> which does.  I tried the processEvents(event-type, timeout) overload
> which seems to say it handles these differently, but no improvement.  I
> tried destroy(), which from a previous post of mine I found from a year
> ago I seemed to have claimed resolved, but that was just worse!  If you
> would be kind, enough please keep an eye on this thread as I will report
> back, and if it's failure I should welcome any further thoughts!
>
> I did read up about processing events and event loops.  One thing I do not
> understand is *how* the deleteLater()s only take effect in the "main"
> loop, not elsewhere e.g. if I call my own explicit processEvents().  Is
> there a simple conceptual explanation of how/why?  For example, the
> existing app I am working is very heavily designed around having some kind
> of modal dialog (which may call other modals) up most of the time.  I
> believe I found from my testing that deleteLater()s do *not* get released
> during modal dialog event loop, only when return to "top" loop?  This is
> not so good if the program does not spend a lot of its time at that level!
>
> Meanwhile, an observation about this email-forum.  You have put your reply
> at the top of mine, e.g. just where Gmail puts you for *Reply*, so I have
> done the same time this time.  The very first time I replied in this
> message group I was *shouted at* immediately by someone saying that order
> of replying was unacceptable here, for people with other readers, and I
> must adhere to "reply at end".  I felt I nearly got banned!  However I have
> seen increasingly people reply as you have.  Does it matter any longer?  I
> don't want to offend anyone!
>
>
> On Fri, 4 Oct 2019 at 21:58, Kevin Keating <[email protected]>
> wrote:
>
>> processEvents() will process everything other than DeferredDelete events,
>> which is the type of event that gets created by deleteLater() calls.
>> That's intended (but confusing) behavior.  See
>> https://doc.qt.io/qt-5/qcoreapplication.html#processEvents.  In your
>> example script, one potential fix is to not give MyDialog a parent, so
>> change "dlg = MyDialog(self)" to "dlg = MyDialog()".  That should let PyQt
>> destroy the dialog as soon as the Python reference count drops to zero,
>> which would happen with "del dlg", "dlg=None", or on the next loop
>> iteration.
>>
>> If you need to parent the dialogs, another option is to use
>> "sip.delete(dlg)" instead of "dlg.deleteLater()", which will trigger
>> immediate deletion of the dialog instead of scheduling it for later.
>>
>> - Kevin
>>
>> On 10/4/2019 9:09:48 AM, J Barchan <[email protected]> wrote:
>> Hello Experts,
>>
>> I do not know whether this issue is PyQt-related, but it might be, as I
>> think it's to do with deleteLater() and *possibly* Python memory
>> management.  I should be so grateful if someone could take the time to look
>> through and advise me what to try next?
>>
>> Qt 5.12.2. Python/PyQt (though that does not help, it should not be the
>> issue). Tested under Linux, known from user to happen under Windows too. I
>> am in trouble, and I need help from someone who knows their QtWebEngine!
>>
>> Briefly: I have to create and delete QtWebEngines (for non-interactive
>> use, read below) a *large* number of times from a loop which cannot call
>> the *main* event loop. Every instance holds onto its memory allocation
>> --- which is "large" --- until code finally returns to *main* event
>> loop. I *cannot* find any way of getting Qt to release the memory being
>> use by QtWebEngine as it proceeds, *only* when return to main event
>> loop. Result is whole machine runs out of memory + swap space, until it
>> dies/freezes machine, requiring reboot!
>>
>>    -
>>
>>    In large body of code, QWebEngineView is employed in a QDialog.
>>    -
>>
>>    Sometimes that dialog is used interactively by user.
>>    -
>>
>>    But it is also used *non*-interactively in order to use its ability
>>    to print from HTML to PDF file.
>>    -
>>
>>    Code will do a non-interactive "batch run" of hundreds/thousands of
>>    pieces of HTML, exporting to PDF file(s).
>>    -
>>
>>    During this *large* amounts of memory will be gobbled by QtWebEngine.
>>    Of the order of hundreds of create/delete taking Gigabytes of machine
>>    memory. So much so that machine can even run out of all memory and die!
>>    -
>>
>>    *Only* a return to top-level, main Qt event loop allows that memory
>>    to be recouped. I *need* something better than that!
>>
>> I paste below about as minimal an example of code I am using in a test to
>> prove behaviour.
>>
>> import sys
>>
>> from PyQt5 import QtCore, QtWidgets
>> from PyQt5.QtWebEngineWidgets import QWebEngineView
>>
>> class MyDialog(QtWidgets.QDialog):
>>     def __init__(self, parent=None):
>>         super(MyDialog, self).__init__(parent)
>>
>>         self.wev = QWebEngineView(self)
>>
>>         self.renderLoop = QtCore.QEventLoop(self)
>>         self.rendered = False
>>         self.wev.loadFinished.connect(self.synchronousWebViewLoaded)
>>
>>         # set some HTML
>>         html = "<html><body>Hello World</body></html>"
>>         self.wev.setHtml(html)
>>
>>         # wait for the HTML to finish rendering asynchronously
>>         # see synchronousWebViewLoaded() below
>>         if not self.rendered:
>>             self.renderLoop.exec()
>>
>>         # here I would do the printing in real code
>>         # but it's not necessary to include this to show the memory problem
>>
>>     def synchronousWebViewLoaded(self, ok: bool):
>>         self.rendered = True
>>         # cause the self.renderLoop.exec() above to exit now
>>         self.renderLoop.quit()
>>
>> class MyMainWindow(QtWidgets.QMainWindow):
>>     def __init__(self, parent=None):
>>         super(MyMainWindow, self).__init__(parent)
>>
>>         self.btn = QtWidgets.QPushButton("Do Test", self)
>>         self.btn.clicked.connect(self.doTest)
>>
>>     def doTest(self):
>>         print("Started\n")
>>         # create & delete 500 non-interactive dialog instances
>>         for i in range(500):
>>             # create the dialog, it loads the HTML and waits till it has finished rendering
>>             dlg = MyDialog(self)
>>             # try desperately to get to delete the dialog/webengine to reclaim memory...
>>             dlg.deleteLater()
>>             # next lines do not help from Python
>>             # del dlg
>>             # dlg = None
>>         QtWidgets.QMessageBox.information(self, "Dismiss to return to main event loop", "At this point memory is still in use :(")
>>
>> if __name__ == '__main__':
>>     # -*- coding: utf-8 -*-
>>
>>     app = QtWidgets.QApplication(sys.argv)
>>
>>     mainWin = MyMainWindow()
>>     mainWin.show()
>>
>>     sys.exit(app.exec())
>>
>> I have tried various flavours of processEvents() in the loop after
>> deleteLater() but none releases the memory in use. *Only, only* when the
>> code returns to the top-level, main Qt event loop does it get released.
>> Which is too late.
>>
>> To monitor what is going on, under Linux I used
>>
>> watch -n 1 free mem
>> watch -n 1 ps -C QtWebEngineProc
>> top -o %MEM
>>
>> There are two areas of memory hogging:
>>
>>    - The process itself uses up considerable memory per QtWebEngine
>>    - It will run 26 (yes, precisely 26) QtWebEngineProc processes to
>>    service the requests, each also taking memory.
>>
>> Both of these disappear as & when return to Qt top-level event loop, so
>> we know the memory can & should be released. I do not know if this
>> behaviour is QtWebEngine specific.
>>
>> Anyone kind enough to answer will need to be specific about what to put
>> where to resolve or try out, as I say I have tried a lot of fiddling!
>> Unfortunately, advising to do the whole thing "a different way" (e.g. "do
>> not use QtWebEngineView", "rewrite code so it does not have to do hundreds
>> at a time", etc.) is really not what I am looking for, I need to understand
>> why I can't get it to release its memory as it is now? Can anyone make my
>> deleteLater() release its memory without going back to the top-level Qt
>> event loop??
>>
>> --
>> Kindest,
>> Jonathan
>> _______________________________________________ PyQt mailing list
>> [email protected]
>> https://www.riverbankcomputing.com/mailman/listinfo/pyqt
>>
>>
>
> --
> Kindest,
> Jonathan
>


-- 
Kindest,
Jonathan
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.