Update singleapplication

This commit is contained in:
Jonas Kvinge
2020-10-05 21:31:03 +02:00
parent 393a2e0ea0
commit 4a01a578d1
8 changed files with 778 additions and 453 deletions

View File

@@ -1,6 +1,6 @@
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (c) Itay Grudev 2015 - 2018 // Copyright (c) Itay Grudev 2015 - 2020
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -35,149 +35,207 @@
#include <limits> #include <limits>
#include <QtGlobal> #include <QtGlobal>
#include <QCoreApplication> #include <QApplication>
#include <QThread> #include <QThread>
#include <QSharedMemory> #include <QSharedMemory>
#include <QLocalSocket> #include <QLocalSocket>
#include <QByteArray> #include <QByteArray>
#include <QElapsedTimer> #include <QElapsedTimer>
#include <QtDebug> #include <QtDebug>
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
# include <QRandomGenerator>
#else
# include <QDateTime>
#endif
#include "singleapplication.h" #include "singleapplication.h"
#include "singleapplication_p.h" #include "singleapplication_p.h"
/** /**
* @brief Constructor. * @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
* Checks and fires up LocalServer or closes the program if another instance already exists
* @param argc * @param argc
* @param argv * @param argv
* @param {bool} allowSecondaryInstances * @param allowSecondary Whether to enable secondary instance support
* @param options Optional flags to toggle specific behaviour
* @param timeout Maximum time blocking functions are allowed during app load
*/ */
SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondary, Options options, int timeout) SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondary, Options options, int timeout)
: app_t(argc, argv), d_ptr(new SingleApplicationPrivate(this)) { : app_t(argc, argv),
d_ptr(new SingleApplicationPrivate(this)) {
Q_D(SingleApplication); Q_D(SingleApplication);
// Store the current mode of the program // Store the current mode of the program
d->options = options; d->options_ = options;
// Generating an application ID used for identifying the shared memory block and QLocalServer // Generating an application ID used for identifying the shared memory block and QLocalServer
d->genBlockServerName(); d->genBlockServerName();
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
d->randomSleep();
#ifdef Q_OS_UNIX #ifdef Q_OS_UNIX
// By explicitly attaching it and then deleting it we make sure that the // By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
// memory is deleted even after the process has crashed on Unix. d->memory_ = new QSharedMemory(d->blockServerName_);
d->memory = new QSharedMemory(d->blockServerName); d->memory_->attach();
d->memory->attach(); delete d->memory_;
delete d->memory;
#endif #endif
// Guarantee thread safe behaviour with a shared memory block. // Guarantee thread safe behaviour with a shared memory block.
d->memory = new QSharedMemory(d->blockServerName); d->memory_ = new QSharedMemory(d->blockServerName_);
// Create a shared memory block // Create a shared memory block
if (d->memory->create(sizeof(InstancesInfo))) { if (d->memory_->create(sizeof(InstancesInfo))) {
// Initialize the shared memory block // Initialize the shared memory block
d->memory->lock(); if (!d->memory_->lock()) {
qCritical() << "SingleApplication: Unable to lock memory block after create.";
abortSafely();
}
d->initializeMemoryBlock(); d->initializeMemoryBlock();
d->memory->unlock();
} }
else { else {
// Attempt to attach to the memory segment if (d->memory_->error() == QSharedMemory::AlreadyExists) {
if (!d->memory->attach()) { // Attempt to attach to the memory segment
qCritical() << "SingleApplication: Unable to attach to shared memory block."; if (!d->memory_->attach()) {
qCritical() << d->memory->errorString(); qCritical() << "SingleApplication: Unable to attach to shared memory block.";
delete d; abortSafely();
::exit(EXIT_FAILURE); }
if (!d->memory_->lock()) {
qCritical() << "SingleApplication: Unable to lock memory block after attach.";
abortSafely();
}
}
else {
qCritical() << "SingleApplication: Unable to create block.";
abortSafely();
} }
} }
InstancesInfo *inst = static_cast<InstancesInfo *>(d->memory->data()); InstancesInfo *inst = static_cast<InstancesInfo*>(d->memory_->data());
QElapsedTimer time; QElapsedTimer time;
time.start(); time.start();
// Make sure the shared memory block is initialised and in consistent state // Make sure the shared memory block is initialised and in consistent state
while (true) { forever {
d->memory->lock(); // If the shared memory block's checksum is valid continue
if (d->blockChecksum() == inst->checksum) break; if (d->blockChecksum() == inst->checksum) break;
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
if (time.elapsed() > 5000) { if (time.elapsed() > 5000) {
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure."; qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
d->initializeMemoryBlock(); d->initializeMemoryBlock();
} }
d->memory->unlock(); // Otherwise wait for a random period and try again.
// The random sleep here limits the probability of a collision between two racing apps and allows the app to initialise faster
// Random sleep here limits the probability of a collision between two racing apps if (!d->memory_->unlock()) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
QThread::sleep(QRandomGenerator::global()->bounded(8u, 18u)); qDebug() << d->memory_->errorString();
#else }
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max()); d->randomSleep();
QThread::sleep(8 + static_cast<unsigned long>(static_cast<float>(qrand()) / RAND_MAX * 10)); if (!d->memory_->lock()) {
#endif qCritical() << "SingleApplication: Unable to lock memory after random wait.";
abortSafely();
}
} }
if (inst->primary == false) { if (inst->primary == false) {
d->startPrimary(); d->startPrimary();
d->memory->unlock(); if (!d->memory_->unlock()) {
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
qDebug() << d->memory_->errorString();
}
return; return;
} }
// Check if another instance can be started // Check if another instance can be started
if (allowSecondary) { if (allowSecondary) {
inst->secondary += 1;
inst->checksum = d->blockChecksum();
d->instanceNumber = inst->secondary;
d->startSecondary(); d->startSecondary();
if (d->options & Mode::SecondaryNotification) { if (d->options_ & Mode::SecondaryNotification) {
d->connectToPrimary(timeout, SingleApplicationPrivate::SecondaryInstance); d->connectToPrimary(timeout, SingleApplicationPrivate::SecondaryInstance);
} }
d->memory->unlock(); if (!d->memory_->unlock()) {
qDebug() << "SingleApplication: Unable to unlock memory after secondary start.";
qDebug() << d->memory_->errorString();
}
return; return;
} }
d->memory->unlock(); if (!d->memory_->unlock()) {
qDebug() << "SingleApplication: Unable to unlock memory at end of execution.";
qDebug() << d->memory_->errorString();
}
d->connectToPrimary(timeout, SingleApplicationPrivate::NewInstance); d->connectToPrimary(timeout, SingleApplicationPrivate::NewInstance);
delete d; delete d;
::exit(EXIT_SUCCESS); ::exit(EXIT_SUCCESS);
} }
/**
* @brief Destructor
*/
SingleApplication::~SingleApplication() { SingleApplication::~SingleApplication() {
Q_D(SingleApplication); Q_D(SingleApplication);
delete d; delete d;
} }
/**
* Checks if the current application instance is primary.
* @return Returns true if the instance is primary, false otherwise.
*/
bool SingleApplication::isPrimary() { bool SingleApplication::isPrimary() {
Q_D(SingleApplication); Q_D(SingleApplication);
return d->server != nullptr; return d->server_ != nullptr;
} }
/**
* Checks if the current application instance is secondary.
* @return Returns true if the instance is secondary, false otherwise.
*/
bool SingleApplication::isSecondary() { bool SingleApplication::isSecondary() {
Q_D(SingleApplication); Q_D(SingleApplication);
return d->server == nullptr; return d->server_ == nullptr;
} }
/**
* Allows you to identify an instance by returning unique consecutive instance ids.
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
* @return Returns a unique instance id.
*/
quint32 SingleApplication::instanceId() { quint32 SingleApplication::instanceId() {
Q_D(SingleApplication); Q_D(SingleApplication);
return d->instanceNumber; return d->instanceNumber_;
} }
/**
* Returns the OS PID (Process Identifier) of the process running the primary instance.
* Especially useful when SingleApplication is coupled with OS. specific APIs.
* @return Returns the primary instance PID.
*/
qint64 SingleApplication::primaryPid() { qint64 SingleApplication::primaryPid() {
Q_D(SingleApplication); Q_D(SingleApplication);
return d->primaryPid(); return d->primaryPid();
} }
/**
* Returns the username the primary instance is running as.
* @return Returns the username the primary instance is running as.
*/
QString SingleApplication::primaryUser() {
Q_D(SingleApplication);
return d->primaryUser();
}
/**
* Returns the username the current instance is running as.
* @return Returns the username the current instance is running as.
*/
QString SingleApplication::currentUser() {
Q_D(SingleApplication);
return d->getUsername();
}
/**
* Sends message to the Primary Instance.
* @param message The message to send.
* @param timeout the maximum timeout in milliseconds for blocking functions.
* @return true if the message was sent successfuly, false otherwise.
*/
bool SingleApplication::sendMessage(QByteArray message, int timeout) { bool SingleApplication::sendMessage(QByteArray message, int timeout) {
Q_D(SingleApplication); Q_D(SingleApplication);
@@ -186,10 +244,26 @@ bool SingleApplication::sendMessage(QByteArray message, int timeout) {
if (isPrimary()) return false; if (isPrimary()) return false;
// Make sure the socket is connected // Make sure the socket is connected
d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect); if (!d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect))
return false;
d->socket->write(message); d->socket_->write(message);
bool dataWritten = d->socket->waitForBytesWritten(timeout); const bool dataWritten = d->socket_->waitForBytesWritten(timeout);
d->socket->flush(); d->socket_->flush();
return dataWritten; return dataWritten;
}
/**
* Cleans up the shared memory block and exits with a failure.
* This function halts program execution.
*/
void SingleApplication::abortSafely() {
Q_D(SingleApplication);
qCritical() << "SingleApplication: " << d->memory_->error() << d->memory_->errorString();
delete d;
::exit(EXIT_FAILURE);
} }

