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