QLockFile was introduced in Qt 5.1.
--HG-- branch : develop
This commit is contained in:
parent
4d171184bd
commit
49ffd7a77d
|
@ -6,11 +6,14 @@ HEADERS += \
|
|||
$$PWD/undoevent.h \
|
||||
$$PWD/vsettings.h \
|
||||
$$PWD/qcommandlineoption.h \
|
||||
$$PWD/qcommandlineparser.h
|
||||
$$PWD/qcommandlineparser.h \
|
||||
$$PWD/qlockfile.h \
|
||||
$$PWD/qlockfile_p.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/vapplication.cpp \
|
||||
$$PWD/undoevent.cpp \
|
||||
$$PWD/vsettings.cpp \
|
||||
$$PWD/qcommandlineoption.cpp \
|
||||
$$PWD/qcommandlineparser.cpp
|
||||
$$PWD/qcommandlineparser.cpp \
|
||||
$$PWD/qlockfile.cpp
|
||||
|
|
331
src/app/core/qlockfile.cpp
Normal file
331
src/app/core/qlockfile.cpp
Normal file
|
@ -0,0 +1,331 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:GPL$
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU General Public License version 3.0 requirements will be
|
||||
** met: http://www.gnu.org/copyleft/gpl.html.
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "qlockfile.h"
|
||||
#include "qlockfile_p.h"
|
||||
|
||||
#include <QThread>
|
||||
#include <QElapsedTimer>
|
||||
#include <QDateTime>
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 1, 0)
|
||||
|
||||
class QLockFileThread : public QThread
|
||||
{
|
||||
public:
|
||||
static void msleep(unsigned long msecs) { QThread::msleep(msecs); }
|
||||
};
|
||||
|
||||
/*!
|
||||
\class QLockFile
|
||||
\inmodule QtCore
|
||||
\brief The QLockFile class provides locking between processes using a file.
|
||||
\since 5.1
|
||||
|
||||
A lock file can be used to prevent multiple processes from accessing concurrently
|
||||
the same resource. For instance, a configuration file on disk, or a socket, a port,
|
||||
a region of shared memory...
|
||||
|
||||
Serialization is only guaranteed if all processes that access the shared resource
|
||||
use QLockFile, with the same file path.
|
||||
|
||||
QLockFile supports two use cases:
|
||||
to protect a resource for a short-term operation (e.g. verifying if a configuration
|
||||
file has changed before saving new settings), and for long-lived protection of a
|
||||
resource (e.g. a document opened by a user in an editor) for an indefinite amount of time.
|
||||
|
||||
When protecting for a short-term operation, it is acceptable to call lock() and wait
|
||||
until any running operation finishes.
|
||||
When protecting a resource over a long time, however, the application should always
|
||||
call setStaleLockTime(0) and then tryLock() with a short timeout, in order to
|
||||
warn the user that the resource is locked.
|
||||
|
||||
If the process holding the lock crashes, the lock file stays on disk and can prevent
|
||||
any other process from accessing the shared resource, ever. For this reason, QLockFile
|
||||
tries to detect such a "stale" lock file, based on the process ID written into the file,
|
||||
and (in case that process ID got reused meanwhile), on the last modification time of
|
||||
the lock file (30s by default, for the use case of a short-lived operation).
|
||||
If the lock file is found to be stale, it will be deleted.
|
||||
|
||||
For the use case of protecting a resource over a long time, you should therefore call
|
||||
setStaleLockTime(0), and when tryLock() returns LockFailedError, inform the user
|
||||
that the document is locked, possibly using getLockInfo() for more details.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\enum QLockFile::LockError
|
||||
|
||||
This enum describes the result of the last call to lock() or tryLock().
|
||||
|
||||
\value NoError The lock was acquired successfully.
|
||||
\value LockFailedError The lock could not be acquired because another process holds it.
|
||||
\value PermissionError The lock file could not be created, for lack of permissions
|
||||
in the parent directory.
|
||||
\value UnknownError Another error happened, for instance a full partition
|
||||
prevented writing out the lock file.
|
||||
*/
|
||||
|
||||
/*!
|
||||
Constructs a new lock file object.
|
||||
The object is created in an unlocked state.
|
||||
When calling lock() or tryLock(), a lock file named \a fileName will be created,
|
||||
if it doesn't already exist.
|
||||
|
||||
\sa lock(), unlock()
|
||||
*/
|
||||
QLockFile::QLockFile(const QString &fileName)
|
||||
: d_ptr(new QLockFilePrivate(fileName))
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Destroys the lock file object.
|
||||
If the lock was acquired, this will release the lock, by deleting the lock file.
|
||||
*/
|
||||
QLockFile::~QLockFile()
|
||||
{
|
||||
unlock();
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets \a staleLockTime to be the time in milliseconds after which
|
||||
a lock file is considered stale.
|
||||
The default value is 30000, i.e. 30 seconds.
|
||||
If your application typically keeps the file locked for more than 30 seconds
|
||||
(for instance while saving megabytes of data for 2 minutes), you should set
|
||||
a bigger value using setStaleLockTime().
|
||||
|
||||
The value of \a staleLockTime is used by lock() and tryLock() in order
|
||||
to determine when an existing lock file is considered stale, i.e. left over
|
||||
by a crashed process. This is useful for the case where the PID got reused
|
||||
meanwhile, so the only way to detect a stale lock file is by the fact that
|
||||
it has been around for a long time.
|
||||
|
||||
\sa staleLockTime()
|
||||
*/
|
||||
void QLockFile::setStaleLockTime(int staleLockTime)
|
||||
{
|
||||
Q_D(QLockFile);
|
||||
d->staleLockTime = staleLockTime;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the time in milliseconds after which
|
||||
a lock file is considered stale.
|
||||
|
||||
\sa setStaleLockTime()
|
||||
*/
|
||||
int QLockFile::staleLockTime() const
|
||||
{
|
||||
Q_D(const QLockFile);
|
||||
return d->staleLockTime;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns \c true if the lock was acquired by this QLockFile instance,
|
||||
otherwise returns \c false.
|
||||
|
||||
\sa lock(), unlock(), tryLock()
|
||||
*/
|
||||
bool QLockFile::isLocked() const
|
||||
{
|
||||
Q_D(const QLockFile);
|
||||
return d->isLocked;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates the lock file.
|
||||
|
||||
If another process (or another thread) has created the lock file already,
|
||||
this function will block until that process (or thread) releases it.
|
||||
|
||||
Calling this function multiple times on the same lock from the same
|
||||
thread without unlocking first is not allowed. This function will
|
||||
\e dead-lock when the file is locked recursively.
|
||||
|
||||
Returns \c true if the lock was acquired, false if it could not be acquired
|
||||
due to an unrecoverable error, such as no permissions in the parent directory.
|
||||
|
||||
\sa unlock(), tryLock()
|
||||
*/
|
||||
bool QLockFile::lock()
|
||||
{
|
||||
return tryLock(-1);
|
||||
}
|
||||
|
||||
/*!
|
||||
Attempts to create the lock file. This function returns \c true if the
|
||||
lock was obtained; otherwise it returns \c false. If another process (or
|
||||
another thread) has created the lock file already, this function will
|
||||
wait for at most \a timeout milliseconds for the lock file to become
|
||||
available.
|
||||
|
||||
Note: Passing a negative number as the \a timeout is equivalent to
|
||||
calling lock(), i.e. this function will wait forever until the lock
|
||||
file can be locked if \a timeout is negative.
|
||||
|
||||
If the lock was obtained, it must be released with unlock()
|
||||
before another process (or thread) can successfully lock it.
|
||||
|
||||
Calling this function multiple times on the same lock from the same
|
||||
thread without unlocking first is not allowed, this function will
|
||||
\e always return false when attempting to lock the file recursively.
|
||||
|
||||
\sa lock(), unlock()
|
||||
*/
|
||||
bool QLockFile::tryLock(int timeout)
|
||||
{
|
||||
Q_D(QLockFile);
|
||||
QElapsedTimer timer;
|
||||
if (timeout > 0)
|
||||
timer.start();
|
||||
int sleepTime = 100;
|
||||
forever {
|
||||
d->lockError = d->tryLock_sys();
|
||||
switch (d->lockError) {
|
||||
case NoError:
|
||||
d->isLocked = true;
|
||||
return true;
|
||||
case PermissionError:
|
||||
case UnknownError:
|
||||
return false;
|
||||
case LockFailedError:
|
||||
if (!d->isLocked && d->isApparentlyStale()) {
|
||||
// Stale lock from another thread/process
|
||||
// Ensure two processes don't remove it at the same time
|
||||
QLockFile rmlock(d->fileName + QLatin1String(".rmlock"));
|
||||
if (rmlock.tryLock()) {
|
||||
if (d->isApparentlyStale() && d->removeStaleLock())
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (timeout == 0 || (timeout > 0 && timer.hasExpired(timeout)))
|
||||
return false;
|
||||
QLockFileThread::msleep(sleepTime);
|
||||
if (sleepTime < 5 * 1000)
|
||||
sleepTime *= 2;
|
||||
}
|
||||
// not reached
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn void QLockFile::unlock()
|
||||
Releases the lock, by deleting the lock file.
|
||||
|
||||
Calling unlock() without locking the file first, does nothing.
|
||||
|
||||
\sa lock(), tryLock()
|
||||
*/
|
||||
|
||||
/*!
|
||||
Retrieves information about the current owner of the lock file.
|
||||
|
||||
If tryLock() returns \c false, and error() returns LockFailedError,
|
||||
this function can be called to find out more information about the existing
|
||||
lock file:
|
||||
\list
|
||||
\li the PID of the application (returned in \a pid)
|
||||
\li the \a hostname it's running on (useful in case of networked filesystems),
|
||||
\li the name of the application which created it (returned in \a appname),
|
||||
\endlist
|
||||
|
||||
Note that tryLock() automatically deleted the file if there is no
|
||||
running application with this PID, so LockFailedError can only happen if there is
|
||||
an application with this PID (it could be unrelated though).
|
||||
|
||||
This can be used to inform users about the existing lock file and give them
|
||||
the choice to delete it. After removing the file using removeStaleLockFile(),
|
||||
the application can call tryLock() again.
|
||||
|
||||
This function returns \c true if the information could be successfully retrieved, false
|
||||
if the lock file doesn't exist or doesn't contain the expected data.
|
||||
This can happen if the lock file was deleted between the time where tryLock() failed
|
||||
and the call to this function. Simply call tryLock() again if this happens.
|
||||
*/
|
||||
bool QLockFile::getLockInfo(qint64 *pid, QString *hostname, QString *appname) const
|
||||
{
|
||||
Q_D(const QLockFile);
|
||||
return d->getLockInfo(pid, hostname, appname);
|
||||
}
|
||||
|
||||
bool QLockFilePrivate::getLockInfo(qint64 *pid, QString *hostname, QString *appname) const
|
||||
{
|
||||
QFile reader(fileName);
|
||||
if (!reader.open(QIODevice::ReadOnly))
|
||||
return false;
|
||||
|
||||
QByteArray pidLine = reader.readLine();
|
||||
pidLine.chop(1);
|
||||
QByteArray appNameLine = reader.readLine();
|
||||
appNameLine.chop(1);
|
||||
QByteArray hostNameLine = reader.readLine();
|
||||
hostNameLine.chop(1);
|
||||
if (pidLine.isEmpty() || appNameLine.isEmpty())
|
||||
return false;
|
||||
|
||||
qint64 thePid = pidLine.toLongLong();
|
||||
if (pid)
|
||||
*pid = thePid;
|
||||
if (appname)
|
||||
*appname = QString::fromUtf8(appNameLine);
|
||||
if (hostname)
|
||||
*hostname = QString::fromUtf8(hostNameLine);
|
||||
return thePid > 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
Attempts to forcefully remove an existing lock file.
|
||||
|
||||
Calling this is not recommended when protecting a short-lived operation: QLockFile
|
||||
already takes care of removing lock files after they are older than staleLockTime().
|
||||
|
||||
This method should only be called when protecting a resource for a long time, i.e.
|
||||
with staleLockTime(0), and after tryLock() returned LockFailedError, and the user
|
||||
agreed on removing the lock file.
|
||||
|
||||
Returns \c true on success, false if the lock file couldn't be removed. This happens
|
||||
on Windows, when the application owning the lock is still running.
|
||||
*/
|
||||
bool QLockFile::removeStaleLockFile()
|
||||
{
|
||||
Q_D(QLockFile);
|
||||
if (d->isLocked) {
|
||||
qWarning("removeStaleLockFile can only be called when not holding the lock");
|
||||
return false;
|
||||
}
|
||||
return d->removeStaleLock();
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the lock file error status.
|
||||
|
||||
If tryLock() returns \c false, this function can be called to find out
|
||||
the reason why the locking failed.
|
||||
*/
|
||||
QLockFile::LockError QLockFile::error() const
|
||||
{
|
||||
Q_D(const QLockFile);
|
||||
return d->lockError;
|
||||
}
|
||||
|
||||
#endif //QT_VERSION < QT_VERSION_CHECK(5, 1, 0)
|
66
src/app/core/qlockfile.h
Normal file
66
src/app/core/qlockfile.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL21$
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 or version 3 as published by the Free
|
||||
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
||||
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
||||
** following information to ensure the GNU Lesser General Public License
|
||||
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
||||
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QLOCKFILE_H
|
||||
#define QLOCKFILE_H
|
||||
|
||||
#include <QString>
|
||||
#include <QScopedPointer>
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 1, 0)
|
||||
|
||||
class QLockFilePrivate;
|
||||
|
||||
class QLockFile
|
||||
{
|
||||
public:
|
||||
QLockFile(const QString &fileName);
|
||||
~QLockFile();
|
||||
|
||||
bool lock();
|
||||
bool tryLock(int timeout = 0);
|
||||
void unlock();
|
||||
|
||||
void setStaleLockTime(int);
|
||||
int staleLockTime() const;
|
||||
|
||||
bool isLocked() const;
|
||||
bool getLockInfo(qint64 *pid, QString *hostname, QString *appname) const;
|
||||
bool removeStaleLockFile();
|
||||
|
||||
enum LockError {
|
||||
NoError = 0,
|
||||
LockFailedError = 1,
|
||||
PermissionError = 2,
|
||||
UnknownError = 3
|
||||
};
|
||||
LockError error() const;
|
||||
|
||||
protected:
|
||||
QScopedPointer<QLockFilePrivate> d_ptr;
|
||||
|
||||
private:
|
||||
Q_DECLARE_PRIVATE(QLockFile)
|
||||
Q_DISABLE_COPY(QLockFile)
|
||||
};
|
||||
|
||||
#endif //QT_VERSION < QT_VERSION_CHECK(5, 1, 0)
|
||||
|
||||
#endif // QLOCKFILE_H
|
83
src/app/core/qlockfile_p.h
Normal file
83
src/app/core/qlockfile_p.h
Normal file
|
@ -0,0 +1,83 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL21$
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 or version 3 as published by the Free
|
||||
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
||||
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
||||
** following information to ensure the GNU Lesser General Public License
|
||||
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
||||
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QLOCKFILE_P_H
|
||||
#define QLOCKFILE_P_H
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#include "qlockfile.h"
|
||||
#include <QFile>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <qt_windows.h>
|
||||
#endif
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 1, 0)
|
||||
|
||||
class QLockFilePrivate
|
||||
{
|
||||
public:
|
||||
QLockFilePrivate(const QString &fn)
|
||||
: fileName(fn),
|
||||
#ifdef Q_OS_WIN
|
||||
fileHandle(INVALID_HANDLE_VALUE),
|
||||
#else
|
||||
fileHandle(-1),
|
||||
#endif
|
||||
staleLockTime(30 * 1000), // 30 seconds
|
||||
lockError(QLockFile::NoError),
|
||||
isLocked(false)
|
||||
{
|
||||
}
|
||||
QLockFile::LockError tryLock_sys();
|
||||
bool removeStaleLock();
|
||||
bool getLockInfo(qint64 *pid, QString *hostname, QString *appname) const;
|
||||
// Returns \c true if the lock belongs to dead PID, or is old.
|
||||
// The attempt to delete it will tell us if it was really stale or not, though.
|
||||
bool isApparentlyStale() const;
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
static int checkFcntlWorksAfterFlock();
|
||||
#endif
|
||||
|
||||
QString fileName;
|
||||
#ifdef Q_OS_WIN
|
||||
Qt::HANDLE fileHandle;
|
||||
#else
|
||||
int fileHandle;
|
||||
#endif
|
||||
int staleLockTime; // "int milliseconds" is big enough for 24 days
|
||||
QLockFile::LockError lockError;
|
||||
bool isLocked;
|
||||
};
|
||||
|
||||
#endif //QT_VERSION < QT_VERSION_CHECK(5, 1, 0)
|
||||
|
||||
#endif /* QLOCKFILE_P_H */
|
|
@ -49,7 +49,11 @@
|
|||
#include <QMessageBox>
|
||||
#include <QThread>
|
||||
#include <QDateTime>
|
||||
#include <QLockFile>
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 1, 0)
|
||||
# include "qlockfile.h"
|
||||
#else
|
||||
# include <QLockFile>
|
||||
#endif
|
||||
#include <QtXmlPatterns>
|
||||
|
||||
using namespace qmu;
|
||||
|
|
|
@ -61,7 +61,11 @@
|
|||
#include <QtGlobal>
|
||||
#include <QDesktopWidget>
|
||||
#include <QDesktopServices>
|
||||
#include <QLockFile>
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 1, 0)
|
||||
# include "qlockfile.h"
|
||||
#else
|
||||
# include <QLockFile>
|
||||
#endif
|
||||
|
||||
Q_LOGGING_CATEGORY(vMainWindow, "v.mainwindow")
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user