File indexing completed on 2024-05-19 05:42:10

0001 // ct_lvtmdb_lockable.h                                               -*-C++-*-
0002 
0003 /*
0004 // Copyright 2023 Codethink Ltd <codethink@codethink.co.uk>
0005 // SPDX-License-Identifier: Apache-2.0
0006 //
0007 // Licensed under the Apache License, Version 2.0 (the "License");
0008 // you may not use this file except in compliance with the License.
0009 // You may obtain a copy of the License at
0010 //
0011 //     http://www.apache.org/licenses/LICENSE-2.0
0012 //
0013 // Unless required by applicable law or agreed to in writing, software
0014 // distributed under the License is distributed on an "AS IS" BASIS,
0015 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0016 // See the License for the specific language governing permissions and
0017 // limitations under the License.
0018 */
0019 
0020 #ifndef INCLUDED_CT_LVTMDB_LOCKABLE
0021 #define INCLUDED_CT_LVTMDB_LOCKABLE
0022 
0023 //@PURPOSE: Shared implementation of per-object reader-writer locking
0024 
0025 #include <lvtmdb_export.h>
0026 
0027 #include <atomic>
0028 #include <cassert>
0029 #include <functional>
0030 #include <iostream>
0031 #include <memory>
0032 #include <mutex>
0033 #include <shared_mutex>
0034 #include <string>
0035 #include <utility>
0036 
0037 #ifndef NDEBUG
0038 #define LVTMDB_LOCK_DEBUGGING
0039 #endif
0040 
0041 namespace Codethink::lvtmdb {
0042 
0043 // ==========================
0044 // class Lockable
0045 // ==========================
0046 
0047 class LVTMDB_EXPORT Lockable {
0048   private:
0049     // DATA
0050     std::unique_ptr<std::shared_mutex> d_mutex_p;
0051     // Read access through shared or exclusive lock
0052     // Write access *only* through exclusive lock
0053     // this applies to derived classes too
0054     // When locking two objects, the DbObject with the lowest address
0055     // should be locked first (see the dining philosopher's problem).
0056 
0057     // debugging stuff
0058 #ifdef LVTMDB_LOCK_DEBUGGING
0059     std::atomic_int d_lockTracker = 0;
0060     //  n < 0: locked for writing
0061     // n == 0: unlocked
0062     //  n > 0: there are n readers
0063 
0064     void readLock()
0065     {
0066         int res = ++d_lockTracker;
0067         assert(res > 0);
0068         (void) res;
0069     }
0070 
0071     void readUnlock()
0072     {
0073         int res = --d_lockTracker;
0074         assert(res >= 0);
0075         (void) res;
0076     }
0077 
0078     void writeLock()
0079     {
0080         int res = --d_lockTracker;
0081         assert(res == -1);
0082         (void) res;
0083     }
0084 
0085     void writeUnlock()
0086     {
0087         int res = ++d_lockTracker;
0088         assert(res == 0);
0089         (void) res;
0090     }
0091 #endif
0092 
0093   public:
0094     // TYPES
0095     class ROLock {
0096         std::shared_lock<std::shared_mutex> d_lock;
0097 #ifdef LVTMDB_LOCK_DEBUGGING
0098         Lockable *d_source_p = nullptr;
0099 #endif
0100 
0101       public:
0102         ROLock() = delete;
0103         // Don't create locks which do not own a mutex
0104 
0105         ROLock(std::shared_mutex& mutex, Lockable *source):
0106             d_lock(mutex) // mutex locked here
0107 #ifdef LVTMDB_LOCK_DEBUGGING
0108             ,
0109             d_source_p(source)
0110 #endif
0111         {
0112             (void) source;
0113 #ifdef LVTMDB_LOCK_DEBUGGING
0114             if (d_source_p) {
0115                 d_source_p->readLock();
0116             }
0117 #endif
0118         }
0119 
0120         ROLock(ROLock&& other) noexcept:
0121             d_lock(std::move(other.d_lock))
0122 #ifdef LVTMDB_LOCK_DEBUGGING
0123             ,
0124             d_source_p(other.d_source_p)
0125 #endif
0126         {
0127 #ifdef LVTMDB_LOCK_DEBUGGING
0128             // make sure we don't run the destructor twice
0129             other.d_source_p = nullptr;
0130 
0131             assert(d_lock.owns_lock());
0132             assert(!other.d_lock.owns_lock());
0133 #endif
0134         }
0135 
0136         ~ROLock()
0137         {
0138 #ifdef LVTMDB_LOCK_DEBUGGING
0139             if (d_source_p) {
0140                 d_source_p->readUnlock();
0141             }
0142 #endif
0143         } // mutex unlocked here
0144 
0145         ROLock& operator=(ROLock&& other) = delete;
0146         // We don't need this. Make it = delete because the default
0147         // implementation doesn't set other.d_souce_p = nullptr
0148     };
0149 
0150     class RWLock {
0151         std::unique_lock<std::shared_mutex> d_lock;
0152 #ifdef LVTMDB_LOCK_DEBUGGING
0153         Lockable *d_source_p = nullptr;
0154 #endif
0155 
0156       public:
0157         RWLock() = delete;
0158         // Don't create locks which do not own a mutex
0159 
0160         RWLock(std::shared_mutex& mutex, Lockable *source):
0161             d_lock(mutex) // mutex locked here
0162 #ifdef LVTMDB_LOCK_DEBUGGING
0163             ,
0164             d_source_p(source)
0165 #endif
0166         {
0167             (void) source;
0168 #ifdef LVTMDB_LOCK_DEBUGGING
0169             if (d_source_p) {
0170                 d_source_p->writeLock();
0171             }
0172 #endif
0173         }
0174 
0175         RWLock(RWLock&& other) noexcept:
0176             d_lock(std::move(other.d_lock))
0177 #ifdef LVTMDB_LOCK_DEBUGGING
0178             ,
0179             d_source_p(other.d_source_p)
0180 #endif
0181         {
0182 #ifdef LVTMDB_LOCK_DEBUGGING
0183             // make sure we don't run the destructor twice
0184             other.d_source_p = nullptr;
0185 
0186             assert(d_lock.owns_lock());
0187             assert(!other.d_lock.owns_lock());
0188 #endif
0189         }
0190 
0191         ~RWLock()
0192         {
0193 #ifdef LVTMDB_LOCK_DEBUGGING
0194             if (d_source_p) {
0195                 d_source_p->writeUnlock();
0196             }
0197 #endif
0198         } // mutex unlocked here
0199 
0200         RWLock& operator=(RWLock&& other) = delete;
0201         // We don't need this. Make it = delete because the default
0202         // implementation doesn't set other.d_source_p = nullptr
0203     };
0204 
0205     // CREATORS
0206     Lockable(): d_mutex_p(std::make_unique<std::shared_mutex>())
0207     {
0208     }
0209 
0210     virtual ~Lockable() noexcept = default;
0211 
0212     Lockable(Lockable&& other) noexcept: d_mutex_p(std::move(other.d_mutex_p))
0213     {
0214 #ifdef LVTMDB_LOCK_DEBUGGING
0215         d_lockTracker.store(other.d_lockTracker.load());
0216 #endif
0217     }
0218 
0219     Lockable& operator=(Lockable&& other) noexcept
0220     {
0221         d_mutex_p = std::move(other.d_mutex_p);
0222 #ifdef LVTMDB_LOCK_DEBUGGING
0223         d_lockTracker.store(other.d_lockTracker.load());
0224 #endif
0225         return *this;
0226     }
0227 
0228     // ACCESSORS
0229     void assertReadable() const
0230     {
0231 #ifdef LVTMDB_LOCK_DEBUGGING
0232         assert(d_lockTracker.load() != 0);
0233 #endif
0234     }
0235 
0236     void assertWritable() const
0237     {
0238 #ifdef LVTMDB_LOCK_DEBUGGING
0239         assert(d_lockTracker.load() < 0);
0240 #endif
0241     }
0242 
0243     // MODIFIERS
0244     [[nodiscard]] ROLock readOnlyLock()
0245     {
0246         return ROLock{*d_mutex_p, this};
0247     }
0248 
0249     [[nodiscard]] RWLock rwLock()
0250     {
0251         return RWLock{*d_mutex_p, this};
0252     }
0253 
0254     template<class LOCK>
0255     void withLock(std::function<void(void)>& fn)
0256     // Execute fn while holding the lock
0257     // e.g. foo.withLock<ROLock>([] { bar(); });
0258     {
0259         auto lock = LOCK{*d_mutex_p, this};
0260         (void) lock; // cppcheck
0261         fn();
0262     }
0263 
0264     inline void withROLock(std::function<void(void)> fn)
0265     {
0266         withLock<ROLock>(fn);
0267     }
0268 
0269     inline void withRWLock(std::function<void(void)> fn)
0270     {
0271         withLock<RWLock>(fn);
0272     }
0273 
0274     // CLASS METHODS
0275     [[nodiscard]] static std::pair<ROLock, ROLock> roLockTwo(Lockable *a, Lockable *b);
0276     // Lock two objects in a consistent order so as to avoid the dining
0277     // philosopher's problem
0278 
0279     [[nodiscard]] static std::pair<RWLock, RWLock> rwLockTwo(Lockable *a, Lockable *b);
0280     // Lock two objects in a consistent order so as to avoid the dining
0281     // philosopher's problem
0282 };
0283 
0284 } // namespace Codethink::lvtmdb
0285 
0286 #endif // INCLUDED_CT_LVTMDB_LOCKABLE