blob: 62df00b543dc5cb7c6faffdeb3ed2e8cb877d87c [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 COBALT_SRC_LIB_UTIL_PROTECTED_FIELDS_H_
#define COBALT_SRC_LIB_UTIL_PROTECTED_FIELDS_H_
#include <mutex>
#include <shared_mutex>
// ProtectedFields is a useful abstraction for having a set of fields that are protected by a mutex.
// Using protected fields instead of a plain mutex has several advantages including:
//
// 1. The only way to access the protected fields is by locking the mutex, thereby ensuring that
// any places that read/write to the fields are safe. (This increases confidence in code
// correctness for the author, and reduces the burden on the code reviewer)
// 2. The mutex locks use RAII semantics, and as such the fields can be locked, accessed, and
// re-locked in a single line.
// 3. The ProtectedFields can be locked mutably (using lock()) or immutably (using const_lock())
// which allows reading protected fields in const functions (without needing to declare your
// ProtectedFields as mutable.
//
// Simple example usage:
//
// struct SafeField {
// int protected_int;
// };
// ProtectedFields<SafeField> protected_fields;
// protected_fields.lock()->protected_int = 100;
// LOG(INFO) << "Current protected_int: " <<
// protected_fields.const_lock()->protected_int;
//
// Example that holds the lock for multiple lines:
//
// struct SafeFields {
// int protected_int;
// char protected_char;
// };
// ProtectedFields<SafeField> protected_fields;
// auto fields = protected_fields.lock();
// fields->protected_int = 100;
// fields->protected_char = 'a';
//
// Example of taking locked fields as an argument:
//
// struct SafeFields {
// int protected_int;
// char protected_char;
// };
// void SetFieldsLocked(ProtectedFields<SafeFields>::LockedFieldsPtr *fields) {
// auto &f = *fields;
// f->protected_int = 100;
// f->protected_char = 'a';
// }
//
// Example using RWProtectedFields:
//
// RWProtectedFields<int> rw_protected_int(10);
// auto reader_lock = rw_protected_int.const_lock();
// std::thread([&rw_protected_int]() {
// // This works, since any number of reader locks can be taken at once.
// rw_protected_int.const_lock();
// }).join();
//
// Example using LockedFieldsPtr with a condition variable:
//
// struct SafeFields {
// std::condition_variable_any shutdown_notifier;
// bool shutdown;
// }
// ProtectedFields<SafeFields> protected_fields;
// auto fields = protected_fields.lock();
// // Wait until shutdown is true.
// fields->shutdown_notifier.wait(fields, [&fields]() { return fields->shutdown; });
namespace cobalt::util {
// BaseProtectedFields is a template class and is specialized into ProtectedFields and
// RWProtectedFields. (See below).
//
// Template types:
// |Fields| can be any type, and is the object that will be protected.
// |Mutex| is any type that implements the *Mutex* named requirement. (See:
// https://en.cppreference.com/w/cpp/named_req/Mutex)
// |ConstLock| is the lock type used for const_lock(). E.g. for ProtectedFields, it will be
// std::unique_lock<std::mutex>, and for RWProtectedFields it will be
// std::shared_lock<std::shared_mutex>.
template <class Fields, class Mutex, class ConstLock>
class BaseProtectedFields {
public:
// BaseLockedFieldsPtr holds a pointer to Fields, as well a
// unique_lock<mutex>.
//
// Variables of this type are accessed using pointer syntax.
//
// Implements:
// - *BasicLockable*: This allows ProtectedFields objects to be used as the lock value in the wait
// methods of std::condition_variable_any. (See:
// https://en.cppreference.com/w/cpp/named_req/BasicLockable)
//
// N.B. std::condition_variable may be more performant in some cases, and may
// be preferred in performance critical situations. In those cases, raw
// std::unique_lock<std::mutex> should be used.
//
// Template types:
// |InnerFields| is either the same as Fields above, or in the case of a ConstLockedFieldsPtr, it
// is `const Fields`.
// |LockType| The lock type to hold. (e.g. std::unique_lock<std::mutex>)
template <class InnerFields, class LockType>
class BaseLockedFieldsPtr {
public:
InnerFields* operator->() { return fields_; }
InnerFields& operator*() { return *fields_; }
// Blocks until a lock can be obtained for the current execution agent (thread, process, task).
// If an exception is thrown, no lock is obtained. Additionally, after the lock is obtained, the
// inner fields become available again.
void lock() {
lock_.lock();
// Make the inner fields_ accessible again.
std::swap(fields_, hidden_fields_);
}
// Releases the lock held by the execution agent. Throws no exceptions. Additionally, after the
// lock is released, the inner fields are no longer accessible.
//
// Requires: The current execution agent should hold the lock.
void unlock() {
// Make the inner fields_ inaccessible. When the lock is not held, accessing the fields using
// * or -> is undefined behavior.
std::swap(fields_, hidden_fields_);
lock_.unlock();
}
private:
friend class BaseProtectedFields;
BaseLockedFieldsPtr(Mutex* mutex, InnerFields* fields) : lock_(*mutex), fields_(fields) {}
LockType lock_;
InnerFields* fields_;
InnerFields* hidden_fields_;
public:
// Disable copy/assign. Only allow move.
BaseLockedFieldsPtr(BaseLockedFieldsPtr&&) noexcept = default;
BaseLockedFieldsPtr& operator=(BaseLockedFieldsPtr&&) noexcept = default;
BaseLockedFieldsPtr& operator=(const BaseLockedFieldsPtr&) = delete;
BaseLockedFieldsPtr(const BaseLockedFieldsPtr&) = delete;
};
// Alias LockedFieldsPtrImpl for const and non-const variants.
using LockedFieldsPtr = BaseLockedFieldsPtr<Fields, std::unique_lock<Mutex>>;
using ConstLockedFieldsPtr = BaseLockedFieldsPtr<const Fields, ConstLock>;
LockedFieldsPtr lock() { return LockedFieldsPtr(&mutex_, &fields_); }
ConstLockedFieldsPtr const_lock() const { return ConstLockedFieldsPtr(&mutex_, &fields_); }
private:
mutable Mutex mutex_;
Fields fields_;
public:
// Forward whatever arguments passed to BaseProtectedFields onto the constructor for Fields.
template <class... Args>
explicit BaseProtectedFields(Args&&... args) : fields_(std::forward<Args>(args)...) {}
BaseProtectedFields& operator=(const BaseProtectedFields&) = delete;
BaseProtectedFields(const BaseProtectedFields&) = delete;
BaseProtectedFields() = default;
// Make BaseProtectedFields Move constructible/assignable
BaseProtectedFields& operator=(BaseProtectedFields&& other) noexcept {
std::unique_lock<Mutex> lock(other.mutex_);
fields_ = std::move(other.fields_);
return *this;
}
BaseProtectedFields(BaseProtectedFields&& other) noexcept { *this = other; }
};
// The default implementation of ProtectedFields. Uses standard mutexes.
template <class Fields>
using ProtectedFields = BaseProtectedFields<Fields, std::mutex, std::unique_lock<std::mutex>>;
// A ProtectedFields implementation that supports Reader/Writer semantics. (const_lock is a reader
// lock, lock is a writer lock)
template <class Fields>
using RWProtectedFields =
BaseProtectedFields<Fields, std::shared_mutex, std::shared_lock<std::shared_mutex>>;
} // namespace cobalt::util
#endif // COBALT_SRC_LIB_UTIL_PROTECTED_FIELDS_H_