1
0
Fork 0
arangodb/lib/Basics/DataGuardian.h

245 lines
9.8 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// @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<typename T>
class DataGuardian {
public:
////////////////////////////////////////////////////////////////////////////////
/// @brief HazardPtr, class for keeping a hazard pointer with appropriate
/// padding.
////////////////////////////////////////////////////////////////////////////////
class HazardPtr {
public:
std::atomic<T const*> ptr;
private:
char padding[64-sizeof(std::atomic<T const*>)];
};
////////////////////////////////////////////////////////////////////////////////
/// @brief constructor
////////////////////////////////////////////////////////////////////////////////
DataGuardian () {
_P[0].ptr = nullptr;
_P[1].ptr = nullptr;
_V = 0;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief destructor
////////////////////////////////////////////////////////////////////////////////
~DataGuardian () {
std::lock_guard<std::mutex> 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<std::mutex> lock(_mutex);
_H.push_back(&h);
return;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief unregisterHazard
////////////////////////////////////////////////////////////////////////////////
void unregisterHazard (HazardPtr& h) {
std::lock_guard<std::mutex> 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<std::mutex> 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[<theirId>]. 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<int> _V;
////////////////////////////////////////////////////////////////////////////////
/// @brief pointers to all hazard pointer structures that are registered
////////////////////////////////////////////////////////////////////////////////
std::vector<HazardPtr*> _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: