//////////////////////////////////////////////////////////////////////////////// /// @brief Helper class to isolate data protection with hazard pointers. /// /// @file /// /// DISCLAIMER /// /// Copyright 2015 ArangoDB GmbH, Cologne, Germany /// Copyright 2004-2015 triAGENS GmbH, Cologne, Germany /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// /// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Max Neunhoeffer /// @author Copyright 2015, ArangoDB GmbH, Cologne, Germany /// @author Copyright 2009-2015, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// #ifndef ARANGODB_BASICS_DATA_GUARDIAN_H #define ARANGODB_BASICS_DATA_GUARDIAN_H 1 namespace triagens { namespace basics { //////////////////////////////////////////////////////////////////////////////// /// @brief DataGuardian, a template class to manage a single pointer to some /// class, optimized for many fast readers and slow writers using lockfree /// hazard pointer technology. //////////////////////////////////////////////////////////////////////////////// template class DataGuardian { public: //////////////////////////////////////////////////////////////////////////////// /// @brief HazardPtr, class for keeping a hazard pointer with appropriate /// padding. //////////////////////////////////////////////////////////////////////////////// class HazardPtr { public: std::atomic ptr; private: char padding[64-sizeof(std::atomic)]; }; //////////////////////////////////////////////////////////////////////////////// /// @brief constructor //////////////////////////////////////////////////////////////////////////////// DataGuardian () { _P[0].ptr = nullptr; _P[1].ptr = nullptr; _V = 0; } //////////////////////////////////////////////////////////////////////////////// /// @brief destructor //////////////////////////////////////////////////////////////////////////////// ~DataGuardian () { std::lock_guard lock(_mutex); while (isHazard(_P[_V].ptr)) { usleep(250); } T const* temp = _P[_V].ptr.load(); delete temp; // OK, if nullptr _P[_V].ptr = nullptr; } //////////////////////////////////////////////////////////////////////////////// /// @brief registerHazard, every thread that wants to read the guarded pointer /// has to create an instance of HazardPtr first and then register it with /// this object. After use one has to unregisterHazard (see below) the /// structure again. //////////////////////////////////////////////////////////////////////////////// void registerHazard (HazardPtr& h) { std::lock_guard lock(_mutex); _H.push_back(&h); return; } //////////////////////////////////////////////////////////////////////////////// /// @brief unregisterHazard //////////////////////////////////////////////////////////////////////////////// void unregisterHazard (HazardPtr& h) { std::lock_guard lock(_mutex); for (auto it = _H.begin(); it != _H.end(); it++) { if (*it == &h) { _H.erase(it); return; } } } //////////////////////////////////////////////////////////////////////////////// /// @brief lease, call this with your registered HazardPtr structure before /// using the pointer. It is safe to use this pointer until one calls unlease. //////////////////////////////////////////////////////////////////////////////// T const* lease (HazardPtr& h) { int v; T const* p; while (true) { v = _V.load(std::memory_order_consume); // (XXX) // This memory_order_consume corresponds to the change to _V // in exchange() below which uses memory_order_seq_cst, which // implies release semantics. This is important to ensure that // we see the changes to _P just before the version _V // is flipped. p = _P[v].ptr.load(std::memory_order_relaxed); h.ptr = p; // implicit memory_order_seq_cst if (_V.load(std::memory_order_relaxed) != v) { // (YYY) h.ptr = nullptr; // implicit memory_order_seq_cst continue; } break; }; return p; } //////////////////////////////////////////////////////////////////////////////// /// @brief unlease, call this with your registered HazardPtr structure after /// using the pointer. //////////////////////////////////////////////////////////////////////////////// void unlease (HazardPtr& h) { h.ptr = nullptr; // implicit memory_order_seq_cst } //////////////////////////////////////////////////////////////////////////////// /// @brief exchange, call this to exchange the guarded pointer. The function /// can be slow and returns the old version. It is guaranteed that no reader /// is still reading the old version when this method returns, therefore it /// is safe to delete the pointer in that case. //////////////////////////////////////////////////////////////////////////////// T const* exchange (T const* replacement) { std::lock_guard lock(_mutex); int v = _V.load(std::memory_order_relaxed); _P[1-v].ptr.store(replacement, std::memory_order_relaxed); _V = 1-v; // implicit memory_order_seq_cst, whoever sees this // also sees the two above modifications! // Our job is essentially done, we only need to destroy // the old value. However, this might be unsafe, because there might // be a reader. All readers have indicated their reading activity // with a store(std::memory_order_seq_cst) to _H[]. After that // indication, they have rechecked the value of _V and have thus // confirmed that it was not yet changed. Therefore, we can simply // observe _H[*] and wait until none is equal to _P[v]: T const* p = _P[v].ptr.load(std::memory_order_relaxed); while (isHazard(p)) { usleep(250); } // Now it is safe to destroy _P[v] _P[v].ptr = nullptr; return p; } //////////////////////////////////////////////////////////////////////////////// /// @brief internal check whether a given pointer is a hazard //////////////////////////////////////////////////////////////////////////////// private: bool isHazard (T const* p) { for (size_t i = 0; i < _H.size(); i++) { T const* g = _H[i]->ptr.load(std::memory_order_relaxed); if (g != nullptr && g == p) { return true; } } return false; } //////////////////////////////////////////////////////////////////////////////// /// @brief the two versions of the pointer we take care of //////////////////////////////////////////////////////////////////////////////// HazardPtr _P[2]; //////////////////////////////////////////////////////////////////////////////// /// @brief current version of _P, can be 0 or 1 //////////////////////////////////////////////////////////////////////////////// std::atomic _V; //////////////////////////////////////////////////////////////////////////////// /// @brief pointers to all hazard pointer structures that are registered //////////////////////////////////////////////////////////////////////////////// std::vector _H; //////////////////////////////////////////////////////////////////////////////// /// @brief a mutex, only used for slow operations //////////////////////////////////////////////////////////////////////////////// std::mutex _mutex; // Here is a proof that this is all OK: The mutex only ensures // that there is always only at most one mutating thread. // All is standard, except that we must ensure that whenever // _V is changed the mutating thread knows about all readers // that are still using the old version, which is done through // _H[myId]->ptr where id is the id of a thread. The critical // argument needed is the following: Both the change to // _H[myId]->ptr in lease() and the change to _V in exchange() use // memory_order_seq_cst, therefore they happen in some sequential // order and all threads observe the same order. If the reader // in line (YYY) above sees the same value as before in line // (XXX), then any write to _V must be later in the total order // of modifications than the change to _H[myId]->ptr. Therefore // the mutating thread must see the change to _H[myId]->ptr, after // all, it sees its own change to _V. Therefore it is ensured // that _P[v].ptr is returned only when all reading threads have // terminated their lease through unlease(), and thus it is safe // to be deleted. }; } // namespace triagens::basics } // namespace triagens #endif // Local Variables: // mode: outline-minor // outline-regexp: "/// @brief\\|/// {@inheritDoc}\\|/// @page\\|// --SECTION--\\|/// @\\}" // End: