Memory leak when using PyQt with OpenNMT CTranslate

Admin Argos Open Technologies, LLC admin at argosopentech.com
Sat Oct 31 14:04:15 GMT 2020


Hi,

Thanks for the suggestion. It does seem like making the GUIWindow the
parent of the WorkerThreads is correct and that's what they do in Qt's
QThread documentation. I also made a change to connect the QThread's
finished signal to its deleteLater slot which is what is done in the
documentation's example for subclassing QThread. However, neither of these
changes fixed the problem and this example is still leaking memory:

from PyQt5.QtWidgets import QMainWindow, QApplicationfrom PyQt5.QtCore
import QThreadimport ctranslate2
class WorkerThread(QThread):
    def run(self):
        translator = ctranslate2.Translator('/path/to/ctranslate/model')
class GUIWindow(QMainWindow):
    def translate(self):
        new_worker_thread = WorkerThread(self)
        new_worker_thread.finished.connect(new_worker_thread.deleteLater)
        new_worker_thread.start()

app = QApplication([])
main_window = GUIWindow()
main_window.show()


I also posted on the OpenNMT forum and the author seemed to thing it wasn't
an OpenNMT issue:
https://forum.opennmt.net/t/ctranslate-leaks-memory-when-run-from-a-pyqt-thread/4009/4

Thanks,

P.J.

On Fri, Oct 30, 2020 at 4:02 AM Phil Thompson <phil at riverbankcomputing.com>
wrote:

> On 29/10/2020 22:21, Admin Argos Open Technologies, LLC wrote:
> > Hi,
> >
> > Sorry for the double post, but it was suggested I repost the full bug
> > report not just link to the Qt forum.
> >
> > I have a PyQt application with a memory leak in it
> > <https://github.com/argosopentech/argos-translate>. It's an application
> > for
> > doing translations and it uses QThreads and signal/slots to do the
> > translation on a separate thread and then signal a QTextEdit with the
> > result when the translation finishes. The application works but it
> > leaks
> > memory with every translation.
> >
> > <https://forum.qt.io/post/624628> I've been able to narrow the problem
> > down
> > to occurring when I create a CTranslate Translator inside of a PyQt
> > QThread
> > that is created from a QWidget. If I remove the CTranslate Translator
> > and
> > do something else that allocates a large amount of memory there is no
> > leak.
> > If I create the CTranslate Translator from the QWidget with no QThread
> > there is also no leak. If I run the QThread outside of a QWidget there
> > is
> > no leak. The leak only happens with the combo of all three.
> >
> > My best guess is that there is some bug/me misusing in the combination
> > of
> > Python/Qt/CTranslate memory management. Python uses automatic reference
> > counting memory management, while Qt in native C++ use C++ parent based
> > memory management. On top of that CTranslate uses C++ extensions to
> > Python
> > so it seems like there are a lot of places where the problem could be
> > appearing.
> >
> > I made an example script demonstrating the leak
> > <https://gist.github.com/argosopentech/326008095f2cd726d9567e171fcf3842
> >.
> > To run it you need a CTranslate model and need to provide a path to it
> > in
> > the script. Here's a Google Drive link
> > <
> https://drive.google.com/drive/folders/11wxM3Ze7NCgOk_tdtRjwet10DmtvFu3i>
> > where you can download a package for my project that if extracted (its
> > just
> > a renamed .zip archive) has a CTranslate model at /model. When this
> > script
> > runs it leaks ~5GB of memory.
> >
> >
> > from PyQt5.QtWidgets import QMainWindow, QApplication
> > from PyQt5.QtCore import QThread
> > import ctranslate2
> >
> > class WorkerThread(QThread):
> >      def __del__(self):
> >          self.wait()
> >
> >      def run(self):
> >          translator =
> > ctranslate2.Translator('/path/to/ctranslate/model')
> >
> > class GUIWindow(QMainWindow):
> >      def translate(self):
> >          new_worker_thread = WorkerThread()
> >          new_worker_thread.start()
> >
> > app = QApplication([])
> > main_window = GUIWindow()
> > main_window.show()
> >
> > for i in range(120):
> >      print(i)
> >      main_window.translate()
> >
> > app.exec_()
>
> I don't see how the above can be expected to work, no matter what the
> run() method is doing. Your WorkerThread objects are likely to be
> garbage collected before they are finished and the __del__ won't protect
> the thread.
>
> Try making the GUIWindow the parent of the WorkerThreads and see if that
> makes a difference.
>
> Phil
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20201031/f6d1a242/attachment.htm>


More information about the PyQt mailing list