View File

@@ -1,6 +1,6 @@
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (c) Itay Grudev 2015 - 2018 // Copyright (c) Itay Grudev 2015 - 2020
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -42,9 +42,8 @@
class SingleApplicationPrivate; class SingleApplicationPrivate;
/** /**
* @brief The SingleApplication class handles multipe instances of the same * @brief The SingleApplication class handles multipe instances of the same Application
* Application * @see QApplication
* @see QCoreApplication
*/ */
class SingleApplication : public QApplication { class SingleApplication : public QApplication {
Q_OBJECT Q_OBJECT
@@ -118,6 +117,18 @@ class SingleApplication : public QApplication {
*/ */
qint64 primaryPid(); qint64 primaryPid();
/**
* @brief Returns the username of the user running the primary instance
* @returns {QString}
*/
QString primaryUser();
/**
* @brief Returns the username of the current user
* @returns {QString}
*/
QString currentUser();
/** /**
* @brief Sends a message to the primary instance. Returns true on success. * @brief Sends a message to the primary instance. Returns true on success.
* @param {int} timeout - Timeout for connecting * @param {int} timeout - Timeout for connecting
@@ -134,6 +145,7 @@ class SingleApplication : public QApplication {
private: private:
SingleApplicationPrivate *d_ptr; SingleApplicationPrivate *d_ptr;
Q_DECLARE_PRIVATE(SingleApplication) Q_DECLARE_PRIVATE(SingleApplication)
void abortSafely();
}; };
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options) Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)

View File

@@ -1,6 +1,6 @@
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (c) Itay Grudev 2015 - 2018 // Copyright (c) Itay Grudev 2015 - 2020
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -44,6 +44,8 @@
# include <pwd.h> # include <pwd.h>
#endif #endif
#include <QObject>
#include <QThread>
#include <QIODevice> #include <QIODevice>
#include <QSharedMemory> #include <QSharedMemory>
#include <QByteArray> #include <QByteArray>
@@ -52,6 +54,12 @@
#include <QLocalServer> #include <QLocalServer>
#include <QLocalSocket> #include <QLocalSocket>
#include <QDir> #include <QDir>
#include <QElapsedTimer>
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
# include <QRandomGenerator>
#else
# include <QDateTime>
#endif
#include "singleapplication.h" #include "singleapplication.h"
#include "singleapplication_p.h" #include "singleapplication_p.h"
@@ -61,32 +69,74 @@
# include <lmcons.h> # include <lmcons.h>
#endif #endif
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *_q_ptr) SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *ptr)
: q_ptr(_q_ptr), : q_ptr(ptr),
memory(nullptr), memory_(nullptr),
socket(nullptr), socket_(nullptr),
server(nullptr), server_(nullptr),
instanceNumber(-1) {} instanceNumber_(-1) {}
SingleApplicationPrivate::~SingleApplicationPrivate() { SingleApplicationPrivate::~SingleApplicationPrivate() {
if (socket != nullptr) { if (socket_ != nullptr) {
socket->close(); socket_->close();
delete socket; delete socket_;
socket_ = nullptr;
} }
memory->lock(); if (memory_ != nullptr) {
InstancesInfo *inst = static_cast<InstancesInfo *>(memory->data()); memory_->lock();
if (server != nullptr) { InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
server->close(); if (server_ != nullptr) {
delete server; server_->close();
inst->primary = false; delete server_;
inst->primaryPid = -1; inst->primary = false;
inst->checksum = blockChecksum(); inst->primaryPid = -1;
} inst->primaryUser[0] = '\0';
memory->unlock(); inst->checksum = blockChecksum();
}
memory_->unlock();
delete memory_;
memory_ = nullptr;
}
}
QString SingleApplicationPrivate::getUsername() {
#ifdef Q_OS_UNIX
QString username;
#if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID)
struct passwd *pw = getpwuid(geteuid());
if (pw) {
username = QString::fromLocal8Bit(pw->pw_name);
}
#endif
if (username.isEmpty()) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
username = qEnvironmentVariable("USER");
#else
username = QString::fromLocal8Bit(qgetenv("USER"));
#endif
}
return username;
#endif
#ifdef Q_OS_WIN
wchar_t username[UNLEN + 1];
// Specifies size of the buffer on input
DWORD usernameLength = UNLEN + 1;
if (GetUserNameW(username, &usernameLength)) {
return QString::fromWCharArray(username);
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
return qEnvironmentVariable("USERNAME");
#else
return QString::fromLocal8Bit(qgetenv("USERNAME"));
#endif
#endif
delete memory;
} }
void SingleApplicationPrivate::genBlockServerName() { void SingleApplicationPrivate::genBlockServerName() {
@@ -97,11 +147,11 @@ void SingleApplicationPrivate::genBlockServerName() {
appData.addData(SingleApplication::app_t::organizationName().toUtf8()); appData.addData(SingleApplication::app_t::organizationName().toUtf8());
appData.addData(SingleApplication::app_t::organizationDomain().toUtf8()); appData.addData(SingleApplication::app_t::organizationDomain().toUtf8());
if (!(options & SingleApplication::Mode::ExcludeAppVersion)) { if (!(options_ & SingleApplication::Mode::ExcludeAppVersion)) {
appData.addData(SingleApplication::app_t::applicationVersion().toUtf8()); appData.addData(SingleApplication::app_t::applicationVersion().toUtf8());
} }
if (!(options & SingleApplication::Mode::ExcludeAppPath)) { if (!(options_ & SingleApplication::Mode::ExcludeAppPath)) {
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8()); appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8());
#else #else
@@ -110,153 +160,162 @@ void SingleApplicationPrivate::genBlockServerName() {
} }
// User level block requires a user specific data in the hash // User level block requires a user specific data in the hash
if (options & SingleApplication::Mode::User) { if (options_ & SingleApplication::Mode::User) {
#ifdef Q_OS_UNIX appData.addData(getUsername().toUtf8());
QByteArray username;
# if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID)
struct passwd *pw = getpwuid(geteuid());
if (pw) {
username = pw->pw_name;
}
# endif
if (username.isEmpty()) username = qgetenv("USER");
appData.addData(username);
#endif
#ifdef Q_OS_WIN
wchar_t username[UNLEN + 1];
// Specifies size of the buffer on input
DWORD usernameLength = UNLEN + 1;
if (GetUserNameW(username, &usernameLength)) {
appData.addData(QString::fromWCharArray(username).toUtf8());
}
else {
appData.addData(qgetenv("USERNAME"));
}
#endif
} }
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with server naming requirements. // Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with server naming requirements.
blockServerName = appData.result().toBase64().replace("/", "_"); blockServerName_ = appData.result().toBase64().replace("/", "_");
} }
void SingleApplicationPrivate::initializeMemoryBlock() { void SingleApplicationPrivate::initializeMemoryBlock() {
InstancesInfo *inst = static_cast<InstancesInfo *>(memory->data()); InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
inst->primary = false; inst->primary = false;
inst->secondary = 0; inst->secondary = 0;
inst->primaryPid = -1; inst->primaryPid = -1;
inst->primaryUser[0] = '\0';
inst->checksum = blockChecksum(); inst->checksum = blockChecksum();
} }
void SingleApplicationPrivate::startPrimary() { void SingleApplicationPrivate::startPrimary() {
Q_Q(SingleApplication); Q_Q(SingleApplication);
// Successful creation means that no main process exists
// So we start a QLocalServer to listen for connections
QLocalServer::removeServer(blockServerName);
server = new QLocalServer();
// Restrict access to the socket according to the
// SingleApplication::Mode::User flag on User level or no restrictions
if (options & SingleApplication::Mode::User) {
server->setSocketOptions(QLocalServer::UserAccessOption);
}
else {
server->setSocketOptions(QLocalServer::WorldAccessOption);
}
server->listen(blockServerName);
QObject::connect(server, &QLocalServer::newConnection, this, &SingleApplicationPrivate::slotConnectionEstablished);
// Reset the number of connections // Reset the number of connections
InstancesInfo *inst = static_cast<InstancesInfo *>(memory->data()); InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
inst->primary = true; inst->primary = true;
inst->primaryPid = q->applicationPid(); inst->primaryPid = q->applicationPid();
qstrncpy(inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser));
inst->checksum = blockChecksum(); inst->checksum = blockChecksum();
instanceNumber_ = 0;
// Successful creation means that no main process exists
// So we start a QLocalServer to listen for connections
QLocalServer::removeServer(blockServerName_);
server_ = new QLocalServer();
// Restrict access to the socket according to the SingleApplication::Mode::User flag on User level or no restrictions
if (options_ & SingleApplication::Mode::User) {
server_->setSocketOptions(QLocalServer::UserAccessOption);
}
else {
server_->setSocketOptions(QLocalServer::WorldAccessOption);
}
server_->listen(blockServerName_);
QObject::connect(server_, &QLocalServer::newConnection, this, &SingleApplicationPrivate::slotConnectionEstablished);
instanceNumber = 0;
} }
void SingleApplicationPrivate::startSecondary() {} void SingleApplicationPrivate::startSecondary() {
void SingleApplicationPrivate::connectToPrimary(const int msecs, const ConnectionType connectionType) { InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
inst->secondary += 1;
inst->checksum = blockChecksum();
instanceNumber_ = inst->secondary;
}
bool SingleApplicationPrivate::connectToPrimary(int timeout, ConnectionType connectionType) {
QElapsedTimer time;
time.start();
// Connect to the Local Server of the Primary Instance if not already connected. // Connect to the Local Server of the Primary Instance if not already connected.
if (socket == nullptr) { if (socket_ == nullptr) {
socket = new QLocalSocket(); socket_ = new QLocalSocket();
} }
// If already connected - we are done; if (socket_->state() == QLocalSocket::ConnectedState) return true;
if (socket->state() == QLocalSocket::ConnectedState)
return;
// If not connect if (socket_->state() != QLocalSocket::ConnectedState) {
if (socket->state() == QLocalSocket::UnconnectedState ||
socket->state() == QLocalSocket::ClosingState) {
socket->connectToServer(blockServerName);
}
// Wait for being connected forever {
if (socket->state() == QLocalSocket::ConnectingState) { randomSleep();
socket->waitForConnected(msecs);
if (socket_->state() != QLocalSocket::ConnectingState)
socket_->connectToServer(blockServerName_);
if (socket_->state() == QLocalSocket::ConnectingState) {
socket_->waitForConnected(timeout - time.elapsed());
}
// If connected break out of the loop
if (socket_->state() == QLocalSocket::ConnectedState) break;
// If elapsed time since start is longer than the method timeout return
if (time.elapsed() >= timeout) return false;
}
} }
// Initialisation message according to the SingleApplication protocol // Initialisation message according to the SingleApplication protocol
if (socket->state() == QLocalSocket::ConnectedState) { QByteArray initMsg;
// Notify the parent that a new instance had been started; QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
QByteArray initMsg; writeStream.setVersion(QDataStream::Qt_5_8);
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
writeStream.setVersion(QDataStream::Qt_5_6); writeStream << blockServerName_.toLatin1();
writeStream << static_cast<quint8>(connectionType);
writeStream << instanceNumber_;
writeStream << blockServerName.toLatin1();
writeStream << static_cast<quint8>(connectionType);
writeStream << instanceNumber;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
quint16 checksum = qChecksum(initMsg); quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
#else #else
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length())); quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
#endif #endif
writeStream << checksum;
// The header indicates the message length that follows writeStream << checksum;
QByteArray header;
QDataStream headerStream(&header, QIODevice::WriteOnly);
headerStream.setVersion(QDataStream::Qt_5_6); // The header indicates the message length that follows
QByteArray header;
QDataStream headerStream(&header, QIODevice::WriteOnly);
headerStream.setVersion(QDataStream::Qt_5_8);
headerStream << static_cast<quint64>(initMsg.length());
headerStream << static_cast<quint64>(initMsg.length()); socket_->write(header);
socket_->write(initMsg);
socket_->flush();
if (socket_->waitForBytesWritten(timeout - time.elapsed())) return true;
return false;
socket->write(header);
socket->write(initMsg);
socket->flush();
socket->waitForBytesWritten(msecs);
}
} }
quint16 SingleApplicationPrivate::blockChecksum() { quint16 SingleApplicationPrivate::blockChecksum() {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
quint16 checksum = qChecksum(QByteArray(static_cast<const char *>(memory->constData()), offsetof(InstancesInfo, checksum))); quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum)));
#else #else
quint16 checksum = qChecksum(static_cast<const char *>(memory->constData()), offsetof(InstancesInfo, checksum)); quint16 checksum = qChecksum(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum));
#endif #endif
return checksum; return checksum;
} }
qint64 SingleApplicationPrivate::primaryPid() { qint64 SingleApplicationPrivate::primaryPid() {
qint64 pid; memory_->lock();
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
memory->lock(); qint64 pid = inst->primaryPid;
InstancesInfo *inst = static_cast<InstancesInfo *>(memory->data()); memory_->unlock();
pid = inst->primaryPid;
memory->unlock();
return pid; return pid;
}
QString SingleApplicationPrivate::primaryUser() {
memory_->lock();
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
QByteArray username = inst->primaryUser;
memory_->unlock();
return QString::fromUtf8(username);
} }
/** /**
@@ -264,43 +323,41 @@ qint64 SingleApplicationPrivate::primaryPid() {
*/ */
void SingleApplicationPrivate::slotConnectionEstablished() { void SingleApplicationPrivate::slotConnectionEstablished() {
QLocalSocket *nextConnSocket = server->nextPendingConnection(); QLocalSocket *nextConnSocket = server_->nextPendingConnection();
connectionMap.insert(nextConnSocket, ConnectionInfo()); connectionMap_.insert(nextConnSocket, ConnectionInfo());
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, [nextConnSocket, this]() {
[nextConnSocket, this]() { auto &info = connectionMap_[nextConnSocket];
auto &info = connectionMap[nextConnSocket]; Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId);
Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId); });
});
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, QObject::connect(nextConnSocket, &QLocalSocket::disconnected, [nextConnSocket, this]() {
[nextConnSocket, this]() { connectionMap_.remove(nextConnSocket);
connectionMap.remove(nextConnSocket); nextConnSocket->deleteLater();
nextConnSocket->deleteLater(); });
});
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, [nextConnSocket, this]() {
auto &info = connectionMap_[nextConnSocket];
switch (info.stage) {
case StageHeader:
readInitMessageHeader(nextConnSocket);
break;
case StageBody:
readInitMessageBody(nextConnSocket);
break;
case StageConnected:
Q_EMIT this->slotDataAvailable(nextConnSocket, info.instanceId);
break;
default:
break;
};
});
QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
[nextConnSocket, this]() {
auto &info = connectionMap[nextConnSocket];
switch (info.stage) {
case StageHeader:
readInitMessageHeader(nextConnSocket);
break;
case StageBody:
readInitMessageBody(nextConnSocket);
break;
case StageConnected:
Q_EMIT this->slotDataAvailable(nextConnSocket, info.instanceId);
break;
default:
break;
};
});
} }
void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) { void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
if (!connectionMap.contains(sock)) { if (!connectionMap_.contains(sock)) {
return; return;
} }
@@ -309,30 +366,30 @@ void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
} }
QDataStream headerStream(sock); QDataStream headerStream(sock);
headerStream.setVersion(QDataStream::Qt_5_8);
headerStream.setVersion(QDataStream::Qt_5_6);
// Read the header to know the message length // Read the header to know the message length
quint64 msgLen = 0; quint64 msgLen = 0;
headerStream >> msgLen; headerStream >> msgLen;
ConnectionInfo &info = connectionMap[sock]; ConnectionInfo &info = connectionMap_[sock];
info.stage = StageBody; info.stage = StageBody;
info.msgLen = msgLen; info.msgLen = msgLen;
if (sock->bytesAvailable() >= static_cast<qint64>(msgLen)) { if (sock->bytesAvailable() >= static_cast<qint64>(msgLen)) {
readInitMessageBody(sock); readInitMessageBody(sock);
} }
} }
void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) { void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
Q_Q(SingleApplication); Q_Q(SingleApplication);
if (!connectionMap.contains(sock)) { if (!connectionMap_.contains(sock)) {
return; return;
} }
ConnectionInfo &info = connectionMap[sock]; ConnectionInfo &info = connectionMap_[sock];
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) { if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
return; return;
} }
@@ -340,8 +397,7 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
// Read the message body // Read the message body
QByteArray msgBytes = sock->read(info.msgLen); QByteArray msgBytes = sock->read(info.msgLen);
QDataStream readStream(msgBytes); QDataStream readStream(msgBytes);
readStream.setVersion(QDataStream::Qt_5_8);
readStream.setVersion(QDataStream::Qt_5_6);
// server name // server name
QByteArray latin1Name; QByteArray latin1Name;
@@ -362,12 +418,12 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
readStream >> msgChecksum; readStream >> msgChecksum;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
const quint16 actualChecksum = qChecksum(msgBytes); const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
#else #else
const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16))); const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
#endif #endif
bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName && msgChecksum == actualChecksum; bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName_ && msgChecksum == actualChecksum;
if (!isValid) { if (!isValid) {
sock->close(); sock->close();
@@ -377,23 +433,38 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
info.instanceId = instanceId; info.instanceId = instanceId;
info.stage = StageConnected; info.stage = StageConnected;
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options & SingleApplication::Mode::SecondaryNotification)) { if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplication::Mode::SecondaryNotification)) {
Q_EMIT q->instanceStarted(); Q_EMIT q->instanceStarted();
} }
if (sock->bytesAvailable() > 0) { if (sock->bytesAvailable() > 0) {
Q_EMIT this->slotDataAvailable(sock, instanceId); Q_EMIT this->slotDataAvailable(sock, instanceId);
} }
} }
void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) { void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
Q_Q(SingleApplication); Q_Q(SingleApplication);
Q_EMIT q->receivedMessage(instanceId, dataSocket->readAll()); Q_EMIT q->receivedMessage(instanceId, dataSocket->readAll());
} }
void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) { void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
if (closedSocket->bytesAvailable() > 0) if (closedSocket->bytesAvailable() > 0) {
Q_EMIT slotDataAvailable(closedSocket, instanceId); Q_EMIT slotDataAvailable(closedSocket, instanceId);
}
}
void SingleApplicationPrivate::randomSleep() {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
QThread::msleep(QRandomGenerator::global()->bounded(8u, 18u));
#else
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
QThread::msleep(8 + static_cast<unsigned long>(static_cast<float>(qrand()) / RAND_MAX * 10));
#endif
} }

