blob: 0736c705731838c0750f643b66e8d384b5c07b53 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef LOCKDEP_LOCK_DEPENDENCY_SET_H_
#define LOCKDEP_LOCK_DEPENDENCY_SET_H_
#include <atomic>
#include <cstddef>
#include <lockdep/common.h>
namespace lockdep {
// A lock-free, wait-free hash set that tracks the set of lock classes that have
// been acquired prior the lock class that owns the set. Each lock class
// maintains its own dependency set.
//
// Implementation note: This hash set makes use of relaxed atomic operations.
// This approach is appropriate because the only variables communicated between
// threads are the values of the atomic variables themselves, other loads and
// stores are not published. Additionally, sequential consistency within a
// thread is ensured due to control dependencies only on the atomic variables.
class LockDependencySet {
public:
LockDependencySet() = default;
LockDependencySet(const LockDependencySet&) = delete;
void operator=(const LockDependencySet&) = delete;
LockDependencySet(LockDependencySet&&) = delete;
void operator=(LockDependencySet&&) = delete;
// Checks the dependency hash set for the given lock class. This method may
// safely race with AddLockClass(), converging on the correct answer by the
// next check.
bool HasLockClass(LockClassId id) const {
for (size_t i = 0; i < kMaxLockDependencies; i++) {
const auto& entry = GetEntry(id, i);
const LockClassId entry_id = entry.load(std::memory_order_relaxed);
if (entry_id == id)
return true;
else if (entry_id == kInvalidLockClassId)
return false;
}
return false;
}
// Adds the given lock class id to the dependency set if not already present.
// Updates the set using the following lock free approach:
// 1. The dependency set is fixed size and all entries start out empty.
// 2. New entries are added using open addressing with linear probing.
// 3. An entry may only change from empty to holding a lock class id.
// 4. To add an entry the set is probed linearly until either:
// a) The id to add appears in the set already.
// b) The first empty entry is found.
// c) The entire set has been probed; return max dependencies error.
// 5. Attempt to compare-exchange the empty entry with the lock class id:
// a) If the update succeeds return success.
// b) If the update does not succeed but the winning value is the same
// lock class id return with success.
// c) If the update does not succeed and the winning value is a different
// lock class id proceed to the next entry and continue the probe from
// step #4.
LockResult AddLockClass(LockClassId id) {
for (size_t i = 0; i < kMaxLockDependencies; i++) {
auto& entry = GetEntry(id, i);
LockClassId entry_id = entry.load(std::memory_order_relaxed);
if (entry_id == id)
return LockResult::DependencyExists;
while (entry_id == kInvalidLockClassId) {
const bool result = entry.compare_exchange_weak(entry_id, id, std::memory_order_relaxed,
std::memory_order_relaxed);
if (result) {
return LockResult::Success;
} else if (entry_id == id) {
return LockResult::DependencyExists;
} else {
// Continue to find an unused slot, moving back to the outer
// loop because of the while loop condition.
}
}
}
return LockResult::MaxLockDependencies;
}
// Iterator type to traverse the set of populated lock classes. Entries
// added after the iterator is created may or may not be returned, depending
// on where they land in the hash set relative to the index member.
struct Iterator {
const LockDependencySet* set;
size_t index;
LockClassId operator*() const {
// TODO(eieio): See if it makes sense to add a memory order param
// to the iterator.
return set->list_[index].load(std::memory_order_relaxed);
}
Iterator operator++() {
while (index != kMaxLockDependencies) {
index++;
if (**this != kInvalidLockClassId)
break;
}
return *this;
}
bool operator!=(const Iterator& other) const {
return set != other.set || index != other.index;
}
};
// Iterator accessors.
Iterator begin() const {
Iterator iter{this, 0};
if (*iter == kInvalidLockClassId)
++iter;
return iter;
}
Iterator end() const { return {this, kMaxLockDependencies}; }
// Clears the dependency set. This method is not used by the main algorithm
// but may be useful for unit tests and benchmarking. Until lock sequence
// memoization is implemented it is generally safe to call this method at
// any time, resulting in dependency set being rebuilt at runtime. Once
// lock sequence memoization is implemented it is necessary to clear the
// memoization table whenever any dependency set is cleared so that the
// dependency set can be rebuilt; failure to do so could result in missing
// new lock violations.
void clear() {
for (auto& entry : list_)
entry.store(kInvalidLockClassId, std::memory_order_relaxed);
}
private:
// Returns a reference to an entry by computing a trivial hash of the given id
// and a linear probe offset.
std::atomic<LockClassId>& GetEntry(LockClassId id, size_t offset) {
const size_t index = (id + offset) % kMaxLockDependencies;
return list_[index];
}
const std::atomic<LockClassId>& GetEntry(LockClassId id, size_t offset) const {
const size_t index = (id + offset) % kMaxLockDependencies;
return list_[index];
}
// The list of atomic variables that make up the hash set. Initialized to
// kInvalidLockClassId (0).
std::atomic<LockClassId> list_[kMaxLockDependencies]{};
};
} // namespace lockdep
#endif // LOCKDEP_LOCK_DEPENDENCY_SET_H_