1
0
mirror of https://github.com/itay-grudev/SingleApplication.git synced 2025-04-20 20:14:45 +08:00

Mitigated possible race condition during app startup

This commit is contained in:
Itay Grudev 2020-09-09 00:28:02 +01:00
parent 81052d9a61
commit e4282f2476
4 changed files with 101 additions and 51 deletions

View File

@ -74,17 +74,28 @@ SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSeconda
// 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();
qDebug() << "SingleApplication: Created and initialized new memory block.";
} else { } else {
if( d->memory->error() == QSharedMemory::AlreadyExists ){
// Attempt to attach to the memory segment // Attempt to attach to the memory segment
if( ! d->memory->attach() ){ if( ! d->memory->attach() ){
qCritical() << "SingleApplication: Unable to attach to shared memory block."; qCritical() << "SingleApplication: Unable to attach to shared memory block.";
qCritical() << d->memory->errorString(); abortSafely();
delete d; }
::exit( EXIT_FAILURE ); if( ! d->memory->lock() ){
qCritical() << "SingleApplication: Unable to lock memory block after attach.";
abortSafely();
}
qDebug() << "SingleApplication: Attached to new memory block.";
} else {
qCritical() << "SingleApplication: Unable create block.";
abortSafely();
} }
d->memory->lock();
} }
auto *inst = static_cast<InstancesInfo*>( d->memory->data() ); auto *inst = static_cast<InstancesInfo*>( d->memory->data() );
@ -93,9 +104,13 @@ SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSeconda
// 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 ){ while( true ){
qDebug() << "SingleApplication: Verifying block checksum.";
// If the shared memory block's checksum is valid continue // If the shared memory block's checksum is valid continue
if( d->blockChecksum() == inst->checksum ) break; if( d->blockChecksum() == inst->checksum ) break;
qDebug() << "SingleApplication: Invalid block checksum. Waiting.";
// If more than 5s have elapsed, assume the primary instance crashed and // If more than 5s have elapsed, assume the primary instance crashed and
// assume it's position // assume it's position
if( time.elapsed() > 5000 ){ if( time.elapsed() > 5000 ){
@ -106,36 +121,48 @@ SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSeconda
// Otherwise wait for a random period and try again. The random sleep here // Otherwise wait for a random period and try again. The random sleep here
// limits the probability of a collision between two racing apps and // limits the probability of a collision between two racing apps and
// allows the app to initialise faster // allows the app to initialise faster
d->memory->unlock(); if( ! d->memory->unlock() ){
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
qDebug() << d->memory->errorString();
}
#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 ) #if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 )
QThread::sleep( QRandomGenerator::global()->bounded( 8u, 18u )); QThread::sleep( QRandomGenerator::global()->bounded( 8u, 18u ));
#else #else
qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max() ); qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max() );
QThread::sleep( 8 + static_cast <unsigned long>( static_cast <float>( qrand() ) / RAND_MAX * 10 )); QThread::sleep( 8 + static_cast <unsigned long>( static_cast <float>( qrand() ) / RAND_MAX * 10 ));
#endif #endif
d->memory->lock(); if( ! d->memory->lock() ){
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 );
@ -235,3 +262,16 @@ bool SingleApplication::sendMessage( const QByteArray &message, int 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() << d->memory->errorString();
delete d;
::exit( EXIT_FAILURE );
}

View File

@ -140,6 +140,7 @@ Q_SIGNALS:
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

@ -161,6 +161,17 @@ void SingleApplicationPrivate::startPrimary()
{ {
Q_Q(SingleApplication); Q_Q(SingleApplication);
// Reset the number of connections
auto *inst = static_cast <InstancesInfo*>( memory->data() );
inst->primary = true;
inst->primaryPid = q->applicationPid();
qstrncpy( inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser) );
inst->checksum = blockChecksum();
instanceNumber = 0;
qDebug() << "SingleApplication: Started as a primary instance.";
// 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 );
@ -181,20 +192,17 @@ void SingleApplicationPrivate::startPrimary()
this, this,
&SingleApplicationPrivate::slotConnectionEstablished &SingleApplicationPrivate::slotConnectionEstablished
); );
// Reset the number of connections
auto *inst = static_cast <InstancesInfo*>( memory->data() );
inst->primary = true;
inst->primaryPid = q->applicationPid();
qstrncpy( inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser) );
inst->checksum = blockChecksum();
instanceNumber = 0;
} }
void SingleApplicationPrivate::startSecondary() void SingleApplicationPrivate::startSecondary()
{ {
auto *inst = static_cast <InstancesInfo*>( memory->data() );
inst->secondary += 1;
inst->checksum = blockChecksum();
instanceNumber = inst->secondary;
qDebug() << "SingleApplication: Started as a secondary instance.";
} }
void SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType ) void SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType )
@ -411,6 +419,7 @@ void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock )
( connectionType == SecondaryInstance && ( connectionType == SecondaryInstance &&
options & SingleApplication::Mode::SecondaryNotification ) ) options & SingleApplication::Mode::SecondaryNotification ) )
{ {
qDebug() << "SingleApplication: instanceStarted()";
Q_EMIT q->instanceStarted(); Q_EMIT q->instanceStarted();
} }

View File

@ -41,8 +41,8 @@ struct InstancesInfo {
bool primary; bool primary;
quint32 secondary; quint32 secondary;
qint64 primaryPid; qint64 primaryPid;
quint16 checksum;
char primaryUser[128]; char primaryUser[128];
quint16 checksum; // Must be the last field
}; };
struct ConnectionInfo { struct ConnectionInfo {