src/publishsubscribe/qsystemreadwritelock_unix.cpp
changeset 0 876b1a06bc25
equal deleted inserted replaced
-1:000000000000 0:876b1a06bc25
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the Qt Mobility Components.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 #include "qsystemreadwritelock_p.h"
       
    43 #include <QFile>
       
    44 #include <sys/sem.h>
       
    45 #include <fcntl.h>
       
    46 #include <errno.h>
       
    47 #include <unistd.h>
       
    48 #include <QRegExp>
       
    49 #include <QCryptographicHash>
       
    50 #include <QDir>
       
    51 
       
    52 QTM_BEGIN_NAMESPACE
       
    53 
       
    54 class QSystemReadWriteLockPrivate
       
    55 {
       
    56 public:
       
    57     enum SemIndex {
       
    58         ActiveReaders = 0,
       
    59         TotalWriters,
       
    60         ActiveWriterSem,
       
    61         NumInstances
       
    62     };
       
    63 
       
    64     QSystemReadWriteLockPrivate():id(-1),semId(-1),
       
    65                     key(QString()),keyFileName(QString()),
       
    66                     error(QSystemReadWriteLock::NoError),
       
    67                     errorString(QString()){};
       
    68     void setError(const QString &errorString);
       
    69 
       
    70     key_t id;
       
    71     int semId;
       
    72     QString key;
       
    73     QString keyFileName;
       
    74 
       
    75     QSystemReadWriteLock::SystemReadWriteLockError error;
       
    76     QString errorString;
       
    77 };
       
    78 
       
    79 void QSystemReadWriteLockPrivate::setError(const QString &_errorString)
       
    80 {
       
    81     errorString = _errorString;
       
    82     switch (errno) {
       
    83         case EPERM:
       
    84         case EACCES:
       
    85             error = QSystemReadWriteLock::PermissionDenied;
       
    86             break;
       
    87         case ERANGE:
       
    88         case ENOMEM:
       
    89         case ENOSPC:
       
    90             error = QSystemReadWriteLock::OutOfResources;
       
    91             break;
       
    92         case EIDRM:
       
    93             error = QSystemReadWriteLock::NotFound;
       
    94             break;
       
    95         default:
       
    96             error = QSystemReadWriteLock::UnknownError;
       
    97      }
       
    98 }
       
    99 
       
   100 /*
       
   101     This function is taken from qcore_unix_p.h(qt_safe_open)
       
   102     If/when QSystemReadWriteLock is integrated into Qt,
       
   103     this function defintion may replaced by the sharedmemory
       
   104     definition.
       
   105  */
       
   106 static inline int safe_open(const char *pathname, int flags, mode_t mode = 0777)
       
   107 {
       
   108 #ifdef O_CLOEXEC
       
   109     flags |= O_CLOEXEC;
       
   110 #endif
       
   111     register int fd;
       
   112 
       
   113     do {
       
   114         fd = ::open(pathname, flags, mode);
       
   115     } while (fd == -1 && errno == EINTR);
       
   116 
       
   117     // unknown flags are ignored, so we have no way of verifying if
       
   118     // O_CLOEXEC was accepted
       
   119     if (fd != -1)
       
   120         ::fcntl(fd, F_SETFD, FD_CLOEXEC);
       
   121     return fd;
       
   122 }
       
   123 
       
   124 /*
       
   125     This function is taken from qsharedmemory_unix.cpp.
       
   126     If/when QSystemReadWriteLock is integrated into Qt,
       
   127     this function defintion may replaced by the sharedmemory
       
   128     definition.
       
   129  */
       
   130 int createUnixKeyFile(const QString &fileName)
       
   131 {
       
   132     if (QFile::exists(fileName)) {
       
   133         return 0;
       
   134     }
       
   135 
       
   136     int fd = safe_open(QFile::encodeName(fileName).constData(),
       
   137             O_EXCL | O_CREAT | O_RDWR, 0640);
       
   138 
       
   139     if (-1 == fd) {
       
   140         if (errno == EEXIST)
       
   141             return 0;
       
   142         return -1;
       
   143     } else {
       
   144         close(fd);
       
   145     }
       
   146     return 1;
       
   147 }
       
   148 
       
   149 /*
       
   150     This function is taken from qsharedmemory_unix.cpp.
       
   151     If/when QSystemReadWriteLock is integrated into Qt,
       
   152     the function definition may replaced by the sharedmemory
       
   153     definition.
       
   154  */
       
   155 QString makePlatformSafeKey(const QString &key,
       
   156         const QString &prefix)
       
   157 {
       
   158     if (key.isEmpty())
       
   159         return QString();
       
   160 
       
   161     QString result = prefix;
       
   162 
       
   163     QString part1 = key;
       
   164     part1.remove(QRegExp(QLatin1String("[^A-Za-z]")));
       
   165     result.append(part1);
       
   166 
       
   167     QByteArray hex = QCryptographicHash::hash(key.toUtf8(), QCryptographicHash::Sha1).toHex();
       
   168     result.append(QLatin1String(hex));
       
   169     return QDir::tempPath() + QLatin1Char('/') + result;
       
   170 }
       
   171 
       
   172 /*
       
   173    \class QSystemReadWriteLock
       
   174 
       
   175    \brief The QSystemReadWriteLock class provides read-write locking between
       
   176    processes.
       
   177 
       
   178    A read-write lock is a synchronization tool for protecting resources that can
       
   179    be accessed for reading and writing. This type of lock is useful if you want
       
   180    to allow multiple processes/threads to have simultaneous read-only access, but as soon
       
   181    as one thread wants to write to the resource, all other threads must be
       
   182    blocked until the writing is complete.
       
   183 
       
   184    QSystemReadWriteLock behaves much like the QReadWriteLock class, but it also
       
   185    works across multiple processes (although it also works perfectly well,
       
   186    albeit slightly less efficiently, in a single process).  The system
       
   187    resources are cleaned up when the last QSystemReadWriteLock instance
       
   188    is destroyed.
       
   189 
       
   190    The behaviour of QSystemReadWriteLock is such that writers will
       
   191    take precedence over readers.  When writers are waiting, any new
       
   192    readers must wait until all writers complete.  That is, writers
       
   193    can starve readers.
       
   194  */
       
   195 
       
   196 /*
       
   197    Implementation Details:
       
   198 
       
   199    The QSystemReadWriteLock class uses Linux kernel semaphores to synchronize
       
   200    access between readers and writers.
       
   201 
       
   202    4 semaphores are used in total.
       
   203    activeReaders - number of active readers
       
   204    TotalWriters - number of active and pending writers
       
   205 
       
   206    ActiveWriterSem - indicates whether a writer is active
       
   207 
       
   208    NumInstances - counts the number of instances of QSystemReadWriteLock
       
   209                   objects there for a given key.  This is needed
       
   210                   to determine whether system resources are
       
   211                   safe to clean up during an instance's destruction.
       
   212 
       
   213   activeReaders, TotalWriters and NumInstance are used as counters and
       
   214   not in the way typical semaphores are used.  e.g.a value of 5 for
       
   215   activeReaders indicates that there are currently 5 readers.
       
   216 
       
   217   ActiveWriterSem is used as a typical semaphore.  A value of 1
       
   218   means there's an free slot for a writer to become active.
       
   219  */
       
   220 
       
   221 /*
       
   222   Construct a system read write lock from the provided \a key.
       
   223 
       
   224   The \a mode parameter is only used in Unix systems to handle the case
       
   225   where underlying system resources such as semaphores survive a process
       
   226   crash.  In this case, the next process to instatiate a lock will
       
   227   get the resources that survived the crash, and unless \mode is
       
   228   Create, the resources will not be reset.
       
   229  */
       
   230 QSystemReadWriteLock::QSystemReadWriteLock(const QString &key, AccessMode mode)
       
   231 {
       
   232     d = new QSystemReadWriteLockPrivate;
       
   233     d->key = key;
       
   234     d->keyFileName = makePlatformSafeKey(key, QLatin1String("qipc_systemsem_"));
       
   235 
       
   236     int built = createUnixKeyFile(d->keyFileName);
       
   237     if (built == -1) {
       
   238         d->setError(QObject::tr("QSystemReadWriteLock::QSystemReadWriteLock: "
       
   239                     "unable to make key file for key: %1(%2)")
       
   240                     .arg(key).arg(QString::fromLatin1(strerror(errno))));
       
   241         return;
       
   242     }
       
   243 
       
   244     // Get the unix key for the created file
       
   245     d->id = ftok(QFile::encodeName(d->keyFileName).constData(), 'Q');
       
   246     if (d->id == -1) {
       
   247         d->setError(QObject::tr("QSystemReadWriteLock::QSystemReadWriteLock: "
       
   248                         "ftok failed for key %1(%2)")
       
   249                     .arg(key).arg(QString::fromLatin1(strerror(errno))));
       
   250         return;
       
   251     }
       
   252 
       
   253     d->semId = ::semget(d->id, 4, IPC_CREAT | IPC_EXCL | 0666);
       
   254     bool created = false;
       
   255     if (d->semId == -1) {
       
   256         if (errno == EEXIST) {
       
   257             d->semId = ::semget(d->id, 4,
       
   258                     (mode == QSystemReadWriteLock::Create)?IPC_CREAT|0666:0);
       
   259             if (d->semId == -1) {
       
   260                 d->setError(QObject::tr("QSystemReadWriteLock::QSystemReadWriteLock: "
       
   261                                     "Unable to access semaphore set for key %1(%2)")
       
   262                             .arg(key).arg(QString::fromLatin1(strerror(errno))));
       
   263                 return;
       
   264             }
       
   265         } else {
       
   266             d->setError(QObject::tr("QSystemReadWriteLock:QSystemReadWriteLock: "
       
   267                         "Unable to access semaphore set for key %1(%2)")
       
   268                         .arg(key).arg(QString::fromLatin1(strerror(errno))));
       
   269             return;
       
   270         }
       
   271     } else {
       
   272         created = true;
       
   273     }
       
   274 
       
   275     Q_ASSERT(d->semId != -1);
       
   276     if (created || mode == QSystemReadWriteLock::Create) {
       
   277         typedef union {
       
   278             int val;
       
   279             struct semid_ds *buf;
       
   280             ushort *array;
       
   281         }semun;
       
   282 
       
   283         semun counterInitVal; //initial value for a "counter" semaphores
       
   284         counterInitVal.val = 0;
       
   285 
       
   286         semun activeWriterInitVal;//initial value for the "active writer" semaphore
       
   287         activeWriterInitVal.val = 1;
       
   288 
       
   289         if (-1 == ::semctl(d->semId, QSystemReadWriteLockPrivate::ActiveReaders,
       
   290                     SETVAL, counterInitVal) ||
       
   291                 -1 == ::semctl(d->semId, QSystemReadWriteLockPrivate::TotalWriters,
       
   292                     SETVAL, counterInitVal) ||
       
   293                 -1 == ::semctl(d->semId, QSystemReadWriteLockPrivate::ActiveWriterSem,
       
   294                     SETVAL, activeWriterInitVal) ||
       
   295                 -1 == ::semctl(d->semId, QSystemReadWriteLockPrivate::NumInstances,
       
   296                     SETVAL, counterInitVal))
       
   297         {
       
   298             d->setError(QObject::tr("QSystemReadWriteLock::QSystemReadWriteLock: "
       
   299                         "Unable to reset semaphore set for key %1(%2)")
       
   300                         .arg(key).arg(QString::fromLatin1(strerror(errno))));
       
   301             QFile::remove(d->keyFileName);
       
   302             ::semctl(d->semId, 0, IPC_RMID);
       
   303             d->semId = -1;
       
   304             return;
       
   305         }
       
   306     }
       
   307 
       
   308     struct sembuf op;
       
   309     op.sem_num = QSystemReadWriteLockPrivate::NumInstances;
       
   310     op.sem_op = 1;
       
   311     op.sem_flg = SEM_UNDO;
       
   312     int semoprv = ::semop(d->semId, &op, 1);
       
   313     if (semoprv == -1) {
       
   314         d->setError(QObject::tr("QSystemReadWriteLock::QSystemReadWriteLock: "
       
   315                             "Unable to increment NumInstances semaphore "
       
   316                             "for key%1(%2)").arg(key).arg(QString::fromLatin1(strerror(errno))));
       
   317         d->semId = -1;
       
   318         return;
       
   319     }
       
   320 }
       
   321 
       
   322 /*
       
   323   Destroy the lock instance.  The last QSystemReadWriteLock instance
       
   324   for a particular key will destroy the underlying system resources.
       
   325  */
       
   326 QSystemReadWriteLock::~QSystemReadWriteLock()
       
   327 {
       
   328     if (d->semId != -1) {
       
   329         //decrement and check that there are 0 instances
       
   330         //be aware these 2 operations occur together atomically
       
   331         struct sembuf ops[2];
       
   332         ops[0].sem_num = QSystemReadWriteLockPrivate::NumInstances;
       
   333         ops[0].sem_op = -1;
       
   334         ops[0].sem_flg = SEM_UNDO | IPC_NOWAIT;
       
   335 
       
   336         ops[1].sem_num = QSystemReadWriteLockPrivate::NumInstances;
       
   337         ops[1].sem_op = 0;
       
   338         ops[1].sem_flg = IPC_NOWAIT;
       
   339 
       
   340         //if successful, then delete the semaphore set
       
   341         int semoprv  = ::semop(d->semId, ops, 2);
       
   342         if (semoprv == 0) {
       
   343             if(::semctl(d->semId, 0, IPC_RMID) == -1) {
       
   344                 qWarning("QSystemReadWriteLock::~QSystemReadWriteLock: "
       
   345                          "Unable to remove semaphore %s(%s)",
       
   346                          d->key.toLocal8Bit().constData(), strerror(errno));
       
   347             }
       
   348             QFile::remove(d->keyFileName);
       
   349         } else {
       
   350             if (errno == EAGAIN) {
       
   351                 //wasn't 0 instances so just decrement the NumInstances semaphore
       
   352                 if (::semop(d->semId, ops, 1) == -1) {
       
   353                     qWarning("QSystemReadWriteLock::~QSystemReadWriteLock: "
       
   354                              "Unable to decrement NumInstances semaphore for key %s(%s)",
       
   355                              d->key.toLocal8Bit().constData(), strerror(errno));
       
   356                 }
       
   357             } else {
       
   358                 qWarning("QSystemReadWriteLock::~QSystemReadWriteLock: "
       
   359                          "Unable to decrement and check NumInstances semaphore for key %s(%s)",
       
   360                          d->key.toLocal8Bit().constData(), strerror(errno));
       
   361                 ::semop(d->semId, ops, 1);//try decrement anyway
       
   362             }
       
   363         }
       
   364 
       
   365     }
       
   366 
       
   367     Q_ASSERT(d);
       
   368     delete d;
       
   369     d = 0;
       
   370 }
       
   371 
       
   372 /*
       
   373   Return the key of the lock as passed to the constructor.
       
   374  */
       
   375 QString QSystemReadWriteLock::key() const
       
   376 {
       
   377     return d->key;
       
   378 }
       
   379 
       
   380 /*
       
   381   Locks the lock for reading.  The function will block if any thread/process
       
   382   has locked for writing.
       
   383  */
       
   384 bool QSystemReadWriteLock::lockForRead()
       
   385 {
       
   386     if (d->semId == -1) {
       
   387         d->errorString = QObject::tr("QSystemReadWriteLock::lockForRead: "
       
   388                                      "Unable to lock for read for key %1"
       
   389                                      "(Lock had not been correctly initialized)").arg(d->key);
       
   390         d->error = UnknownError;
       
   391         return false;
       
   392     }
       
   393 
       
   394     struct sembuf ops[2];
       
   395 
       
   396     ops[0].sem_num = QSystemReadWriteLockPrivate::ActiveReaders;
       
   397     ops[0].sem_op = 1;
       
   398     ops[0].sem_flg = SEM_UNDO;
       
   399 
       
   400     ops[1].sem_num = QSystemReadWriteLockPrivate::TotalWriters;
       
   401     ops[1].sem_op = 0;
       
   402     ops[1].sem_flg = 0;
       
   403 
       
   404     if (-1 == ::semop(d->semId, ops, 2)) {
       
   405         d->setError(QObject::tr("QSystemReadWriteLock::lockForRead: "
       
   406                                 "Unable to lock for read for key %1(%2)")
       
   407                     .arg(d->key).arg(QString::fromLatin1(strerror(errno))));
       
   408         return false;
       
   409     } else {
       
   410         d->errorString.clear();
       
   411         d->error = NoError;
       
   412         return true;
       
   413     }
       
   414 }
       
   415 
       
   416 /*
       
   417    Locks the lock for writing.  This function will block if another thread/process
       
   418    has locked for reading or writing.
       
   419  */
       
   420 bool QSystemReadWriteLock::lockForWrite()
       
   421 {
       
   422     if (d->semId == -1) {
       
   423         d->errorString = QObject::tr("QSystemReadWriteLock::lockForWrite: "
       
   424                                      "Unable to lock for write for key %1"
       
   425                                      "(Lock had not been correctly initialized)").arg(d->key);
       
   426         d->error = UnknownError;
       
   427         return false;
       
   428     }
       
   429 
       
   430     struct sembuf op;
       
   431     op.sem_num = QSystemReadWriteLockPrivate::TotalWriters;
       
   432     op.sem_op = 1;
       
   433     op.sem_flg = SEM_UNDO;
       
   434 
       
   435     int semoprv = ::semop(d->semId, &op, 1);
       
   436     if (semoprv == -1) {
       
   437         d->setError(QObject::tr("QSystemReadWriteLock::lockForWrite: "
       
   438                                 "Could not increment TotalWriters semaphore for key %1(%2)")
       
   439                     .arg(d->key).arg(QString::fromLatin1(strerror(errno))));
       
   440         return false;
       
   441     }
       
   442 
       
   443     op.sem_num = QSystemReadWriteLockPrivate::ActiveReaders;
       
   444     op.sem_op = 0;
       
   445     op.sem_flg = 0;
       
   446     semoprv = ::semop(d->semId, &op, 1);
       
   447     if (semoprv == -1) {
       
   448         d->setError(QObject::tr("QSystemReadWriteLock::lockForWrite: "
       
   449                                 "Could not detect if all readers were finished for key %1(%2)")
       
   450                     .arg(d->key).arg(QString::fromLatin1(strerror(errno))));
       
   451 
       
   452         // Decrement our write lock
       
   453         op.sem_num = QSystemReadWriteLockPrivate::TotalWriters;
       
   454         op.sem_op = -1;
       
   455         op.sem_flg = SEM_UNDO;
       
   456         ::semop(d->semId, &op, 1);
       
   457         return false;
       
   458     } else {
       
   459         op.sem_num = QSystemReadWriteLockPrivate::ActiveWriterSem;
       
   460         op.sem_op = -1;
       
   461         op.sem_flg =SEM_UNDO;
       
   462         semoprv = ::semop(d->semId, &op, 1);
       
   463 
       
   464         if (semoprv == -1) {
       
   465             d->setError(QObject::tr("QSystemReadWriteLock::lockForWrite: "
       
   466                                     "Could not decrement ActiveWriterSem semaphore for key %1(%2)")
       
   467                         .arg(d->key).arg(QString::fromLatin1(strerror(errno))));
       
   468 
       
   469             op.sem_num = QSystemReadWriteLockPrivate::TotalWriters;
       
   470             op.sem_op = -1;
       
   471             op.sem_flg = 0;
       
   472             ::semop(d->semId, &op, 1);
       
   473             return false;
       
   474         }
       
   475         d->errorString.clear();
       
   476         d->error = NoError;
       
   477         return true;
       
   478     }
       
   479 }
       
   480 
       
   481 /*
       
   482   Release the lock.
       
   483  */
       
   484 void QSystemReadWriteLock::unlock()
       
   485 {
       
   486     if (d->semId == -1) {
       
   487         d->errorString = QObject::tr("QSystemReadWriteLock::unlock: "
       
   488                                      "Unable to unlock for key %1"
       
   489                                      "(Lock had not been correctly initialized)").arg(d->key);
       
   490         d->error = UnknownError;
       
   491         return;
       
   492     }
       
   493 
       
   494     struct sembuf op;
       
   495     op.sem_num = QSystemReadWriteLockPrivate::ActiveReaders;
       
   496     op.sem_op = -1;
       
   497     op.sem_flg = SEM_UNDO | IPC_NOWAIT;
       
   498     if (::semop(d->semId, &op, 1) == 0) {
       
   499         //do nothing, succeeded in decrementing number of readers
       
   500     } else if (errno == EAGAIN){ //no readers, so check for writers
       
   501             struct sembuf ops[3];
       
   502             ops[0].sem_num = QSystemReadWriteLockPrivate::ActiveWriterSem;
       
   503             ops[0].sem_op = 0;
       
   504             ops[0].sem_flg = IPC_NOWAIT;
       
   505 
       
   506             ops[1].sem_num = QSystemReadWriteLockPrivate::ActiveWriterSem;
       
   507             ops[1].sem_op = 1;
       
   508             ops[1].sem_flg = SEM_UNDO;
       
   509 
       
   510             ops[2].sem_num = QSystemReadWriteLockPrivate::TotalWriters;
       
   511             ops[2].sem_op = -1;
       
   512             ops[2].sem_flg = SEM_UNDO;
       
   513 
       
   514             if (::semop(d->semId, ops, 3) == -1) {
       
   515                 if (errno != EAGAIN) {
       
   516                     d->setError(QObject::tr("QSystemSemaphoreWriteLock::unlock: "
       
   517                                             "Unable to check and update writer semaphores for key %1(%2)")
       
   518                                 .arg(d->key).arg(QString::fromLatin1(strerror(errno))));
       
   519                     return;
       
   520                 } //Note: EAGAIN indicates that ActiveWriterSem is has a non zero value
       
   521                   //indicating there is no current writer, so nothing needs to be done
       
   522             }
       
   523     } else {
       
   524         //error in decrementing readers
       
   525         d->setError(QObject::tr("QSystemReadWriteLock::unlock: "
       
   526                                 "Unable to decrement ActiveReaders semaphore for key %1(%2)")
       
   527                     .arg(d->key).arg(QString::fromLatin1(strerror(errno))));
       
   528         return;
       
   529     }
       
   530     d->errorString.clear();
       
   531     d->error = NoError;
       
   532 }
       
   533 
       
   534 /*
       
   535   Returns the error code of the last encountered error
       
   536  */
       
   537 QSystemReadWriteLock::SystemReadWriteLockError QSystemReadWriteLock::error() const {
       
   538     return d->error;
       
   539 }
       
   540 
       
   541 /*
       
   542   Returns a string describing the last encountered error
       
   543  */
       
   544 QString QSystemReadWriteLock::errorString() const
       
   545 {
       
   546     return d->errorString;
       
   547 }
       
   548 
       
   549 QTM_END_NAMESPACE
       
   550