View File

@@ -1,6 +1,6 @@
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (c) Itay Grudev 2015 - 2016 // Copyright (c) Itay Grudev 2015 - 2020
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -36,6 +36,7 @@
#include <QtGlobal> #include <QtGlobal>
#include <QObject> #include <QObject>
#include <QString>
#include <QMap> #include <QMap>
#include "singleapplication.h" #include "singleapplication.h"
@@ -48,6 +49,7 @@ struct InstancesInfo {
bool primary; bool primary;
quint32 secondary; quint32 secondary;
qint64 primaryPid; qint64 primaryPid;
char primaryUser[128];
quint16 checksum; quint16 checksum;
}; };
@@ -60,6 +62,7 @@ struct ConnectionInfo {
class SingleApplicationPrivate : public QObject { class SingleApplicationPrivate : public QObject {
Q_OBJECT Q_OBJECT
public: public:
enum ConnectionType : quint8 { enum ConnectionType : quint8 {
InvalidConnection = 0, InvalidConnection = 0,
@@ -74,32 +77,35 @@ class SingleApplicationPrivate : public QObject {
}; };
Q_DECLARE_PUBLIC(SingleApplication) Q_DECLARE_PUBLIC(SingleApplication)
explicit SingleApplicationPrivate(SingleApplication *_q_ptr); explicit SingleApplicationPrivate(SingleApplication *ptr);
~SingleApplicationPrivate() override; ~SingleApplicationPrivate() override;
QString getUsername();
void genBlockServerName(); void genBlockServerName();
void initializeMemoryBlock(); void initializeMemoryBlock();
void startPrimary(); void startPrimary();
void startSecondary(); void startSecondary();
void connectToPrimary(const int msecs, const ConnectionType connectionType); bool connectToPrimary(const int msecs, const ConnectionType connectionType);
quint16 blockChecksum(); quint16 blockChecksum();
qint64 primaryPid(); qint64 primaryPid();
QString primaryUser();
void readInitMessageHeader(QLocalSocket *socket); void readInitMessageHeader(QLocalSocket *socket);
void readInitMessageBody(QLocalSocket *socket); void readInitMessageBody(QLocalSocket *socket);
void randomSleep();
SingleApplication *q_ptr; SingleApplication *q_ptr;
QSharedMemory *memory; QSharedMemory *memory_;
QLocalSocket *socket; QLocalSocket *socket_;
QLocalServer *server; QLocalServer *server_;
quint32 instanceNumber; quint32 instanceNumber_;
QString blockServerName; QString blockServerName_;
SingleApplication::Options options; SingleApplication::Options options_;
QMap<QLocalSocket *, ConnectionInfo> connectionMap; QMap<QLocalSocket*, ConnectionInfo> connectionMap_;
public slots: public slots:
void slotConnectionEstablished(); void slotConnectionEstablished();
void slotDataAvailable(QLocalSocket *, const quint32); void slotDataAvailable(QLocalSocket*, const quint32);
void slotClientConnectionClosed(QLocalSocket *, const quint32); void slotClientConnectionClosed(QLocalSocket*, const quint32);
}; };
#endif // SINGLEAPPLICATION_P_H #endif // SINGLEAPPLICATION_P_H

View File

@@ -1,6 +1,6 @@
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (c) Itay Grudev 2015 - 2018 // Copyright (c) Itay Grudev 2015 - 2020
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -42,142 +42,200 @@
#include <QByteArray> #include <QByteArray>
#include <QElapsedTimer> #include <QElapsedTimer>
#include <QtDebug> #include <QtDebug>
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
# include <QRandomGenerator>
#else
# include <QDateTime>
#endif
#include "singlecoreapplication.h" #include "singlecoreapplication.h"
#include "singlecoreapplication_p.h" #include "singlecoreapplication_p.h"
/** /**
* @brief Constructor. Checks and fires up LocalServer or closes the program * @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
* if another instance already exists
* @param argc * @param argc
* @param argv * @param argv
* @param {bool} allowSecondaryInstances * @param allowSecondary Whether to enable secondary instance support
* @param options Optional flags to toggle specific behaviour
* @param timeout Maximum time blocking functions are allowed during app load
*/ */
SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allowSecondary, Options options, int timeout) SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allowSecondary, Options options, int timeout)
: app_t(argc, argv), d_ptr(new SingleCoreApplicationPrivate(this)) { : app_t(argc, argv),
d_ptr(new SingleCoreApplicationPrivate(this)) {
Q_D(SingleCoreApplication); Q_D(SingleCoreApplication);
// Store the current mode of the program // Store the current mode of the program
d->options = options; d->options_ = options;
// Generating an application ID used for identifying the shared memory block and QLocalServer // Generating an application ID used for identifying the shared memory block and QLocalServer
d->genBlockServerName(); d->genBlockServerName();
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
d->randomSleep();
#ifdef Q_OS_UNIX #ifdef Q_OS_UNIX
// By explicitly attaching it and then deleting it we make sure that the // By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
// memory is deleted even after the process has crashed on Unix. d->memory_ = new QSharedMemory(d->blockServerName_);
d->memory = new QSharedMemory(d->blockServerName); d->memory_->attach();
d->memory->attach(); delete d->memory_;
delete d->memory;
#endif #endif
// Guarantee thread safe behaviour with a shared memory block. // Guarantee thread safe behaviour with a shared memory block.
d->memory = new QSharedMemory(d->blockServerName); d->memory_ = new QSharedMemory(d->blockServerName_);
// Create a shared memory block // Create a shared memory block
if (d->memory->create(sizeof(InstancesInfo))) { if (d->memory_->create(sizeof(InstancesInfo))) {
// Initialize the shared memory block // Initialize the shared memory block
d->memory->lock(); if (!d->memory_->lock()) {
qCritical() << "SingleCoreApplication: Unable to lock memory block after create.";
abortSafely();
}
d->initializeMemoryBlock(); d->initializeMemoryBlock();
d->memory->unlock();
} }
else { else {
// Attempt to attach to the memory segment if (d->memory_->error() == QSharedMemory::AlreadyExists) {
if (!d->memory->attach()) { // Attempt to attach to the memory segment
qCritical() << "SingleCoreApplication: Unable to attach to shared memory block."; if (!d->memory_->attach()) {
qCritical() << d->memory->errorString(); qCritical() << "SingleCoreApplication: Unable to attach to shared memory block.";
delete d; abortSafely();
::exit(EXIT_FAILURE); }
if (!d->memory_->lock()) {
qCritical() << "SingleCoreApplication: Unable to lock memory block after attach.";
abortSafely();
}
}
else {
qCritical() << "SingleCoreApplication: Unable to create block.";
abortSafely();
} }
} }
InstancesInfo *inst = static_cast<InstancesInfo *>(d->memory->data()); InstancesInfo *inst = static_cast<InstancesInfo*>(d->memory_->data());
QElapsedTimer time; QElapsedTimer time;
time.start(); time.start();
// Make sure the shared memory block is initialised and in consistent state // Make sure the shared memory block is initialised and in consistent state
while (true) { forever {
d->memory->lock(); // If the shared memory block's checksum is valid continue
if (d->blockChecksum() == inst->checksum) break; if (d->blockChecksum() == inst->checksum) break;
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
if (time.elapsed() > 5000) { if (time.elapsed() > 5000) {
qWarning() << "SingleCoreApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure."; qWarning() << "SingleCoreApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
d->initializeMemoryBlock(); d->initializeMemoryBlock();
} }
d->memory->unlock(); // Otherwise wait for a random period and try again.
// The random sleep here limits the probability of a collision between two racing apps and allows the app to initialise faster
// Random sleep here limits the probability of a collision between two racing apps if (!d->memory_->unlock()) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) qDebug() << "SingleCoreApplication: Unable to unlock memory for random wait.";
QThread::sleep(QRandomGenerator::global()->bounded(8u, 18u)); qDebug() << d->memory_->errorString();
#else }
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max()); d->randomSleep();
QThread::sleep(8 + static_cast<unsigned long>(static_cast<float>(qrand()) / RAND_MAX * 10)); if (!d->memory_->lock()) {
#endif qCritical() << "SingleCoreApplication: Unable to lock memory after random wait.";
abortSafely();
}
} }
if (inst->primary == false) { if (inst->primary == false) {
d->startPrimary(); d->startPrimary();
d->memory->unlock(); if (!d->memory_->unlock()) {
qDebug() << "SingleCoreApplication: Unable to unlock memory after primary start.";
qDebug() << d->memory_->errorString();
}
return; return;
} }
// Check if another instance can be started // Check if another instance can be started
if (allowSecondary) { if (allowSecondary) {
inst->secondary += 1;
inst->checksum = d->blockChecksum();
d->instanceNumber = inst->secondary;
d->startSecondary(); d->startSecondary();
if (d->options & Mode::SecondaryNotification) { if (d->options_ & Mode::SecondaryNotification) {
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::SecondaryInstance); d->connectToPrimary(timeout, SingleCoreApplicationPrivate::SecondaryInstance);
} }
d->memory->unlock(); if (!d->memory_->unlock()) {
qDebug() << "SingleCoreApplication: Unable to unlock memory after secondary start.";
qDebug() << d->memory_->errorString();
}
return; return;
} }
d->memory->unlock(); if (!d->memory_->unlock()) {
qDebug() << "SingleCoreApplication: Unable to unlock memory at end of execution.";
qDebug() << d->memory_->errorString();
}
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::NewInstance); d->connectToPrimary(timeout, SingleCoreApplicationPrivate::NewInstance);
delete d; delete d;
::exit(EXIT_SUCCESS); ::exit(EXIT_SUCCESS);
} }
/**
* @brief Destructor
*/
SingleCoreApplication::~SingleCoreApplication() { SingleCoreApplication::~SingleCoreApplication() {
Q_D(SingleCoreApplication); Q_D(SingleCoreApplication);
delete d; delete d;
} }
/**
* Checks if the current application instance is primary.
* @return Returns true if the instance is primary, false otherwise.
*/
bool SingleCoreApplication::isPrimary() { bool SingleCoreApplication::isPrimary() {
Q_D(SingleCoreApplication); Q_D(SingleCoreApplication);
return d->server != nullptr; return d->server_ != nullptr;
} }
/**
* Checks if the current application instance is secondary.
* @return Returns true if the instance is secondary, false otherwise.
*/
bool SingleCoreApplication::isSecondary() { bool SingleCoreApplication::isSecondary() {
Q_D(SingleCoreApplication); Q_D(SingleCoreApplication);
return d->server == nullptr; return d->server_ == nullptr;
} }
/**
* Allows you to identify an instance by returning unique consecutive instance ids.
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
* @return Returns a unique instance id.
*/
quint32 SingleCoreApplication::instanceId() { quint32 SingleCoreApplication::instanceId() {
Q_D(SingleCoreApplication); Q_D(SingleCoreApplication);
return d->instanceNumber; return d->instanceNumber_;
} }
/**
* Returns the OS PID (Process Identifier) of the process running the primary instance.
* Especially useful when SingleCoreApplication is coupled with OS. specific APIs.
* @return Returns the primary instance PID.
*/
qint64 SingleCoreApplication::primaryPid() { qint64 SingleCoreApplication::primaryPid() {
Q_D(SingleCoreApplication); Q_D(SingleCoreApplication);
return d->primaryPid(); return d->primaryPid();
} }
/**
* Returns the username the primary instance is running as.
* @return Returns the username the primary instance is running as.
*/
QString SingleCoreApplication::primaryUser() {
Q_D(SingleCoreApplication);
return d->primaryUser();
}
/**
* Returns the username the current instance is running as.
* @return Returns the username the current instance is running as.
*/
QString SingleCoreApplication::currentUser() {
Q_D(SingleCoreApplication);
return d->getUsername();
}
/**
* Sends message to the Primary Instance.
* @param message The message to send.
* @param timeout the maximum timeout in milliseconds for blocking functions.
* @return true if the message was sent successfuly, false otherwise.
*/
bool SingleCoreApplication::sendMessage(QByteArray message, int timeout) { bool SingleCoreApplication::sendMessage(QByteArray message, int timeout) {
Q_D(SingleCoreApplication); Q_D(SingleCoreApplication);
@@ -186,10 +244,26 @@ bool SingleCoreApplication::sendMessage(QByteArray message, int timeout) {
if (isPrimary()) return false; if (isPrimary()) return false;
// Make sure the socket is connected // Make sure the socket is connected
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::Reconnect); if (!d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect))
return false;
d->socket->write(message); d->socket_->write(message);
bool dataWritten = d->socket->waitForBytesWritten(timeout); const bool dataWritten = d->socket_->waitForBytesWritten(timeout);
d->socket->flush(); d->socket_->flush();
return dataWritten; return dataWritten;
}
/**
* Cleans up the shared memory block and exits with a failure.
* This function halts program execution.
*/
void SingleCoreApplication::abortSafely() {
Q_D(SingleCoreApplication);
qCritical() << "SingleCoreApplication: " << d->memory_->error() << d->memory_->errorString();
delete d;
::exit(EXIT_FAILURE);
} }

