SingleApplication: Refactor startup code
This commit is contained in:
parent
9ae0b32318
commit
6515e06a13
|
@ -80,27 +80,24 @@ SingleApplicationPrivateClass::SingleApplicationPrivateClass(SingleApplicationCl
|
||||||
|
|
||||||
SingleApplicationPrivateClass::~SingleApplicationPrivateClass() {
|
SingleApplicationPrivateClass::~SingleApplicationPrivateClass() {
|
||||||
|
|
||||||
if (socket_ != nullptr) {
|
if (socket_ != nullptr && socket_->isOpen()) {
|
||||||
socket_->close();
|
socket_->close();
|
||||||
delete socket_;
|
|
||||||
socket_ = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (memory_ != nullptr) {
|
if (memory_ != nullptr) {
|
||||||
memory_->lock();
|
memory_->lock();
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
|
||||||
if (server_ != nullptr) {
|
if (server_ != nullptr) {
|
||||||
server_->close();
|
server_->close();
|
||||||
delete server_;
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
instance->primary = false;
|
instance->primary = false;
|
||||||
instance->primaryPid = -1;
|
instance->primaryPid = -1;
|
||||||
instance->primaryUser[0] = '\0';
|
instance->primaryUser[0] = '\0';
|
||||||
instance->checksum = blockChecksum();
|
instance->checksum = blockChecksum();
|
||||||
}
|
}
|
||||||
memory_->unlock();
|
memory_->unlock();
|
||||||
|
if (memory_->isAttached()) {
|
||||||
delete memory_;
|
memory_->detach();
|
||||||
memory_ = nullptr;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -203,7 +200,7 @@ void SingleApplicationPrivateClass::startPrimary() {
|
||||||
// Successful creation means that no main process exists
|
// Successful creation means that no main process exists
|
||||||
// So we start a QLocalServer to listen for connections
|
// So we start a QLocalServer to listen for connections
|
||||||
QLocalServer::removeServer(blockServerName_);
|
QLocalServer::removeServer(blockServerName_);
|
||||||
server_ = new QLocalServer();
|
server_ = new QLocalServer(this);
|
||||||
|
|
||||||
// Restrict access to the socket according to the SingleApplication::Mode::User flag on User level or no restrictions
|
// Restrict access to the socket according to the SingleApplication::Mode::User flag on User level or no restrictions
|
||||||
if (options_ & SingleApplicationClass::Mode::User) {
|
if (options_ & SingleApplicationClass::Mode::User) {
|
||||||
|
@ -230,16 +227,16 @@ void SingleApplicationPrivateClass::startSecondary() {
|
||||||
|
|
||||||
bool SingleApplicationPrivateClass::connectToPrimary(const int timeout, const ConnectionType connectionType) {
|
bool SingleApplicationPrivateClass::connectToPrimary(const int timeout, const 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(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (socket_->state() == QLocalSocket::ConnectedState) return true;
|
if (socket_->state() == QLocalSocket::ConnectedState) return true;
|
||||||
|
|
||||||
|
QElapsedTimer time;
|
||||||
|
time.start();
|
||||||
|
|
||||||
if (socket_->state() != QLocalSocket::ConnectedState) {
|
if (socket_->state() != QLocalSocket::ConnectedState) {
|
||||||
|
|
||||||
forever {
|
forever {
|
||||||
|
@ -261,7 +258,7 @@ bool SingleApplicationPrivateClass::connectToPrimary(const int timeout, const Co
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialisation message according to the SingleApplication protocol
|
// Initialization message according to the SingleApplication protocol
|
||||||
QByteArray initMsg;
|
QByteArray initMsg;
|
||||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
||||||
writeStream.setVersion(QDataStream::Qt_5_8);
|
writeStream.setVersion(QDataStream::Qt_5_8);
|
||||||
|
|
|
@ -45,25 +45,13 @@ class QLocalServer;
|
||||||
class QLocalSocket;
|
class QLocalSocket;
|
||||||
class QSharedMemory;
|
class QSharedMemory;
|
||||||
|
|
||||||
struct InstancesInfo {
|
|
||||||
bool primary;
|
|
||||||
quint32 secondary;
|
|
||||||
qint64 primaryPid;
|
|
||||||
char primaryUser[128];
|
|
||||||
quint16 checksum;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ConnectionInfo {
|
|
||||||
explicit ConnectionInfo() : msgLen(0), instanceId(0), stage(0) {}
|
|
||||||
quint64 msgLen;
|
|
||||||
quint32 instanceId;
|
|
||||||
quint8 stage;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SingleApplicationPrivateClass : public QObject {
|
class SingleApplicationPrivateClass : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
explicit SingleApplicationPrivateClass(SingleApplicationClass *ptr);
|
||||||
|
~SingleApplicationPrivateClass() override;
|
||||||
|
|
||||||
enum ConnectionType : quint8 {
|
enum ConnectionType : quint8 {
|
||||||
InvalidConnection = 0,
|
InvalidConnection = 0,
|
||||||
NewInstance = 1,
|
NewInstance = 1,
|
||||||
|
@ -74,12 +62,25 @@ class SingleApplicationPrivateClass : public QObject {
|
||||||
StageInitHeader = 0,
|
StageInitHeader = 0,
|
||||||
StageInitBody = 1,
|
StageInitBody = 1,
|
||||||
StageConnectedHeader = 2,
|
StageConnectedHeader = 2,
|
||||||
StageConnectedBody = 3,
|
StageConnectedBody = 3
|
||||||
};
|
};
|
||||||
Q_DECLARE_PUBLIC(SingleApplicationClass)
|
Q_DECLARE_PUBLIC(SingleApplicationClass)
|
||||||
|
|
||||||
explicit SingleApplicationPrivateClass(SingleApplicationClass *ptr);
|
struct InstancesInfo {
|
||||||
~SingleApplicationPrivateClass() override;
|
explicit InstancesInfo() : primary(false), secondary(0), primaryPid(0), checksum(0) {}
|
||||||
|
bool primary;
|
||||||
|
quint32 secondary;
|
||||||
|
qint64 primaryPid;
|
||||||
|
char primaryUser[128];
|
||||||
|
quint16 checksum;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConnectionInfo {
|
||||||
|
explicit ConnectionInfo() : msgLen(0), instanceId(0), stage(0) {}
|
||||||
|
quint64 msgLen;
|
||||||
|
quint32 instanceId;
|
||||||
|
quint8 stage;
|
||||||
|
};
|
||||||
|
|
||||||
static QString getUsername();
|
static QString getUsername();
|
||||||
void genBlockServerName();
|
void genBlockServerName();
|
||||||
|
|
|
@ -34,6 +34,8 @@
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
|
#include <boost/scope_exit.hpp>
|
||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QSharedMemory>
|
#include <QSharedMemory>
|
||||||
|
@ -77,108 +79,125 @@ SingleApplicationClass::SingleApplicationClass(int &argc, char *argv[], const bo
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
#ifdef Q_OS_UNIX
|
||||||
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
|
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
{
|
||||||
d->memory_ = new QSharedMemory(QNativeIpcKey(d->blockServerName_));
|
# if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
||||||
#else
|
std::unique_ptr<QSharedMemory> memory = std::make_unique<QSharedMemory>(QNativeIpcKey(d->blockServerName_));
|
||||||
d->memory_ = new QSharedMemory(d->blockServerName_);
|
# else
|
||||||
#endif
|
std::unique_ptr<QSharedMemory> memory = std::make_unique<QSharedMemory>(d->blockServerName_);
|
||||||
d->memory_->attach();
|
# endif
|
||||||
delete d->memory_;
|
if (memory->attach()) {
|
||||||
|
memory->detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Guarantee thread safe behaviour with a shared memory block.
|
// Guarantee thread safe behaviour with a shared memory block.
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
||||||
d->memory_ = new QSharedMemory(QNativeIpcKey(d->blockServerName_));
|
QSharedMemory *memory = new QSharedMemory(QNativeIpcKey(d->blockServerName_), this);
|
||||||
#else
|
#else
|
||||||
d->memory_ = new QSharedMemory(d->blockServerName_);
|
QSharedMemory *memory = new QSharedMemory(d->blockServerName_, this);
|
||||||
#endif
|
#endif
|
||||||
|
d->memory_ = memory;
|
||||||
|
|
||||||
|
bool primary = false;
|
||||||
|
|
||||||
// Create a shared memory block
|
// Create a shared memory block
|
||||||
if (d->memory_->create(sizeof(InstancesInfo))) {
|
if (d->memory_->create(sizeof(SingleApplicationPrivateClass::InstancesInfo))) {
|
||||||
// Initialize the shared memory block
|
primary = true;
|
||||||
if (!d->memory_->lock()) {
|
}
|
||||||
qCritical() << "SingleApplication: Unable to lock memory block after create.";
|
else if (d->memory_->error() == QSharedMemory::AlreadyExists) {
|
||||||
abortSafely();
|
if (!d->memory_->attach()) {
|
||||||
|
qCritical() << "SingleApplication: Unable to attach to shared memory block:" << d->memory_->error() << d->memory_->errorString();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
d->initializeMemoryBlock();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (d->memory_->error() == QSharedMemory::AlreadyExists) {
|
qCritical() << "SingleApplication: Unable to create shared memory block:" << d->memory_->error() << d->memory_->errorString();
|
||||||
// Attempt to attach to the memory segment
|
return;
|
||||||
if (!d->memory_->attach()) {
|
|
||||||
qCritical() << "SingleApplication: Unable to attach to shared memory block.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
if (!d->memory_->lock()) {
|
|
||||||
qCritical() << "SingleApplication: Unable to lock memory block after attach.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
qCritical() << "SingleApplication: Unable to create block.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(d->memory_->data());
|
bool locked = false;
|
||||||
|
|
||||||
|
BOOST_SCOPE_EXIT((memory)(&locked)) {
|
||||||
|
if (locked && !memory->unlock()) {
|
||||||
|
qWarning() << "SingleApplication: Unable to unlock shared memory block:" << memory->error() << memory->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}BOOST_SCOPE_EXIT_END
|
||||||
|
|
||||||
|
if (!d->memory_->lock()) {
|
||||||
|
qCritical() << "SingleApplication: Unable to lock shared memory block:" << d->memory_->error() << d->memory_->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
locked = true;
|
||||||
|
|
||||||
|
if (primary) {
|
||||||
|
// Initialize the shared memory block
|
||||||
|
d->initializeMemoryBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleApplicationPrivateClass::InstancesInfo *instance = static_cast<SingleApplicationPrivateClass::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 initialized and in a consistent state
|
||||||
forever {
|
while (d->blockChecksum() != instance->checksum) {
|
||||||
// If the shared memory block's checksum is valid continue
|
|
||||||
if (d->blockChecksum() == instance->checksum) break;
|
|
||||||
|
|
||||||
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
|
// If more than 5 seconds have elapsed, assume the primary instance crashed and assume its 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 5 seconds. Assuming primary instance failure.";
|
||||||
d->initializeMemoryBlock();
|
d->initializeMemoryBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise wait for a random period and try again.
|
// 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
|
// The random sleep here limits the probability of a collision between two racing apps and allows the app to initialize faster
|
||||||
if (!d->memory_->unlock()) {
|
if (locked) {
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
|
if (d->memory_->unlock()) {
|
||||||
qDebug() << d->memory_->errorString();
|
locked = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qCritical() << "SingleApplication: Unable to unlock shared memory block for random wait:" << memory->error() << memory->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SingleApplicationPrivateClass::randomSleep();
|
SingleApplicationPrivateClass::randomSleep();
|
||||||
|
|
||||||
if (!d->memory_->lock()) {
|
if (!d->memory_->lock()) {
|
||||||
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
|
qCritical() << "SingleApplication: Unable to lock shared memory block after random wait:" << memory->error() << memory->errorString();
|
||||||
abortSafely();
|
return;
|
||||||
}
|
}
|
||||||
|
locked = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!instance->primary) {
|
if (instance->primary) {
|
||||||
|
// Check if another instance can be started
|
||||||
|
if (allowSecondary) {
|
||||||
|
d->startSecondary();
|
||||||
|
if (d->options_ & Mode::SecondaryNotification) {
|
||||||
|
d->connectToPrimary(timeout, SingleApplicationPrivateClass::SecondaryInstance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
d->startPrimary();
|
d->startPrimary();
|
||||||
if (!d->memory_->unlock()) {
|
primary = true;
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
|
|
||||||
qDebug() << d->memory_->errorString();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if another instance can be started
|
if (locked) {
|
||||||
if (allowSecondary) {
|
if (d->memory_->unlock()) {
|
||||||
d->startSecondary();
|
locked = false;
|
||||||
if (d->options_ & Mode::SecondaryNotification) {
|
|
||||||
d->connectToPrimary(timeout, SingleApplicationPrivateClass::SecondaryInstance);
|
|
||||||
}
|
}
|
||||||
if (!d->memory_->unlock()) {
|
else {
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory after secondary start.";
|
qWarning() << "SingleApplication: Unable to unlock shared memory block:" << memory->error() << memory->errorString();
|
||||||
qDebug() << d->memory_->errorString();
|
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!d->memory_->unlock()) {
|
if (!primary && !allowSecondary) {
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory at end of execution.";
|
d->connectToPrimary(timeout, SingleApplicationPrivateClass::NewInstance);
|
||||||
qDebug() << d->memory_->errorString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
d->connectToPrimary(timeout, SingleApplicationPrivateClass::NewInstance);
|
|
||||||
|
|
||||||
delete d;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SingleApplicationClass::~SingleApplicationClass() {
|
SingleApplicationClass::~SingleApplicationClass() {
|
||||||
|
@ -308,22 +327,3 @@ bool SingleApplicationClass::sendMessage(const QByteArray &message, const int ti
|
||||||
return d->writeConfirmedMessage(timeout, message);
|
return d->writeConfirmedMessage(timeout, message);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleans up the shared memory block and exits with a failure.
|
|
||||||
* This function halts program execution.
|
|
||||||
*/
|
|
||||||
void SingleApplicationClass::abortSafely() {
|
|
||||||
|
|
||||||
#if defined(SINGLEAPPLICATION)
|
|
||||||
Q_D(SingleApplication);
|
|
||||||
#elif defined(SINGLECOREAPPLICATION)
|
|
||||||
Q_D(SingleCoreApplication);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
qCritical() << "SingleApplication: " << d->memory_->error() << d->memory_->errorString();
|
|
||||||
delete d;
|
|
||||||
|
|
||||||
::exit(EXIT_FAILURE);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -102,7 +102,7 @@ class SingleApplicationClass : public ApplicationClass { // clazy:exclude=ctor-
|
||||||
* instance and the secondary instance.
|
* instance and the secondary instance.
|
||||||
* @note The timeout is just a hint for the maximum time of blocking
|
* @note The timeout is just a hint for the maximum time of blocking
|
||||||
* operations. It does not guarantee that the SingleApplication
|
* operations. It does not guarantee that the SingleApplication
|
||||||
* initialisation will be completed in given time, though is a good hint.
|
* initialization 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.
|
||||||
*/
|
*/
|
||||||
explicit SingleApplicationClass(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000);
|
explicit SingleApplicationClass(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000);
|
||||||
|
|
Loading…
Reference in New Issue