QNetworkReply не удаляется

Я делаю что-то похожее на этот вопрос, но у меня есть более тонкая проблема.

У меня есть клиентский класс API, который делает HTTP-запросы; Я храню QNetworkReply в объекте, чтобы получить доступ к его данным из слота, подключенного к "готовому" сигналу. При следующем запросе это заменяется следующим QNetworkReply, поэтому Python должен иметь возможность освободить предыдущий объект запроса и, следовательно, базовые сетевые ресурсы. Вместо этого старые объекты ответа, кажется, застряли где-то, что привело к утечке памяти, и, если приложение работает достаточно долго, задержка при выходе, вероятно, из-за того, что все когда-либо выданные запросы, наконец, удаляются.

Упрощенный, но полный пример:

import sys, signal
from PySide import QtCore, QtNetwork

class Poller(QtCore.QObject):
    url = QtCore.QUrl("http://localhost:5000/")

    def __init__(self, parent=None):
        super(Poller,self).__init__(parent)
        self.manager = QtNetwork.QNetworkAccessManager()

    def start(self):
        request = QtNetwork.QNetworkRequest(self.url)
        self.reply = self.manager.get(request)
        self.reply.finished.connect(self.readReply)
        self.reply.error.connect(self.error)

    def readReply(self):
        text = self.reply.readAll()
        self.reply.close() # doesn't help
        self.reply.deleteLater() # doesn't help
        QtCore.QTimer.singleShot(10, self.start)

    @QtCore.Slot(QtNetwork.QNetworkReply.NetworkError)
    def error(self, err):
        self.reply.finished.disconnect()
        sys.stderr.write('Error: ' + str(err) + '\n')
        QtCore.QTimer.singleShot(10, self.start)

signal.signal(signal.SIGINT, signal.SIG_DFL)
app = QtCore.QCoreApplication(sys.argv)
p = Poller()
p.start()
app.exec_()

Опрашиваемый http-сервер не имеет большого значения; для этого теста я использую Привет мир Flask. Фактически, так как это не делает keepalive соединения, если я проверяю "netstat", я вижу постоянно увеличивающееся количество соединений TCP-зомби в состоянии TIME_WAIT и в конечном итоге начинаю получать PySide.QtNetwork.QNetworkReply.NetworkError.UnknownNetworkError после 30 000+ портов были израсходованы; дополнительные доказательства того, что QNetworkReply не освобождается должным образом.

Та же проблема происходит с PySide или PyQt4. Что я делаю не так, или это может быть ошибкой?

1 ответ

Решение

Во-первых, выясняется, что после закрытия TCP-соединения ненадолго задерживается в TIME_WAIT. Я превышал ограничение только потому, что для тестирования я установил таймер singleShot на 0 мс.

Я переписал пример на C++. deleteLater работал как ожидалось, и я мог воспроизвести как увеличение памяти, так и медленный выход, пропустив его. (Поскольку память управляется Qt, все объекты ответа должны были быть удалены деструктором QNetworkAccessManager).

Интересно, что при ближайшем рассмотрении медленное завершение не происходит в Python при использовании deleteLater, но происходит рост памяти. Поэтому я предполагаю, что объект C++ удаляется, но где-то все еще используются ресурсы. Это все еще таинственно для меня.

Исправление, однако, заключается в том, чтобы позвонить setParent(None) на QNetworkReply. Это можно сделать в любое время, даже когда он впервые возвращается из QNetworkAccessManager. Первоначально он был отцом к менеджеру; изменение родителя на null означает, что Qt не отвечает за управление памятью, и он будет правильно обрабатываться Python, даже без использования deleteLater.

(Я нашел эту подсказку где-то в Интернете; к сожалению, я не могу найти ее сейчас, или я бы связал ее.)

Изменить: я думал, что это сработало в моем тестировании, но мое приложение все еще утечка памяти.

Изменить 2: у меня нет утечки с PyQt4 на Python 2, но я делаю с PySide, и с обоими на Python 3.

Другие вопросы по тегам