From kde-games-devel Mon May 05 21:39:33 2003 From: Nicolas Hadacek Date: Mon, 05 May 2003 21:39:33 +0000 To: kde-games-devel Subject: [Kde-games-devel] KHighscore locking mechanism X-MARC-Message: https://marc.info/?l=kde-games-devel&m=105219244317964 MIME-Version: 1 Content-Type: multipart/mixed; boundary="--Boundary_(ID_ELh0R7C6Q1Zbhso/NvMGmw)" --Boundary_(ID_ELh0R7C6Q1Zbhso/NvMGmw) Content-type: text/plain; charset=us-ascii Content-transfer-encoding: 7BIT Content-disposition: inline Hi all, I am currently trying to solve the problems of using global (for a computer) config files for saving highscores. Some basic mechanism is already in place in KHighscore but a big (potential) problem is race condition between games played by several users at the time when updating the highscores lists. So I propose a locking mechanism (see the patch against khighscore.h/cpp and a separate kfilelock.h/cpp which could be used in other parts of KDE). These changes are untested but I just want to have some opinions :) The second step is to use the locking in KExtHighscore which should be quite easy (I have made the changes at home) since support for multiple user is there (but basically untested). The third step is to provide some upgrade path from highscore list per user to a global highscore list... I can do that later :) Flames, praises, comments, ... welcome ! see you, Nicolas --Boundary_(ID_ELh0R7C6Q1Zbhso/NvMGmw) Content-type: text/x-diff; charset=us-ascii; name=highscores.diff Content-transfer-encoding: 7BIT Content-disposition: attachment; filename=highscores.diff Index: khighscore.h =================================================================== RCS file: /home/kde/kdegames/libkdegames/highscore/khighscore.h,v retrieving revision 1.7 diff -u -2 -d -p -b -r1.7 khighscore.h --- khighscore.h 6 Jan 2002 18:05:53 -0000 1.7 +++ khighscore.h 6 May 2003 03:26:53 -0000 @@ -81,5 +81,47 @@ class KHighscore : public QObject Q_OBJECT public: + /** + * Constructor. + */ KHighscore(QObject* parent = 0); + + /** + * Read the current state of the config file. Remember that if not + * locked for writing, the config file can change at any time. + * (This method is only useful for a global config file). + */ + void readCurrentConfig(); + + /** + * Lock the config file for writing (does nothing and return true if a + * local config is used). Perform writing without GUI + * interaction to avoid blocking and don't forget to unlock the file + * as soon as possible with @ref unlock(). + * + * If the config file cannot be locked, + * the method waits for 1 second and if it failed again displays + * a message box asking for retry or cancel. + * @param widget used as parent of the message box. + * @param display if false do not display the message box. + * + * @return false on error or if the config file is locked by another + * process. In such case, the config file is set to read-only. + */ + bool lockForWriting(QWidget *widget = 0, bool display = true); + + /** + * Unlock the config file. @see lockForWriting. + */ + void unlock(); + + /** @return if the config file is locked (return true if a local config + * file is used. + */ + bool isLocked() const; + + /** + * Destructor. + * Remove the eventual lock on the config file. + */ ~KHighscore(); @@ -219,4 +261,9 @@ protected: **/ KConfig* config() const; + + /** + * @return the config filename if a global config file is used. + */ + QString globalConfigFilename() const; private: Index: khighscore.cpp =================================================================== RCS file: /home/kde/kdegames/libkdegames/highscore/khighscore.cpp,v retrieving revision 1.15 diff -u -2 -d -p -b -r1.15 khighscore.cpp --- khighscore.cpp 25 Feb 2003 01:03:18 -0000 1.15 +++ khighscore.cpp 6 May 2003 03:26:53 -0000 @@ -21,7 +21,12 @@ */ +#include + #include #include #include +#include +#include +#include #include "khighscore.h" @@ -33,8 +38,5 @@ class KHighscorePrivate { public: - KHighscorePrivate() - { - mConfig = 0; - } + KHighscorePrivate() : mConfig(0) {} KConfig* mConfig; @@ -45,35 +47,94 @@ KHighscore::KHighscore(QObject* parent) { d = new KHighscorePrivate; + readCurrentConfig(); +} +void KHighscore::readCurrentConfig() +{ +#ifdef HIGHSCORE_DIRECTORY + delete d->mConfig; + d->mConfig = new KSimpleConfig(globalConfigFilename(), true); +#endif } -KHighscore::~KHighscore() +bool KHighscore::lockForWriting(QWidget *widget, bool display) { -// not necessary, as KConfig destructor should handle this - sync(); - if (d->mConfig) { +#ifdef HIGHSCORE_DIRECTORY + if ( isLocked() ) return true; // already locked + bool first = true; + QString filename = globalConfigFile(); + for (;;) { + bool ok = ( FileLock::lock(filename)==0 ); + if (ok) { delete d->mConfig; + d->mConfig = new KSimpleConfig(filename, false); + return true; } - delete d; + if ( !first ) { + if ( !display ) break; + KGuiItem item = KStdGuiItem::cont(); + item.setText(i18n("Retry")); + int res = KMessageBox::warningContinueCancel(parent, i18n("Cannot lock the global config file. Another process is probably currently writing to it."), i18n("Config file locked"), item, "ask_lock_game_config_file"); + if ( res==KMessageBox::Cancel ) break; + } else sleep(1); + first = false; + } + return false; +#else + Q_UNUSED(widget); + Q_UNUSED(display); + return true; +#endif } -KConfig* KHighscore::config() const +void KHighscore::unlock() { - #ifdef HIGHSCORE_DIRECTORY - if (!d->mConfig) { +#ifdef HIGHSCORE_DIRECTORY + if ( isLocked () ) FileLock::unlock(globalConfigFilename()); + d->mConfig->setReadOnly(true); +#endif +} + +bool KHighscore::isLocked() const +{ +#ifdef HIGHSCORE_DIRECTORY + return !d->mConfig->isReadOnly(); +#else + return true; +#endif +} + +QString KHighscore::globalConfigFilename() const +{ +#ifdef HIGHSCORE_DIRECTORY //AB: is instanceName() correct? MUST be the same for all //processes of the game! - QString file=QString::fromLocal8Bit("%1/%2").arg(HIGHSCORE_DIRECTORY).arg(KGlobal::instance()->instanceName()); - d->mConfig = new KSimpleConfig(file); - } + return QString::fromLocal8Bit("%1/%2").arg(HIGHSCORE_DIRECTORY) + .arg(KGlobal::instance()->instanceName()); +#else + return QString::null; +#endif +} + +KHighscore::~KHighscore() +{ +#ifdef HIGHSCORE_DIRECTORY + unlock(); + delete d->mConfig; +#endif + delete d; +} + +KConfig* KHighscore::config() const +{ +#ifdef HIGHSCORE_DIRECTORY return d->mConfig; - #else +#else return kapp->config(); - #endif +#endif } void KHighscore::writeEntry(int entry, const QString& key, const QVariant& value) { - // save the group in case that we are on kapp->config() KConfigGroupSaver cg(config(), group()); QString confKey = QString("%1_%2").arg(entry).arg(key); @@ -83,5 +144,4 @@ void KHighscore::writeEntry(int entry, c void KHighscore::writeEntry(int entry, const QString& key, int value) { - // save the group in case that we are on kapp->config() KConfigGroupSaver cg(config(), group()); QString confKey = QString("%1_%2").arg(entry).arg(key); --Boundary_(ID_ELh0R7C6Q1Zbhso/NvMGmw) Content-type: text/x-c++src; charset=us-ascii; name=kfilelock.cpp Content-transfer-encoding: 7BIT Content-disposition: attachment; filename=kfilelock.cpp #include "kfilelock.h" #include #include #include #include #include // this code is heavily borrowed from gjots // Copyright (C) 2002 Robert Hepple int FileLock::lock(const QString &filename) { QFileInfo info(filename); QString lockFilename = info.dirPath() + "/.#" + info.fileName(); int fd = open(lockFilename.local8Bit(), O_RDWR | O_CREAT, 0644); if ( fd<0 ) return -1; // be sure that no other process access the file if ( flock(fd, LOCK_EX | LOCK_NB)<0 ) { close(fd); return -1; } FILE *f = fdopen(fd, "r+"); if ( f==0 ) { flock(fd, LOCK_UN); close(fd); return 1; } // look at the pid number in the file // and check if there is still a process with this pid pid_t pid; if ( fscanf(f, "%d", &pid)==1 && ( getpgid(pid)>=0 || errno!=ESRCH ) ) { flock(fd, LOCK_UN); fclose(f); return pid; } // write the process pid fseek(f, 0L, SEEK_SET); fprintf(f, "%d", getpid()); flock(fd, LOCK_UN); fclose(f); return 0; } void FileLock::unlock(const QString &filename) { QFileInfo info(filename); QString lockFilename = info.dirPath() + "/.#" + info.fileName(); // first, make sure we locked this file int fd = open(lockFilename.local8Bit(), O_RDONLY); if ( fd<0 ) return; // be sure that no other process access the file if ( flock(fd, LOCK_EX | LOCK_NB)<0 ) { close(fd); return; } FILE *f = fdopen(fd, "r"); if ( f==0 ) { flock(fd, LOCK_UN); close(fd); return; } // read the pid inside the file and check that it is ours pid_t pid; if ( fscanf(f, "%d", &pid)==1 && pid==getpid() ) unlink(lockFilename.local8Bit()); flock(fd, LOCK_UN); fclose(f); } --Boundary_(ID_ELh0R7C6Q1Zbhso/NvMGmw) Content-type: text/x-chdr; charset=us-ascii; name=kfilelock.h Content-transfer-encoding: 7BIT Content-disposition: attachment; filename=kfilelock.h #ifndef KFILELOCK_H #define KFILELOCK_H #include class FileLock { public: /** * Create a file named ".#filename" and put the current * process id inside the file. * @return 0 on success, -1 on failure (no permission) or the pid number * of the process which currently holds the lock. */ static int lock(const QString &filename); /** * Remove the file created by @ref lockFile. */ static void unlock(const QString &filename); }; #endif --Boundary_(ID_ELh0R7C6Q1Zbhso/NvMGmw) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline _______________________________________________ kde-games-devel mailing list kde-games-devel@mail.kde.org http://mail.kde.org/mailman/listinfo/kde-games-devel --Boundary_(ID_ELh0R7C6Q1Zbhso/NvMGmw)--