View File

@@ -1,6 +1,6 @@
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (c) Itay Grudev 2015 - 2018 // Copyright (c) Itay Grudev 2015 - 2020
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -42,8 +42,7 @@
class SingleCoreApplicationPrivate; class SingleCoreApplicationPrivate;
/** /**
* @brief The SingleCoreApplication class handles multiple instances of the same * @brief The SingleCoreApplication class handles multipe instances of the same Application
* Application
* @see QCoreApplication * @see QCoreApplication
*/ */
class SingleCoreApplication : public QCoreApplication { class SingleCoreApplication : public QCoreApplication {
@@ -89,9 +88,8 @@ class SingleCoreApplication : public QCoreApplication {
* operations. It does not guarantee that the SingleCoreApplication * operations. It does not guarantee that the SingleCoreApplication
* initialisation will be completed in given time, though is a good hint. * initialisation will be completed in given time, though is a good hint.
* Usually 4*timeout would be the worst case (fail) scenario. * Usually 4*timeout would be the worst case (fail) scenario.
* @see See the corresponding QAPPLICATION_CLASS constructor for reference
*/ */
explicit SingleCoreApplication(int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000); explicit SingleCoreApplication(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000);
~SingleCoreApplication() override; ~SingleCoreApplication() override;
/** /**
@@ -118,6 +116,18 @@ class SingleCoreApplication : public QCoreApplication {
*/ */
qint64 primaryPid(); qint64 primaryPid();
/**
* @brief Returns the username of the user running the primary instance
* @returns {QString}
*/
QString primaryUser();
/**
* @brief Returns the username of the current user
* @returns {QString}
*/
QString currentUser();
/** /**
* @brief Sends a message to the primary instance. Returns true on success. * @brief Sends a message to the primary instance. Returns true on success.
* @param {int} timeout - Timeout for connecting * @param {int} timeout - Timeout for connecting
@@ -134,6 +144,7 @@ class SingleCoreApplication : public QCoreApplication {
private: private:
SingleCoreApplicationPrivate *d_ptr; SingleCoreApplicationPrivate *d_ptr;
Q_DECLARE_PRIVATE(SingleCoreApplication) Q_DECLARE_PRIVATE(SingleCoreApplication)
void abortSafely();
}; };
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleCoreApplication::Options) Q_DECLARE_OPERATORS_FOR_FLAGS(SingleCoreApplication::Options)

View File

@@ -1,6 +1,6 @@
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (c) Itay Grudev 2015 - 2018 // Copyright (c) Itay Grudev 2015 - 2020
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -44,6 +44,8 @@
# include <pwd.h> # include <pwd.h>
#endif #endif
#include <QObject>
#include <QThread>
#include <QIODevice> #include <QIODevice>
#include <QSharedMemory> #include <QSharedMemory>
#include <QByteArray> #include <QByteArray>
@@ -52,6 +54,12 @@
#include <QLocalServer> #include <QLocalServer>
#include <QLocalSocket> #include <QLocalSocket>
#include <QDir> #include <QDir>
#include <QElapsedTimer>
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
# include <QRandomGenerator>
#else
# include <QDateTime>
#endif
#include "singlecoreapplication.h" #include "singlecoreapplication.h"
#include "singlecoreapplication_p.h" #include "singlecoreapplication_p.h"
@@ -61,32 +69,74 @@
# include <lmcons.h> # include <lmcons.h>
#endif #endif
SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *_q_ptr) SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *ptr)
: q_ptr(_q_ptr), : q_ptr(ptr),
memory(nullptr), memory_(nullptr),
socket(nullptr), socket_(nullptr),
server(nullptr), server_(nullptr),
instanceNumber(-1) {} instanceNumber_(-1) {}
SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate() { SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate() {
if (socket != nullptr) { if (socket_ != nullptr) {
socket->close(); socket_->close();
delete socket; delete socket_;
socket_ = nullptr;
} }
memory->lock(); if (memory_ != nullptr) {
InstancesInfo *inst = static_cast<InstancesInfo *>(memory->data()); memory_->lock();
if (server != nullptr) { InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
server->close(); if (server_ != nullptr) {
delete server; server_->close();
inst->primary = false; delete server_;
inst->primaryPid = -1; inst->primary = false;
inst->checksum = blockChecksum(); inst->primaryPid = -1;
} inst->primaryUser[0] = '\0';
memory->unlock(); inst->checksum = blockChecksum();
}
memory_->unlock();
delete memory_;
memory_ = nullptr;
}
}
QString SingleCoreApplicationPrivate::getUsername() {
#ifdef Q_OS_UNIX
QString username;
#if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID)
struct passwd *pw = getpwuid(geteuid());
if (pw) {
username = QString::fromLocal8Bit(pw->pw_name);
}
#endif
if (username.isEmpty()) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
username = qEnvironmentVariable("USER");
#else
username = QString::fromLocal8Bit(qgetenv("USER"));
#endif
}
return username;
#endif
#ifdef Q_OS_WIN
wchar_t username[UNLEN + 1];
// Specifies size of the buffer on input
DWORD usernameLength = UNLEN + 1;
if (GetUserNameW(username, &usernameLength)) {
return QString::fromWCharArray(username);
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
return qEnvironmentVariable("USERNAME");
#else
return QString::fromLocal8Bit(qgetenv("USERNAME"));
#endif
#endif
delete memory;
} }
void SingleCoreApplicationPrivate::genBlockServerName() { void SingleCoreApplicationPrivate::genBlockServerName() {
@@ -97,11 +147,11 @@ void SingleCoreApplicationPrivate::genBlockServerName() {
appData.addData(SingleCoreApplication::app_t::organizationName().toUtf8()); appData.addData(SingleCoreApplication::app_t::organizationName().toUtf8());
appData.addData(SingleCoreApplication::app_t::organizationDomain().toUtf8()); appData.addData(SingleCoreApplication::app_t::organizationDomain().toUtf8());
if (!(options & SingleCoreApplication::Mode::ExcludeAppVersion)) { if (!(options_ & SingleCoreApplication::Mode::ExcludeAppVersion)) {
appData.addData(SingleCoreApplication::app_t::applicationVersion().toUtf8()); appData.addData(SingleCoreApplication::app_t::applicationVersion().toUtf8());
} }
if (!(options & SingleCoreApplication::Mode::ExcludeAppPath)) { if (!(options_ & SingleCoreApplication::Mode::ExcludeAppPath)) {
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toLower().toUtf8()); appData.addData(SingleCoreApplication::app_t::applicationFilePath().toLower().toUtf8());
#else #else
@@ -110,153 +160,162 @@ void SingleCoreApplicationPrivate::genBlockServerName() {
} }
// User level block requires a user specific data in the hash // User level block requires a user specific data in the hash
if (options & SingleCoreApplication::Mode::User) { if (options_ & SingleCoreApplication::Mode::User) {
#ifdef Q_OS_UNIX appData.addData(getUsername().toUtf8());
QByteArray username;
# if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID)
struct passwd *pw = getpwuid(geteuid());
if (pw) {
username = pw->pw_name;
}
# endif
if (username.isEmpty()) username = qgetenv("USER");
appData.addData(username);
#endif
#ifdef Q_OS_WIN
wchar_t username[UNLEN + 1];
// Specifies size of the buffer on input
DWORD usernameLength = UNLEN + 1;
if (GetUserNameW(username, &usernameLength)) {
appData.addData(QString::fromWCharArray(username).toUtf8());
}
else {
appData.addData(qgetenv("USERNAME"));
}
#endif
} }
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with server naming requirements. // Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with server naming requirements.
blockServerName = appData.result().toBase64().replace("/", "_"); blockServerName_ = appData.result().toBase64().replace("/", "_");
} }
void SingleCoreApplicationPrivate::initializeMemoryBlock() { void SingleCoreApplicationPrivate::initializeMemoryBlock() {
InstancesInfo *inst = static_cast<InstancesInfo *>(memory->data()); InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
inst->primary = false; inst->primary = false;
inst->secondary = 0; inst->secondary = 0;
inst->primaryPid = -1; inst->primaryPid = -1;
inst->primaryUser[0] = '\0';
inst->checksum = blockChecksum(); inst->checksum = blockChecksum();
} }
void SingleCoreApplicationPrivate::startPrimary() { void SingleCoreApplicationPrivate::startPrimary() {
Q_Q(SingleCoreApplication); Q_Q(SingleCoreApplication);
// Successful creation means that no main process exists
// So we start a QLocalServer to listen for connections
QLocalServer::removeServer(blockServerName);
server = new QLocalServer();
// Restrict access to the socket according to the
// SingleCoreApplication::Mode::User flag on User level or no restrictions
if (options & SingleCoreApplication::Mode::User) {
server->setSocketOptions(QLocalServer::UserAccessOption);
}
else {
server->setSocketOptions(QLocalServer::WorldAccessOption);
}
server->listen(blockServerName);
QObject::connect(server, &QLocalServer::newConnection, this, &SingleCoreApplicationPrivate::slotConnectionEstablished);
// Reset the number of connections // Reset the number of connections
InstancesInfo *inst = static_cast<InstancesInfo *>(memory->data()); InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
inst->primary = true; inst->primary = true;
inst->primaryPid = q->applicationPid(); inst->primaryPid = q->applicationPid();
qstrncpy(inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser));
inst->checksum = blockChecksum(); inst->checksum = blockChecksum();
instanceNumber_ = 0;
// Successful creation means that no main process exists
// So we start a QLocalServer to listen for connections
QLocalServer::removeServer(blockServerName_);
server_ = new QLocalServer();
// Restrict access to the socket according to the SingleCoreApplication::Mode::User flag on User level or no restrictions
if (options_ & SingleCoreApplication::Mode::User) {
server_->setSocketOptions(QLocalServer::UserAccessOption);
}
else {
server_->setSocketOptions(QLocalServer::WorldAccessOption);
}
server_->listen(blockServerName_);
QObject::connect(server_, &QLocalServer::newConnection, this, &SingleCoreApplicationPrivate::slotConnectionEstablished);
instanceNumber = 0;
} }
void SingleCoreApplicationPrivate::startSecondary() {} void SingleCoreApplicationPrivate::startSecondary() {
void SingleCoreApplicationPrivate::connectToPrimary(const int msecs, const ConnectionType connectionType) { InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
inst->secondary += 1;
inst->checksum = blockChecksum();
instanceNumber_ = inst->secondary;
}
bool SingleCoreApplicationPrivate::connectToPrimary(int timeout, ConnectionType connectionType) {
QElapsedTimer time;
time.start();
// Connect to the Local Server of the Primary Instance if not already connected. // Connect to the Local Server of the Primary Instance if not already connected.
if (socket == nullptr) { if (socket_ == nullptr) {
socket = new QLocalSocket(); socket_ = new QLocalSocket();
} }
// If already connected - we are done; if (socket_->state() == QLocalSocket::ConnectedState) return true;
if (socket->state() == QLocalSocket::ConnectedState)
return;
// If not connect if (socket_->state() != QLocalSocket::ConnectedState) {
if (socket->state() == QLocalSocket::UnconnectedState ||
socket->state() == QLocalSocket::ClosingState) {
socket->connectToServer(blockServerName);
}
// Wait for being connected forever {
if (socket->state() == QLocalSocket::ConnectingState) { randomSleep();
socket->waitForConnected(msecs);
if (socket_->state() != QLocalSocket::ConnectingState)
socket_->connectToServer(blockServerName_);
if (socket_->state() == QLocalSocket::ConnectingState) {
socket_->waitForConnected(timeout - time.elapsed());
}
// If connected break out of the loop
if (socket_->state() == QLocalSocket::ConnectedState) break;
// If elapsed time since start is longer than the method timeout return
if (time.elapsed() >= timeout) return false;
}
} }
// Initialisation message according to the SingleCoreApplication protocol // Initialisation message according to the SingleCoreApplication protocol
if (socket->state() == QLocalSocket::ConnectedState) { QByteArray initMsg;
// Notify the parent that a new instance had been started; QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
QByteArray initMsg; writeStream.setVersion(QDataStream::Qt_5_8);
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
writeStream.setVersion(QDataStream::Qt_5_6); writeStream << blockServerName_.toLatin1();
writeStream << static_cast<quint8>(connectionType);
writeStream << instanceNumber_;
writeStream << blockServerName.toLatin1();
writeStream << static_cast<quint8>(connectionType);
writeStream << instanceNumber;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
quint16 checksum = qChecksum(initMsg); quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
#else #else
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length())); quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
#endif #endif
writeStream << checksum;
// The header indicates the message length that follows writeStream << checksum;
QByteArray header;
QDataStream headerStream(&header, QIODevice::WriteOnly);
headerStream.setVersion(QDataStream::Qt_5_6); // The header indicates the message length that follows
QByteArray header;
QDataStream headerStream(&header, QIODevice::WriteOnly);
headerStream.setVersion(QDataStream::Qt_5_8);
headerStream << static_cast<quint64>(initMsg.length());
headerStream << static_cast<quint64>(initMsg.length()); socket_->write(header);
socket_->write(initMsg);
socket_->flush();
if (socket_->waitForBytesWritten(timeout - time.elapsed())) return true;
return false;
socket->write(header);
socket->write(initMsg);
socket->flush();
socket->waitForBytesWritten(msecs);
}
} }
quint16 SingleCoreApplicationPrivate::blockChecksum() { quint16 SingleCoreApplicationPrivate::blockChecksum() {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
quint16 checksum = qChecksum(QByteArray(static_cast<const char *>(memory->constData()), offsetof(InstancesInfo, checksum))); quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum)));
#else #else
quint16 checksum = qChecksum(static_cast<const char *>(memory->constData()), offsetof(InstancesInfo, checksum)); quint16 checksum = qChecksum(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum));
#endif #endif
return checksum; return checksum;
} }
qint64 SingleCoreApplicationPrivate::primaryPid() { qint64 SingleCoreApplicationPrivate::primaryPid() {
qint64 pid; memory_->lock();
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
memory->lock(); qint64 pid = inst->primaryPid;
InstancesInfo *inst = static_cast<InstancesInfo *>(memory->data()); memory_->unlock();
pid = inst->primaryPid;
memory->unlock();
return pid; return pid;
}
QString SingleCoreApplicationPrivate::primaryUser() {
memory_->lock();
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
QByteArray username = inst->primaryUser;
memory_->unlock();
return QString::fromUtf8(username);
} }
/** /**
@@ -264,43 +323,41 @@ qint64 SingleCoreApplicationPrivate::primaryPid() {
*/ */
void SingleCoreApplicationPrivate::slotConnectionEstablished() { void SingleCoreApplicationPrivate::slotConnectionEstablished() {
QLocalSocket *nextConnSocket = server->nextPendingConnection(); QLocalSocket *nextConnSocket = server_->nextPendingConnection();
connectionMap.insert(nextConnSocket, ConnectionInfo()); connectionMap_.insert(nextConnSocket, ConnectionInfo());
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, [nextConnSocket, this]() {
[nextConnSocket, this]() { auto &info = connectionMap_[nextConnSocket];
auto &info = connectionMap[nextConnSocket]; Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId);
Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId); });
});
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, QObject::connect(nextConnSocket, &QLocalSocket::disconnected, [nextConnSocket, this]() {
[nextConnSocket, this]() { connectionMap_.remove(nextConnSocket);
connectionMap.remove(nextConnSocket); nextConnSocket->deleteLater();
nextConnSocket->deleteLater(); });
});
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, [nextConnSocket, this]() {
auto &info = connectionMap_[nextConnSocket];
switch (info.stage) {
case StageHeader:
readInitMessageHeader(nextConnSocket);
break;
case StageBody:
readInitMessageBody(nextConnSocket);
break;
case StageConnected:
Q_EMIT this->slotDataAvailable(nextConnSocket, info.instanceId);
break;
default:
break;
};
});
QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
[nextConnSocket, this]() {
auto &info = connectionMap[nextConnSocket];
switch (info.stage) {
case StageHeader:
readInitMessageHeader(nextConnSocket);
break;
case StageBody:
readInitMessageBody(nextConnSocket);
break;
case StageConnected:
Q_EMIT this->slotDataAvailable(nextConnSocket, info.instanceId);
break;
default:
break;
};
});
} }
void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) { void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
if (!connectionMap.contains(sock)) { if (!connectionMap_.contains(sock)) {
return; return;
} }
@@ -309,30 +366,30 @@ void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
} }
QDataStream headerStream(sock); QDataStream headerStream(sock);
headerStream.setVersion(QDataStream::Qt_5_8);
headerStream.setVersion(QDataStream::Qt_5_6);
// Read the header to know the message length // Read the header to know the message length
quint64 msgLen = 0; quint64 msgLen = 0;
headerStream >> msgLen; headerStream >> msgLen;
ConnectionInfo &info = connectionMap[sock]; ConnectionInfo &info = connectionMap_[sock];
info.stage = StageBody; info.stage = StageBody;
info.msgLen = msgLen; info.msgLen = msgLen;
if (sock->bytesAvailable() >= static_cast<qint64>(msgLen)) { if (sock->bytesAvailable() >= static_cast<qint64>(msgLen)) {
readInitMessageBody(sock); readInitMessageBody(sock);
} }
} }
void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) { void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
Q_Q(SingleCoreApplication); Q_Q(SingleCoreApplication);
if (!connectionMap.contains(sock)) { if (!connectionMap_.contains(sock)) {
return; return;
} }
ConnectionInfo &info = connectionMap[sock]; ConnectionInfo &info = connectionMap_[sock];
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) { if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
return; return;
} }
@@ -340,8 +397,7 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
// Read the message body // Read the message body
QByteArray msgBytes = sock->read(info.msgLen); QByteArray msgBytes = sock->read(info.msgLen);
QDataStream readStream(msgBytes); QDataStream readStream(msgBytes);
readStream.setVersion(QDataStream::Qt_5_8);
readStream.setVersion(QDataStream::Qt_5_6);
// server name // server name
QByteArray latin1Name; QByteArray latin1Name;
@@ -362,12 +418,12 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
readStream >> msgChecksum; readStream >> msgChecksum;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
const quint16 actualChecksum = qChecksum(msgBytes); const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
#else #else
const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16))); const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
#endif #endif
bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName && msgChecksum == actualChecksum; bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName_ && msgChecksum == actualChecksum;
if (!isValid) { if (!isValid) {
sock->close(); sock->close();
@@ -377,23 +433,38 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
info.instanceId = instanceId; info.instanceId = instanceId;
info.stage = StageConnected; info.stage = StageConnected;
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options & SingleCoreApplication::Mode::SecondaryNotification)) { if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleCoreApplication::Mode::SecondaryNotification)) {
Q_EMIT q->instanceStarted(); Q_EMIT q->instanceStarted();
} }
if (sock->bytesAvailable() > 0) { if (sock->bytesAvailable() > 0) {
Q_EMIT this->slotDataAvailable(sock, instanceId); Q_EMIT this->slotDataAvailable(sock, instanceId);
} }
} }
void SingleCoreApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) { void SingleCoreApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
Q_Q(SingleCoreApplication); Q_Q(SingleCoreApplication);
Q_EMIT q->receivedMessage(instanceId, dataSocket->readAll()); Q_EMIT q->receivedMessage(instanceId, dataSocket->readAll());
} }
void SingleCoreApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) { void SingleCoreApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
if (closedSocket->bytesAvailable() > 0) if (closedSocket->bytesAvailable() > 0) {
Q_EMIT slotDataAvailable(closedSocket, instanceId); Q_EMIT slotDataAvailable(closedSocket, instanceId);
}
}
void SingleCoreApplicationPrivate::randomSleep() {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
QThread::msleep(QRandomGenerator::global()->bounded(8u, 18u));
#else
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
QThread::msleep(8 + static_cast<unsigned long>(static_cast<float>(qrand()) / RAND_MAX * 10));
#endif
} }

