[PyQt] QtWebEngine uses up all memory and will not give it back!
J Barchan
jnbarchan at gmail.com
Fri Oct 4 14:08:11 BST 2019
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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20191004/9591f64f/attachment.html>
More information about the PyQt
mailing list