Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions panels/notification/server/notifyserverapplet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,22 @@ NotifyServerApplet::NotifyServerApplet(QObject *parent)
NotifyServerApplet::~NotifyServerApplet()
{
qDebug(notifyLog) << "Exit notification server.";
if (m_manager) {
m_manager->deleteLater();
}
if (m_worker) {
if (m_manager) {
// Schedule manager deletion while the worker event loop is still able to process it.
connect(m_worker, &QThread::finished, m_manager, &QObject::deleteLater, Qt::UniqueConnection);
Comment on lines 28 to +31
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): QThread::finished → deleteLater on an object in the worker thread is likely never processed after calling exit(), leading to a leak.

Because QThread::finished is emitted in the owning (likely main/UI) thread, the deleteLater call on m_manager becomes a queued event to the worker’s event loop. But exit() stops that event loop and finished is emitted only after it has already exited, so the queued deleteLater may never run, leaking m_manager.

Instead, delete m_manager before shutting down the worker, for example by:

  • Calling QMetaObject::invokeMethod(m_manager, "deleteLater", Qt::BlockingQueuedConnection) before m_worker->exit()/wait(), or
  • Deleting it directly if it already lives in the main thread.

The key point is to clean up m_manager synchronously in the correct thread, rather than relying on finished after the event loop is gone.

}
m_worker->exit();
m_worker->wait();
m_worker->deleteLater();
delete m_worker;
m_worker = nullptr;
m_manager = nullptr;
return;
}

if (m_manager) {
delete m_manager;
m_manager = nullptr;
}
}

Expand Down
74 changes: 74 additions & 0 deletions tests/panels/notification/server/notifyserverapplet_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,43 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later

#include <gtest/gtest.h>

Check warning on line 5 in tests/panels/notification/server/notifyserverapplet_test.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <gtest/gtest.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <gmock/gmock.h>

Check warning on line 6 in tests/panels/notification/server/notifyserverapplet_test.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <gmock/gmock.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.

#include <atomic>

Check warning on line 8 in tests/panels/notification/server/notifyserverapplet_test.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <atomic> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QCoreApplication>

Check warning on line 9 in tests/panels/notification/server/notifyserverapplet_test.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QCoreApplication> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QPointer>

Check warning on line 10 in tests/panels/notification/server/notifyserverapplet_test.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QPointer> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QTest>

Check warning on line 11 in tests/panels/notification/server/notifyserverapplet_test.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QTest> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QThread>

Check warning on line 12 in tests/panels/notification/server/notifyserverapplet_test.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QThread> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QSignalSpy>

Check warning on line 13 in tests/panels/notification/server/notifyserverapplet_test.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QSignalSpy> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QVariant>

Check warning on line 14 in tests/panels/notification/server/notifyserverapplet_test.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QVariant> not found. Please note: Cppcheck does not need standard library headers to get proper results.

#define private public
#include "notifyserverapplet.h"

Check warning on line 17 in tests/panels/notification/server/notifyserverapplet_test.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "notifyserverapplet.h" not found.
#undef private
#include "notificationmanager.h"

using namespace notification;
using ::testing::_;
using ::testing::Return;
using ::testing::Invoke;

class TrackableNotificationManager : public NotificationManager {
Q_OBJECT
public:
explicit TrackableNotificationManager(QObject *parent = nullptr)
: NotificationManager(parent) {}

~TrackableNotificationManager() override
{
++destroyedCount;
}

static std::atomic_int destroyedCount;
};

std::atomic_int TrackableNotificationManager::destroyedCount = 0;

// Mock class for NotificationManager
class MockNotificationManager : public NotificationManager {
Q_OBJECT
Expand Down Expand Up @@ -72,6 +93,59 @@
EXPECT_NO_THROW(delete testApplet);
}

// Test destructor when only manager exists (no worker thread)
TEST_F(NotifyServerAppletTest, DestructorWithManagerOnlyTest) {
TrackableNotificationManager::destroyedCount = 0;

auto *testApplet = new NotifyServerApplet();
auto *manager = new TrackableNotificationManager();
QPointer<TrackableNotificationManager> managerGuard(manager);

testApplet->m_manager = manager;
// m_worker remains nullptr

delete testApplet;

EXPECT_TRUE(managerGuard.isNull());
EXPECT_EQ(TrackableNotificationManager::destroyedCount.load(), 1);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): The null QPointer and destroyedCount checks may race with an asynchronous deleteLater, making this test potentially flaky.

Because the manager is deleted via a signal/deleteLater, its actual destruction depends on event loop processing. Immediately checking managerGuard.isNull() and destroyedCount == 1 after delete testApplet; can therefore be timing‑dependent. To avoid flakiness, either wait for the event loop until the guard becomes null (e.g. QTRY_VERIFY_WITH_TIMEOUT(managerGuard.isNull(), 1000);) before asserting the count, or make the manager deletion synchronous in this path so the assertions can be deterministic.

}

// Test destructor when worker exists but manager is null
TEST_F(NotifyServerAppletTest, DestructorWithWorkerOnlyTest) {
auto *testApplet = new NotifyServerApplet();
auto *worker = new QThread();

testApplet->m_worker = worker;
testApplet->m_manager = nullptr;

worker->start();
QTRY_VERIFY_WITH_TIMEOUT(worker->isRunning(), 1000);

EXPECT_NO_THROW(delete testApplet);
}

TEST_F(NotifyServerAppletTest, DestructorDeletesManagerAfterWorkerThreadExit) {
TrackableNotificationManager::destroyedCount = 0;

auto *testApplet = new NotifyServerApplet();
auto *worker = new QThread();
auto *manager = new TrackableNotificationManager();
QPointer<TrackableNotificationManager> managerGuard(manager);

testApplet->m_manager = manager;
testApplet->m_worker = worker;

manager->moveToThread(worker);
worker->start();

QTRY_VERIFY_WITH_TIMEOUT(worker->isRunning(), 1000);

delete testApplet;

EXPECT_TRUE(managerGuard.isNull());
EXPECT_EQ(TrackableNotificationManager::destroyedCount.load(), 1);
}

// Test memory leak detection for destructor with initialized applet
TEST_F(NotifyServerAppletTest, DestructorMemoryLeakTest) {
// This test verifies that all resources are properly cleaned up
Expand Down
Loading