View File

@@ -1,6 +1,6 @@
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (c) Itay Grudev 2015 - 2016 // Copyright (c) Itay Grudev 2015 - 2020
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -31,11 +31,12 @@
// //
// //
#ifndef SINGLECOREAPPLICATION_P_H #ifndef SINGLEAPPLICATION_P_H
#define SINGLECOREAPPLICATION_P_H #define SINGLEAPPLICATION_P_H
#include <QtGlobal> #include <QtGlobal>
#include <QObject> #include <QObject>
#include <QString>
#include <QMap> #include <QMap>
#include "singlecoreapplication.h" #include "singlecoreapplication.h"
@@ -48,6 +49,7 @@ struct InstancesInfo {
bool primary; bool primary;
quint32 secondary; quint32 secondary;
qint64 primaryPid; qint64 primaryPid;
char primaryUser[128];
quint16 checksum; quint16 checksum;
}; };
@@ -60,6 +62,7 @@ struct ConnectionInfo {
class SingleCoreApplicationPrivate : public QObject { class SingleCoreApplicationPrivate : public QObject {
Q_OBJECT Q_OBJECT
public: public:
enum ConnectionType : quint8 { enum ConnectionType : quint8 {
InvalidConnection = 0, InvalidConnection = 0,
@@ -74,32 +77,35 @@ class SingleCoreApplicationPrivate : public QObject {
}; };
Q_DECLARE_PUBLIC(SingleCoreApplication) Q_DECLARE_PUBLIC(SingleCoreApplication)
explicit SingleCoreApplicationPrivate(SingleCoreApplication *_q_ptr); explicit SingleCoreApplicationPrivate(SingleCoreApplication *ptr);
~SingleCoreApplicationPrivate() override; ~SingleCoreApplicationPrivate() override;
QString getUsername();
void genBlockServerName(); void genBlockServerName();
void initializeMemoryBlock(); void initializeMemoryBlock();
void startPrimary(); void startPrimary();
void startSecondary(); void startSecondary();
void connectToPrimary(const int msecs, const ConnectionType connectionType); bool connectToPrimary(const int msecs, const ConnectionType connectionType);
quint16 blockChecksum(); quint16 blockChecksum();
qint64 primaryPid(); qint64 primaryPid();
QString primaryUser();
void readInitMessageHeader(QLocalSocket *socket); void readInitMessageHeader(QLocalSocket *socket);
void readInitMessageBody(QLocalSocket *socket); void readInitMessageBody(QLocalSocket *socket);
void randomSleep();
SingleCoreApplication *q_ptr; SingleCoreApplication *q_ptr;
QSharedMemory *memory; QSharedMemory *memory_;
QLocalSocket *socket; QLocalSocket *socket_;
QLocalServer *server; QLocalServer *server_;
quint32 instanceNumber; quint32 instanceNumber_;
QString blockServerName; QString blockServerName_;
SingleCoreApplication::Options options; SingleCoreApplication::Options options_;
QMap<QLocalSocket *, ConnectionInfo> connectionMap; QMap<QLocalSocket*, ConnectionInfo> connectionMap_;
public slots: public slots:
void slotConnectionEstablished(); void slotConnectionEstablished();
void slotDataAvailable(QLocalSocket *, const quint32); void slotDataAvailable(QLocalSocket*, const quint32);
void slotClientConnectionClosed(QLocalSocket *, const quint32); void slotClientConnectionClosed(QLocalSocket*, const quint32);
}; };
#endif // SINGLECOREAPPLICATION_P_H #endif // SINGLEAPPLICATION_P_H