Issue393

Title Memory leak: PyQt5 objects containing connected slots are never collected
Priority bug Status chatting
Superseder Nosy List univibe
Assigned To Keywords

Created on 2018-03-02.22:56:30 by univibe, last changed by univibe.

Files
File name Uploaded Type Edit Remove
leaky_widgets.py univibe, 2018-03-02.22:56:29 text/plain
Messages
msg2305 (view) Author: univibe Date: 2018-03-02.23:08:23
You may want to change the line

     self.timer.setInterval(1000)

to a shorter interval like 50 to make the memory growth effect more evident in Task 
Manager.
msg2304 (view) Author: univibe Date: 2018-03-02.22:56:29
Hi all! It seems to me that in PyQt5 programs compiled with Nuitka objects containing connected slots are never garbage 
collected, causing a memory leak!

The following program continuously creates windows containing one QPushButton  whose 'clicked' signal is connected to the slot 
some_slot() and leaves them go out of scope.

When running interpreted: widgets are destroyed immediately after going out of scope; on the console the output is something like

    ...
    TestWindow created 1
    TestWindow del 1
    TestWindow created 2
    TestWindow del 2
    ...

When running compiled: widgets are never destroyed!

    ...
    TestWindow created 1
    TestWindow created 2
    ...
  
Memory leak is confirmed by looking at the Task Manager, where the program memory usage stays fixed (at 11.8 Mb on my system) 
when running interpreted, while growing without bounds when running the compiled version.

Note the leak doesn't happen and the widget is correctly collected if signals are disconnected before letting the widget go out 
of scope.

Testing environment:
- Windows 10 64 bit
- Python versions 3.5.3 and 3.6.4, 64 bit
- PyQt5 5.10.1 
- Nuitka 0.5.28.1

#####################################################

import sys

from PyQt5.QtCore import pyqtSlot, QTimer
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton


class TestWindow(QWidget):

	test_window_number = 0

	def __init__(self):
		super().__init__()

		TestWindow.test_window_number += 1
		self.number = TestWindow.test_window_number

		print("TestWindow created", TestWindow.test_window_number)

		ly = QVBoxLayout()
		self.setLayout(ly)

		self.button = QPushButton()
		self.button.setText("Click me")
		self.button.clicked.connect(self.some_slot)

		ly.addWidget(self.button)

	def __del__(self):
		print("TestWindow", self.number, "del")

	@pyqtSlot()
	def some_slot(self):
		print("The button has been clicked")

	def dispose(self):
		self.button.disconnect()

class MainWindow(QWidget):

	def __init__(self):
		super().__init__()

		self.timer = QTimer()
		self.timer.setInterval(1000)
		self.timer.timeout.connect(self.on_timer_timeout)
		self.timer.start()

	def on_timer_timeout(self):
		
		w = TestWindow()
		
		# Uncomment this to disconnect some_slot(): in this case there's no leak.
		# w.dispose()
		
qapplication = QApplication(sys.argv)

w = MainWindow()
w.show()

sys.exit(qapplication.exec_())
History
Date User Action Args
2018-03-02 23:08:23univibesetstatus: unread -> chatting
messages: + msg2305
2018-03-02 22:56:31univibecreate