blob: d2012085b4ae13f445c23b86aaa3d6db3e47ebac [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.
#pragma once
#include <cstddef>
#include <new>
#include <utility>
#include <lockdep/common.h>
#include <lockdep/guard.h>
#include <lockdep/lock_class.h>
namespace lockdep {
// Guards simultaneous acquisitions of multiple locks of the same type.
template <size_t Size, typename LockType, typename Option = void>
class GuardMultiple {
// Prevent passing nestable locks to GuardMultiple to avoid inconsistency
// due to mixing external ordering and address ordering.
static_assert((LockTraits<LockType>::Flags & LockFlagsNestable) == 0,
"Nestable locks cannot be used with GuardMultiple!");
public:
GuardMultiple(GuardMultiple&&) = delete;
GuardMultiple& operator=(GuardMultiple&&) = delete;
GuardMultiple(const GuardMultiple&) = delete;
GuardMultiple& operator=(const GuardMultiple&) = delete;
// Locks the given set of locks, each belonging to the same lock class,
// automatically ordering the arguments by address to preserve the
// intra-class ordering invariant.
template <typename Class, size_t Index,
template <typename, typename, size_t> class... Locks>
GuardMultiple(Locks<Class, LockType, Index>*... locks)
: GuardMultiple{std::make_index_sequence<sizeof...(locks)>{}, locks...} {}
~GuardMultiple() {
// Destroy union storage. Array elements are destroyed in reverse order.
guard_storage_.~Storage();
}
// Releases all of the locks guarded by this instance.
void Release() {
for (size_t i = 0; i < Size; i++)
guard_storage_.guards[i].Release();
}
// Returns true if all of the guards are holding acquired locks. In general
// all of the guards should be in the same state.
explicit operator bool() const {
for (size_t i = 0; i < Size; i++) {
if (!guard_storage_.guards[i])
return false;
}
return true;
}
private:
// Quickly sorts an array of pointers in-place using insertion sort. When
// Size is 2 this has been observed to instantiate as an inline swap.
template <typename T>
static void InsertionSortPointers(T* (&elements)[Size]) {
size_t i = 1;
while (i < Size) {
T* x = elements[i];
ssize_t j = i - 1;
while (j >= 0 && elements[j] > x) {
elements[j + 1] = elements[j];
j--;
}
elements[j + 1] = x;
i++;
}
}
// Builds an array of Lock<LockType> pointers, sorts the array by ascending
// address, and then aggregate initializes the union storage. Array elements
// are constructed in order.
template <size_t... Is, template <typename> class... Locks>
GuardMultiple(std::index_sequence<Is...>, Locks<LockType>*... locks) {
Lock<LockType>* lock_pointers[] = {locks...};
InsertionSortPointers(lock_pointers);
new (&guard_storage_) Storage{
{{OrderedLock, lock_pointers[Is],
reinterpret_cast<uintptr_t>(lock_pointers[Is])}...}};
}
// Storage type to permit late initialization of the underlying Guard
// instances. Since Guard objects are not default constructible, using union
// semantics allows the GuardMultiple constructor to delay initializing the
// Guard array until after it has sorted the incoming lock pointers. This
// type also leverages aggregate initialization since we can't use
// std::array. Unfortunately, GCC only supports direct initialization of
// C-style arrays through aggregate initialization so that is the only
// option.
struct Storage {
Guard<LockType, Option> guards[Size];
};
union {
Storage guard_storage_;
};
};
} // namespace lockdep