blocxx

GenericRWLockImpl.hpp

Go to the documentation of this file.
00001 /*******************************************************************************
00002 * Copyright (C) 2007, Quest Software All rights reserved.
00003 * Copyright (C) 2006, Novell, Inc. All rights reserved.
00004 * 
00005 * Redistribution and use in source and binary forms, with or without
00006 * modification, are permitted provided that the following conditions are met:
00007 * 
00008 *     * Redistributions of source code must retain the above copyright notice,
00009 *       this list of conditions and the following disclaimer.
00010 *     * Redistributions in binary form must reproduce the above copyright
00011 *       notice, this list of conditions and the following disclaimer in the
00012 *       documentation and/or other materials provided with the distribution.
00013 *     * Neither the name of 
00014 *       Quest Software, 
00015 *       nor Novell, Inc., 
00016 *       nor the names of its contributors or employees may be used to 
00017 *       endorse or promote products derived from this software without 
00018 *       specific prior written permission.
00019 * 
00020 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00021 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00022 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00023 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
00024 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00025 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
00026 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00027 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00028 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00029 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00030 * POSSIBILITY OF SUCH DAMAGE.
00031 *******************************************************************************/
00032 
00033 
00039 #ifndef BLOCXX_GENERIC_RWLOCK_IMPL_HPP_INCLUDE_GUARD_
00040 #define BLOCXX_GENERIC_RWLOCK_IMPL_HPP_INCLUDE_GUARD_
00041 #include "blocxx/BLOCXX_config.h"
00042 #include "blocxx/CommonFwd.hpp"
00043 #include "blocxx/NonRecursiveMutexLock.hpp"
00044 #include "blocxx/Condition.hpp"
00045 #include "blocxx/Exception.hpp"
00046 #include "blocxx/TimeoutException.hpp"
00047 #include "blocxx/ExceptionIds.hpp"
00048 #include "blocxx/Timeout.hpp"
00049 #include "blocxx/TimeoutTimer.hpp"
00050 #include "blocxx/Assertion.hpp"
00051 
00052 #include <map>
00053 
00054 namespace BLOCXX_NAMESPACE
00055 {
00056 
00057 BLOCXX_DECLARE_APIEXCEPTION(GenericRWLockImpl, BLOCXX_COMMON_API);
00063 template <typename IdT, typename CompareT>
00064 class GenericRWLockImpl
00065 {
00066 public:
00067    GenericRWLockImpl();
00068    ~GenericRWLockImpl();
00069    
00073    void acquireReadLock(const IdT id, const Timeout& timeout);
00074    
00081    void acquireWriteLock(const IdT id, const Timeout& timeout);
00082    
00086    void releaseReadLock(const IdT id);
00087    
00091    void releaseWriteLock(const IdT id);
00092 
00093 private:
00094 
00095    Condition   m_waiting_writers;
00096 
00097    bool m_canRead;
00098    Condition   m_waiting_readers;
00099 
00100    NonRecursiveMutex m_guard;
00101    unsigned m_numReaders;
00102    unsigned m_numWriters; // current writer + upgrading writer
00103 
00104    struct LockerInfo
00105    {
00106       unsigned int readCount;
00107       unsigned int writeCount;
00108    
00109       bool isReader() const
00110       {
00111          return readCount > 0;
00112       }
00113    
00114       bool isWriter() const
00115       {
00116          return writeCount > 0;
00117       }
00118    };
00119 
00120    typedef std::map<IdT, LockerInfo, CompareT> IdMap;
00121    IdMap m_lockerInfo;
00122 
00123    // unimplemented
00124    GenericRWLockImpl(const GenericRWLockImpl&);
00125    GenericRWLockImpl& operator=(const GenericRWLockImpl&);
00126 };
00127 
00129 template <typename IdT, typename CompareT>
00130 GenericRWLockImpl<IdT, CompareT>::GenericRWLockImpl()
00131    : m_canRead(true)
00132    , m_numReaders(0)
00133    , m_numWriters(0)
00134 {
00135 }
00137 template <typename IdT, typename CompareT>
00138 GenericRWLockImpl<IdT, CompareT>::~GenericRWLockImpl()
00139 {
00140 }
00142 template <typename IdT, typename CompareT>
00143 void
00144 GenericRWLockImpl<IdT, CompareT>::acquireReadLock(const IdT id, const Timeout& timeout)
00145 {
00146    TimeoutTimer timer(timeout);
00147 
00148    NonRecursiveMutexLock l(m_guard);
00149    typename IdMap::iterator info = m_lockerInfo.find(id);
00150 
00151    if (info != m_lockerInfo.end())
00152    {
00153       LockerInfo& ti(info->second);
00154       // id already have a read or write lock, so just increment.
00155       BLOCXX_ASSERT(ti.isReader() || ti.isWriter());
00156       ++ti.readCount;
00157       return;
00158    }
00159 
00160    // id is a new reader
00161    while (!m_canRead || m_numWriters > 0)
00162    {
00163       if (!m_waiting_readers.timedWait(l, timer.asAbsoluteTimeout()))
00164       {
00165          BLOCXX_THROW(TimeoutException, "Timeout while waiting for read lock.");
00166       }
00167    }
00168    
00169    // Increase the reader count
00170    LockerInfo lockerInfo;
00171    lockerInfo.readCount = 1;
00172    lockerInfo.writeCount = 0;
00173    m_lockerInfo.insert(typename IdMap::value_type(id, lockerInfo));
00174 
00175    ++m_numReaders;
00176 }
00177 
00179 template <typename IdT, typename CompareT>
00180 void
00181 GenericRWLockImpl<IdT, CompareT>::releaseReadLock(const IdT id)
00182 {
00183    NonRecursiveMutexLock l(m_guard);
00184 
00185    typename IdMap::iterator pInfo = m_lockerInfo.find(id);
00186 
00187    if (pInfo == m_lockerInfo.end() || !pInfo->second.isReader())
00188    {
00189       BLOCXX_THROW(GenericRWLockImplException, "Cannot release a read lock when no read lock is held");
00190    }
00191 
00192    LockerInfo& info(pInfo->second);
00193    --info.readCount;
00194 
00195    if (!info.isWriter() && !info.isReader())
00196    {
00197       --m_numReaders;
00198       if (m_numReaders == 0)
00199       {
00200          // This needs to wake them all up. In the case where one thread is waiting to upgrade a read to a write lock
00201          // and others are waiting to get a write lock, we have to wake up the thread trying to upgrade.
00202          m_waiting_writers.notifyAll();
00203       }
00204       m_lockerInfo.erase(pInfo);
00205    }
00206 }
00207 
00209 template <typename IdT, typename CompareT>
00210 void
00211 GenericRWLockImpl<IdT, CompareT>::acquireWriteLock(const IdT id, const Timeout& timeout)
00212 {
00213    // 7 cases:
00214    // 1. No id has the lock
00215    //   Get the lock
00216    // 2. This id has the write lock
00217    //   Increment the lock count
00218    // 3. Another id has the write lock & other ids may be waiting for read and/or write locks.
00219    //   Block until the lock is acquired.
00220    // 4. Only this id has a read lock
00221    //   Increment the write lock count .
00222    // 5. >0 other ids have the read lock & other ids may be waiting for write locks.
00223    //   Block until the write lock is acquired.
00224    // 6. This id and other ids have the read lock
00225    //   Block new readers and writers and wait until existing readers finish.
00226    // 7. This id and other ids have the read lock and one of the other ids has requested a write lock.
00227    //   Throw an exception.
00228 
00229    TimeoutTimer timer(timeout);
00230 
00231    NonRecursiveMutexLock l(m_guard);
00232 
00233    typename IdMap::iterator pInfo = m_lockerInfo.find(id);
00234    if (pInfo != m_lockerInfo.end())
00235    {
00236       // This id already has some sort of lock
00237       LockerInfo& ti(pInfo->second);
00238       BLOCXX_ASSERT(ti.isReader() || ti.isWriter());
00239 
00240       if (!ti.isWriter())
00241       {
00242          // The id is upgrading
00243 
00244          BLOCXX_ASSERT(m_numWriters == 0 || m_numWriters == 1);
00245          if (m_numWriters == 1)
00246          {
00247             // another id beat us to upgrading the write lock.  Throw an exception.
00248             BLOCXX_THROW(DeadlockException, "Upgrading read lock to a write lock failed, another upgrade is already in progress.");
00249          }
00250 
00251          // switch from being a reader to a writer
00252          --m_numReaders;
00253          // mark us as a writer, this will prevent other ids from becoming a writer
00254          ++m_numWriters;
00255 
00256          // This thread isn't the only reader. Wait for others to finish.
00257          while (m_numReaders != 0)
00258          {
00259             // stop new readers - inside while loop, because it may get reset by other ids releasing locks.
00260             m_canRead = false;
00261 
00262             if (!m_waiting_writers.timedWait(l, timer.asAbsoluteTimeout()))
00263             {
00264                // undo changes
00265                ++m_numReaders;
00266                --m_numWriters;
00267                m_canRead = true;
00268                if (m_numWriters == 0)
00269                {
00270                   m_waiting_readers.notifyAll();
00271                }
00272                BLOCXX_THROW(TimeoutException, "Timeout while waiting for write lock.");
00273             }
00274          }
00275       }
00276       ++ti.writeCount;
00277 
00278    }
00279    else
00280    {
00281       // This id doesn't have any lock
00282 
00283       while (m_numReaders != 0 || m_numWriters != 0)
00284       {
00285          // stop new readers
00286          m_canRead = false;
00287 
00288          if (!m_waiting_writers.timedWait(l, timer.asAbsoluteTimeout()))
00289          {
00290             m_canRead = true;
00291             if (m_numWriters == 0)
00292             {
00293                m_waiting_readers.notifyAll();
00294             }
00295             BLOCXX_THROW(TimeoutException, "Timeout while waiting for write lock.");
00296          }
00297       }
00298 
00299       LockerInfo ti;
00300       ti.readCount = 0;
00301       ti.writeCount = 1;
00302       m_lockerInfo.insert(typename IdMap::value_type(id, ti));
00303       ++m_numWriters;
00304       m_canRead = false;
00305    }
00306 
00307 }
00308 
00310 template <typename IdT, typename CompareT>
00311 void
00312 GenericRWLockImpl<IdT, CompareT>::releaseWriteLock(const IdT id)
00313 {
00314    NonRecursiveMutexLock l(m_guard);
00315    
00316    typename IdMap::iterator pInfo = m_lockerInfo.find(id);
00317    
00318    if (pInfo == m_lockerInfo.end() || !pInfo->second.isWriter())
00319    {
00320       BLOCXX_THROW(GenericRWLockImplException, "Cannot release a write lock when no write lock is held");
00321    }
00322 
00323    LockerInfo& ti(pInfo->second);
00324 
00325    BLOCXX_ASSERT(ti.isWriter());
00326 
00327    --ti.writeCount;
00328 
00329    if (!ti.isWriter())
00330    {
00331       --m_numWriters;
00332 
00333       BLOCXX_ASSERT(m_numWriters == 0);
00334 
00335       m_canRead = true;
00336       if (ti.isReader())
00337       {
00338          // restore reader status
00339          ++m_numReaders;
00340       }
00341       else
00342       {
00343          // This id no longer holds locks.
00344          m_waiting_writers.notifyOne();
00345          m_lockerInfo.erase(pInfo);
00346       }
00347       m_waiting_readers.notifyAll();
00348    }
00349 }
00350 
00351 
00352 } // end namespace BLOCXX_NAMESPACE
00353 
00354 #endif