Skip to content
Merged
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
1 change: 1 addition & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ add_subdirectory(dnd-example)
#add_subdirectory(test-taskbar) # Don't build
#add_subdirectory(frameless-window) # Don't build
add_subdirectory(animation-dci)
add_subdirectory(single-instance-demo)
17 changes: 17 additions & 0 deletions examples/single-instance-demo/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)

set(BIN single-instance-demo)
set(TARGET ${BIN}${DTK_VERSION_MAJOR})

add_executable(${TARGET}
main.cpp
)

target_link_libraries(${TARGET} PRIVATE
Qt${QT_VERSION_MAJOR}::Widgets
${LIB_NAME}
)

set_target_properties(${TARGET} PROPERTIES OUTPUT_NAME ${BIN})

install(TARGETS ${TARGET} DESTINATION "${TOOL_INSTALL_DIR}")
55 changes: 55 additions & 0 deletions examples/single-instance-demo/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#include <QApplication>

Check warning on line 5 in examples/single-instance-demo/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

Check warning on line 6 in examples/single-instance-demo/main.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 <QTimer>

Check warning on line 7 in examples/single-instance-demo/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

Check warning on line 8 in examples/single-instance-demo/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

Check warning on line 9 in examples/single-instance-demo/main.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

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

DGUI_USE_NAMESPACE

// 演示场景:
// 1. 实例 A 启动,成为单实例 primary
// 2. 3 秒后 A 触发退出:aboutToQuit 发出(dtkgui 释放单实例锁和 server),
// 但 main() 中模拟"有线程卡住",进程继续存活 10 秒才真正退出
// 3. 在这 10 秒内启动实例 B:
// - 若在 aboutToQuit 之前启动 → B 检测到 A 存活,正常退出(单实例保护)
// - 若在 aboutToQuit 之后启动 → B 应能成功成为新的 primary(本次修复的目标)

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

if (!DGuiApplicationHelper::setSingleInstance("single-instance-demo")) {
qInfo() << "[demo] another instance is running, exit now. pid =" << app.applicationPid();
return 0;
}

qInfo() << "[demo] primary started. pid =" << app.applicationPid();

// 可通过参数控制退出前的存活时间,默认 3 秒后自动退出
int lifetime = 3000;
if (app.arguments().contains("--lifetime")) {
int pos = app.arguments().indexOf("--lifetime");
if (pos + 1 < app.arguments().size())
lifetime = app.arguments().at(pos + 1).toInt();
}

// 模拟退出阶段有线程卡住:事件循环退出后进程并不会立即结束
const int stuckSeconds = 10;
QObject::connect(&app, &QCoreApplication::aboutToQuit, &app, [stuckSeconds] {
qInfo() << "[demo] aboutToQuit: single instance released,"
<< "process will stay alive for" << stuckSeconds << "s (simulating stuck thread)";
});

QTimer::singleShot(lifetime, &app, &QCoreApplication::quit);
const int ret = app.exec();

// 模拟卡住的清理逻辑阻塞进程完全退出
QThread::sleep(stuckSeconds);
qInfo() << "[demo] stuck cleanup finished, real exit now.";

return ret;
}
59 changes: 56 additions & 3 deletions src/kernel/dguiapplicationhelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,39 @@

Q_GLOBAL_STATIC(QLocalServer, _d_singleServer)

// 单实例锁文件,进程退出(aboutToQuit)时需提前释放,
// 避免线程卡住导致进程未完全退出时新实例无法获取锁。
// 文件名在 setSingleInstance 中设置(多次调用使用最后一次的 key)
class SingleInstanceLock
{
public:
SingleInstanceLock() = default;
~SingleInstanceLock()
{
unlock();
}

void reset(const QString &fileName)
{
unlock();
lock.reset(new QLockFile(fileName));
}

bool isNull() const { return lock.isNull(); }
QLockFile *data() const { return lock.data(); }

void unlock()
{
if (lock)
lock->unlock();
}

private:
QScopedPointer<QLockFile> lock;
};

Q_GLOBAL_STATIC(SingleInstanceLock, _d_singleInstanceLock)

// Protocol changelog:
// v1: version + pid + arguments
// v2: version + pid + arguments + envs (KEY=VALUE pairs, filtered by whitelist)
Expand Down Expand Up @@ -1550,15 +1583,18 @@
}

lockfile += QStringLiteral(".lock");
static QScopedPointer <QLockFile> lock(new QLockFile(lockfile));
// 同一个进程多次调用本接口使用最后一次设置的 key
// FIX dcc 使用不同的 key 两次调用 setSingleInstance 后无法启动的问题
if (_d_singleInstanceLock->isNull())
_d_singleInstanceLock->reset(lockfile);
Comment thread
sourcery-ai[bot] marked this conversation as resolved.

QLockFile *lock = _d_singleInstanceLock->data();
qint64 pid = -1;

Check warning on line 1592 in src/kernel/dguiapplicationhelper.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Local variable 'pid' shadows outer variable
QString hostname, appname;
if (lock->isLocked() && lock->getLockInfo(&pid, &hostname, &appname) && pid == getpid()) {
qCWarning(dgAppHelper) << "call setSingleInstance again within the same process";
lock->unlock();
lock.reset(new QLockFile(lockfile));
_d_singleInstanceLock->reset(lockfile);
lock = _d_singleInstanceLock->data();
}

if (!lock->tryLock()) {
Expand Down Expand Up @@ -1604,6 +1640,23 @@
qCDebug(dgAppHelper) << "===> listen <===" << _d_singleServer->serverName() << getpid();
}

// 应用开始退出时提前释放单实例资源(关闭 server 并解锁锁文件),
// 避免有线程卡住导致进程未退出期间新实例无法启动
if (qApp) {
// Qt::UniqueConnection 对 lambda 无效,用静态标志防止重复连接
static bool connected = false;
if (!connected) {
connected = true;
QObject::connect(qApp, &QCoreApplication::aboutToQuit, qApp, [] {
Comment thread
18202781743 marked this conversation as resolved.
if (_d_singleServer.exists() && _d_singleServer->isListening())
_d_singleServer->close();
if (_d_singleInstanceLock.exists())
_d_singleInstanceLock->unlock();
qCDebug(dgAppHelper) << "single instance released on quit.";
});
}
}

if (new_server) {
Comment thread
18202781743 marked this conversation as resolved.
qCDebug(dgAppHelper) << "===> new server <===" << _d_singleServer->serverName() << getpid();
QObject::connect(_d_singleServer, &QLocalServer::newConnection, qApp, [] {
Expand Down
Loading