aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@qt.io>2023-08-25 16:01:03 +0200
committerShawn Rutledge <shawn.rutledge@qt.io>2023-09-01 18:47:13 +0000
commit2e5632058f8798dcec53016e2565aa23d04f3743 (patch)
treee265d41e2ace9d73b03b5915df6c74cbd3cbc552 /src
parentd82460f5a50527f5dd980ccb3bcd1782b5f84560 (diff)
Fix memory leak in qquickpixmapcache
QtPdf depends on QQuickPixmap::loadImageFromDevice() to pass a QPdfFile (which carries a pointer to the QPdfDocument) down into QPdfIOHandler so that it can avoid needing to reopen the document. But the introduction of loadImageFromDevice(), and then using the pixmap cache so intensively to store a lot of rendered PDF pages, revealed a drastic memory leak: many QQuickPixmapData instances remained in QQuickPixmapStore::m_cache long after their refCounts became 0. Quick scrolling through a PdfMultiPageView (especially if the zoom level is also being changed dynamically) can easily generate a lot of page-rendering jobs, some of which get cancelled early because the view has moved on to other pages and/or zoom levels. Here are several related fixes: - QQuickPixmapReader::getImage() creates a "job" represented by the QQuickPixmapReply that is returned, but now we start the job in a separate method, to ensure that QQuickPixmap::loadImageFromDevice() succeeds in connecting the QQuickPixmapReply::destroyed() signal before the job completes and deletes itself. - Use a QMutex to prevent QQuickPixmapStore::m_cache reentrancy: Rarely, a crash was possible when QQuickPixmapStore's QHash m_cache was modified between QHash::find() and dereferencing the iterator it returns, e.g. in QQuickPixmap::loadImageFromDevice(). - QQuickPixmapData::release() is needed in a few more places. - QQuickPixmapReply::finished isn't emitted if the job is cancelled, so the lambda that called oldD->release() wasn't called, and that was the remaining leak. We tried connecting QObject::destroyed() earlier, but it was causing a crash because the mutex to protect m_cache from simultaneous modification and QHash::find() was not in place. Now it seems ok. - Show "Number of leaked pixmaps: 0" in the qt.scenegraph.leaks logging category to make verification less ambiguous. Task-number: QTBUG-114953 Change-Id: I11518ccf28ee5a5055ae745a5921837d2f3151b6 Reviewed-by: Axel Spoerl <axel.spoerl@qt.io> (cherry picked from commit c871a52f94a4836515754e641ee02386a9876a58)
Diffstat (limited to 'src')
-rw-r--r--src/quick/util/qquickpixmapcache.cpp45
1 files changed, 35 insertions, 10 deletions
diff --git a/src/quick/util/qquickpixmapcache.cpp b/src/quick/util/qquickpixmapcache.cpp
index e6aca2de49..2d891e7764 100644
--- a/src/quick/util/qquickpixmapcache.cpp
+++ b/src/quick/util/qquickpixmapcache.cpp
@@ -225,6 +225,7 @@ public:
static QQuickPixmapReader *instance(QQmlEngine *engine);
static QQuickPixmapReader *existingInstance(QQmlEngine *engine);
+ void startJob(QQuickPixmapReply *job);
protected:
void run() override;
@@ -1121,14 +1122,17 @@ QQuickPixmapReader *QQuickPixmapReader::existingInstance(QQmlEngine *engine)
QQuickPixmapReply *QQuickPixmapReader::getImage(QQuickPixmapData *data)
{
- PIXMAP_READER_LOCK();
QQuickPixmapReply *reply = new QQuickPixmapReply(data);
reply->engineForReader = engine;
- jobs.append(reply);
- // XXX
+ return reply;
+}
+
+void QQuickPixmapReader::startJob(QQuickPixmapReply *job)
+{
+ PIXMAP_READER_LOCK();
+ jobs.append(job);
if (readerThreadExecutionEnforcer())
readerThreadExecutionEnforcer()->processJobsOnReaderThreadLater();
- return reply;
}
void QQuickPixmapReader::cancel(QQuickPixmapReply *reply)
@@ -1253,6 +1257,7 @@ protected:
public:
QHash<QQuickPixmapKey, QQuickPixmapData *> m_cache;
+ QMutex m_cacheMutex; // avoid simultaneous iteration and modification
private:
void shrinkCache(int remove);
@@ -1530,6 +1535,7 @@ void QQuickPixmapData::addToCache()
{
if (!inCache) {
QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
+ QMutexLocker locker(&pixmapStore()->m_cacheMutex);
if (lcImg().isDebugEnabled()) {
qCDebug(lcImg) << "adding" << key << "to total" << pixmapStore()->m_cache.size();
for (auto it = pixmapStore()->m_cache.keyBegin(); it != pixmapStore()->m_cache.keyEnd(); ++it) {
@@ -1550,6 +1556,7 @@ void QQuickPixmapData::removeFromCache(QQuickPixmapStore *store)
if (!store)
store = pixmapStore();
QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
+ QMutexLocker locker(&pixmapStore()->m_cacheMutex);
store->m_cache.remove(key);
qCDebug(lcImg) << "removed" << key << implicitSize << "; total remaining" << pixmapStore()->m_cache.size();
inCache = false;
@@ -1809,15 +1816,22 @@ void QQuickPixmap::setImage(const QImage &p)
{
clear();
- if (!p.isNull())
+ if (!p.isNull()) {
+ if (d)
+ d->release();
d = new QQuickPixmapData(this, QQuickTextureFactory::textureFactoryForImage(p));
+ }
}
void QQuickPixmap::setPixmap(const QQuickPixmap &other)
{
+ if (d == other.d)
+ return;
clear();
if (other.d) {
+ if (d)
+ d->release();
d = other.d;
d->addref();
d->declarativePixmaps.insert(this);
@@ -1881,6 +1895,7 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &reques
QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
QQuickPixmapStore *store = pixmapStore();
+ QMutexLocker locker(&pixmapStore()->m_cacheMutex);
QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.end();
#ifdef Q_OS_WEBOS
@@ -1904,6 +1919,7 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &reques
iter = store->m_cache.find(key);
if (iter == store->m_cache.end()) {
+ locker.unlock();
if (url.scheme() == QLatin1String("image")) {
QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(engine);
if (auto provider = enginePrivate->imageProvider(imageProviderId(url)).staticCast<QQuickImageProvider>()) {
@@ -1949,7 +1965,9 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &reques
#endif
QQuickPixmapReader::readerMutex.lock();
- d->reply = QQuickPixmapReader::instance(engine)->getImage(d);
+ QQuickPixmapReader *reader = QQuickPixmapReader::instance(engine);
+ d->reply = reader->getImage(d);
+ reader->startJob(d->reply);
QQuickPixmapReader::readerMutex.unlock();
} else {
d = *iter;
@@ -1974,29 +1992,36 @@ void QQuickPixmap::loadImageFromDevice(QQmlEngine *engine, QIODevice *device, co
QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
QQuickPixmapStore *store = pixmapStore();
QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.end();
+ QMutexLocker locker(&store->m_cacheMutex);
iter = store->m_cache.find(key);
if (iter == store->m_cache.end()) {
if (!engine)
return;
+ locker.unlock();
d = new QQuickPixmapData(this, url, requestRegion, requestSize, providerOptions,
QQuickImageProviderOptions::UsePluginDefaultTransform, frame, frameCount);
d->specialDevice = device;
d->addToCache();
QQuickPixmapReader::readerMutex.lock();
- d->reply = QQuickPixmapReader::instance(engine)->getImage(d);
+ QQuickPixmapReader *reader = QQuickPixmapReader::instance(engine);
+ d->reply = reader->getImage(d);
if (oldD) {
- QObject::connect(d->reply, &QQuickPixmapReply::finished, [oldD]() {
+ QObject::connect(d->reply, &QQuickPixmapReply::destroyed, store, [oldD]() {
oldD->release();
- });
+ }, Qt::QueuedConnection);
}
+ reader->startJob(d->reply);
QQuickPixmapReader::readerMutex.unlock();
} else {
d = *iter;
d->addref();
d->declarativePixmaps.insert(this);
- qCDebug(lcImg) << "loaded from cache" << url << "frame" << frame;
+ qCDebug(lcImg) << "loaded from cache" << url << "frame" << frame << "refCount" << d->refCount;
+ locker.unlock();
+ if (oldD)
+ oldD->release();
}
}