Commit 29597d36 authored by Orgad Shaneh's avatar Orgad Shaneh Committed by Orgad Shaneh

Truly support multiple instances

Qt*Single*Application is not designed to support multiple instances.

A shared memory is used to list all pids of running instances.

When an instance gracefully quits, it removes its own entry
and every other entry which is not currently running
(i.e. crashed instances).

Running qtcreator -client opens the first running instance in this list.

Running qtcreator -client -pid works as before.

Task-number: QTCREATORBUG-9458
Change-Id: I25d22a81097a224e9e45af093efa2ef2eccf6cb7
Reviewed-by: default avatarFriedemann Kleint <Friedemann.Kleint@digia.com>
Reviewed-by: default avatarDaniel Teske <daniel.teske@digia.com>
parent 3f503c29
......@@ -48,17 +48,13 @@ namespace SharedTools {
static const char ack[] = "ack";
QtLocalPeer::QtLocalPeer(QObject *parent, const QString &appId)
: QObject(parent), id(appId)
QString QtLocalPeer::appSessionId(const QString &appId)
{
if (id.isEmpty())
id = QCoreApplication::applicationFilePath(); //### On win, check if this returns .../argv[0] without casefolding; .\MYAPP == .\myapp on Win
QByteArray idc = id.toUtf8();
QByteArray idc = appId.toUtf8();
quint16 idNum = qChecksum(idc.constData(), idc.size());
//### could do: two 16bit checksums over separate halves of id, for a 32bit result - improved uniqeness probability. Every-other-char split would be best.
socketName = QLatin1String("qtsingleapplication-")
QString res = QLatin1String("qtsingleapplication-")
+ QString::number(idNum, 16);
#if defined(Q_OS_WIN)
if (!pProcessIdToSessionId) {
......@@ -68,12 +64,21 @@ QtLocalPeer::QtLocalPeer(QObject *parent, const QString &appId)
if (pProcessIdToSessionId) {
DWORD sessionId = 0;
pProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
socketName += QLatin1Char('-') + QString::number(sessionId, 16);
res += QLatin1Char('-') + QString::number(sessionId, 16);
}
#else
socketName += QLatin1Char('-') + QString::number(::getuid(), 16);
res += QLatin1Char('-') + QString::number(::getuid(), 16);
#endif
return res;
}
QtLocalPeer::QtLocalPeer(QObject *parent, const QString &appId)
: QObject(parent), id(appId)
{
if (id.isEmpty())
id = QCoreApplication::applicationFilePath(); //### On win, check if this returns .../argv[0] without casefolding; .\MYAPP == .\myapp on Win
socketName = appSessionId(id);
server = new QLocalServer(this);
QString lockName = QDir(QDir::tempPath()).absolutePath()
+ QLatin1Char('/') + socketName
......
......@@ -45,6 +45,7 @@ public:
bool sendMessage(const QString &message, int timeout, bool block);
QString applicationId() const
{ return id; }
static QString appSessionId(const QString &appId);
Q_SIGNALS:
void messageReceived(const QString &message, QObject *socket);
......
......@@ -30,34 +30,91 @@
#include "qtsingleapplication.h"
#include "qtlocalpeer.h"
#include <QWidget>
#include <qtlockedfile.h>
#include <QDir>
#include <QFileOpenEvent>
#include <QSharedMemory>
#include <QWidget>
namespace SharedTools {
void QtSingleApplication::sysInit(const QString &appId)
static const int instancesSize = 1024;
static QString instancesLockFilename(const QString &appSessionId)
{
const QChar slash(QLatin1Char('/'));
QString res = QDir::tempPath();
if (!res.endsWith(slash))
res += slash;
return res + appSessionId + QLatin1String("-instances");
}
QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv)
: QApplication(argc, argv),
firstPeer(-1)
{
this->appId = appId;
const QString appSessionId = QtLocalPeer::appSessionId(appId);
// This shared memory holds a zero-terminated array of active (or crashed) instances
instances = new QSharedMemory(appSessionId, this);
actWin = 0;
block = false;
firstPeer = new QtLocalPeer(this, appId);
connect(firstPeer, SIGNAL(messageReceived(QString,QObject*)), SIGNAL(messageReceived(QString,QObject*)));
pidPeer = new QtLocalPeer(this, appId + QLatin1Char('-') + QString::number(QCoreApplication::applicationPid(), 10));
connect(pidPeer, SIGNAL(messageReceived(QString,QObject*)), SIGNAL(messageReceived(QString,QObject*)));
}
const qint64 appPid = QCoreApplication::applicationPid();
pidPeer = new QtLocalPeer(this, appId + QLatin1Char('-') + QString::number(appPid));
connect(pidPeer, SIGNAL(messageReceived(QString,QObject*)), SIGNAL(messageReceived(QString,QObject*)));
QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled)
: QApplication(argc, argv, GUIenabled)
{
sysInit();
// First instance creates the shared memory, later instances attach to it
const bool created = instances->create(instancesSize);
if (!created) {
if (!instances->attach()) {
qWarning() << "Failed to initialize instances shared memory: "
<< instances->errorString();
delete instances;
instances = 0;
return;
}
}
// QtLockedFile is used to workaround QTBUG-10364
QtLockedFile lockfile(instancesLockFilename(appSessionId));
lockfile.open(QtLockedFile::ReadWrite);
lockfile.lock(QtLockedFile::WriteLock);
qint64 *pids = static_cast<qint64 *>(instances->data());
if (!created) {
// Find the first instance that it still running
// The whole list needs to be iterated in order to append to it
for (; *pids; ++pids) {
if (firstPeer == -1 && isRunning(*pids))
firstPeer = *pids;
}
}
// Add current pid to list and terminate it
*pids++ = appPid;
*pids = 0;
lockfile.unlock();
}
QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv)
: QApplication(argc, argv)
QtSingleApplication::~QtSingleApplication()
{
this->appId = appId;
sysInit(appId);
if (!instances)
return;
const qint64 appPid = QCoreApplication::applicationPid();
QtLockedFile lockfile(instancesLockFilename(QtLocalPeer::appSessionId(appId)));
lockfile.open(QtLockedFile::ReadWrite);
lockfile.lock(QtLockedFile::WriteLock);
// Rewrite array, removing current pid and previously crashed ones
qint64 *pids = static_cast<qint64 *>(instances->data());
qint64 *newpids = pids;
for (; *pids; ++pids) {
if (*pids != appPid && isRunning(*pids))
*newpids++ = *pids;
}
*newpids = 0;
lockfile.unlock();
}
bool QtSingleApplication::event(QEvent *event)
......@@ -72,8 +129,11 @@ bool QtSingleApplication::event(QEvent *event)
bool QtSingleApplication::isRunning(qint64 pid)
{
if (pid == -1)
return firstPeer->isClient();
if (pid == -1) {
pid = firstPeer;
if (pid == -1)
return false;
}
QtLocalPeer peer(this, appId + QLatin1Char('-') + QString::number(pid, 10));
return peer.isClient();
......@@ -81,24 +141,21 @@ bool QtSingleApplication::isRunning(qint64 pid)
void QtSingleApplication::initialize(bool)
{
firstPeer->isClient();
pidPeer->isClient();
}
bool QtSingleApplication::sendMessage(const QString &message, int timeout, qint64 pid)
{
if (pid == -1)
return firstPeer->sendMessage(message, timeout, block);
if (pid == -1) {
pid = firstPeer;
if (pid == -1)
return false;
}
QtLocalPeer peer(this, appId + QLatin1Char('-') + QString::number(pid, 10));
return peer.sendMessage(message, timeout, block);
}
QString QtSingleApplication::id() const
{
return firstPeer->applicationId();
}
QString QtSingleApplication::applicationId() const
{
return appId;
......@@ -112,13 +169,10 @@ void QtSingleApplication::setBlock(bool value)
void QtSingleApplication::setActivationWindow(QWidget *aw, bool activateOnMessage)
{
actWin = aw;
if (activateOnMessage) {
connect(firstPeer, SIGNAL(messageReceived(QString,QObject*)), this, SLOT(activateWindow()));
if (activateOnMessage)
connect(pidPeer, SIGNAL(messageReceived(QString,QObject*)), this, SLOT(activateWindow()));
} else {
disconnect(firstPeer, SIGNAL(messageReceived(QString,QObject*)), this, SLOT(activateWindow()));
else
disconnect(pidPeer, SIGNAL(messageReceived(QString,QObject*)), this, SLOT(activateWindow()));
}
}
......
......@@ -29,6 +29,8 @@
#include <QApplication>
QT_FORWARD_DECLARE_CLASS(QSharedMemory)
namespace SharedTools {
class QtLocalPeer;
......@@ -38,13 +40,11 @@ class QtSingleApplication : public QApplication
Q_OBJECT
public:
QtSingleApplication(int &argc, char **argv, bool GUIenabled = true);
QtSingleApplication(const QString &id, int &argc, char **argv);
~QtSingleApplication();
bool isRunning(qint64 pid = -1);
QString id() const;
void setActivationWindow(QWidget* aw, bool activateOnMessage = true);
QWidget* activationWindow() const;
bool event(QEvent *event);
......@@ -56,18 +56,18 @@ public Q_SLOTS:
bool sendMessage(const QString &message, int timeout = 5000, qint64 pid = -1);
void activateWindow();
//Obsolete methods:
public:
void initialize(bool = true);
// end obsolete methods
Q_SIGNALS:
void messageReceived(const QString &message, QObject *socket);
void fileOpenRequest(const QString &file);
private:
void sysInit(const QString &appId = QString());
QtLocalPeer *firstPeer;
QString instancesFileName(const QString &appId);
qint64 firstPeer;
QSharedMemory *instances;
QtLocalPeer *pidPeer;
QWidget *actWin;
QString appId;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment