/** * Copyright (C) 2018-present MongoDB, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the Server Side Public License, version 1, * as published by MongoDB, Inc. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * Server Side Public License for more details. * * You should have received a copy of the Server Side Public License * along with this program. If not, see * . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the Server Side Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #pragma once #include #include #include #include "mongo/platform/mutex.h" #include "mongo/util/assert_util.h" #include "mongo/util/concurrency/with_lock.h" #include "mongo/util/lru_cache.h" #include "mongo/util/scopeguard.h" #include "mongo/util/string_map.h" namespace mongo { /** * Type indicating that the specific cache instance does not support causal consistency. To be used * as the default 'Time' parameter to the 'InvalidatingLRUCache' template, indicating that the cache * is not causally consistent. */ struct CacheNotCausallyConsistent { bool operator==(const CacheNotCausallyConsistent&) const { return true; } bool operator!=(const CacheNotCausallyConsistent&) const { return false; } bool operator>(const CacheNotCausallyConsistent&) const { return false; } bool operator>=(const CacheNotCausallyConsistent&) const { return true; } bool operator<(const CacheNotCausallyConsistent&) const { return false; } bool operator<=(const CacheNotCausallyConsistent&) const { return true; } std::string toString() const { return "NotCausallyConsistent"; } }; /** * Helper for determining if a given type is CacheNotCausallyConsistent or not. */ template struct isCausallyConsistentImpl : std::true_type {}; template <> struct isCausallyConsistentImpl : std::false_type {}; template inline constexpr bool isCausallyConsistent = isCausallyConsistentImpl::value; /** * Specifies the desired causal consistency for calls to 'get' (and 'acquire', respectively in the * ReadThroughCache, which is its main consumer). */ enum class CacheCausalConsistency { // Provides the fastest acquire semantics, where if the cache already contains a // (non-invalidated) value cached, it will be immediately returned. Otherwise, the 'acquire' // call will block. kLatestCached, // Provides a causally-consistent semantics with respect to a previous call to // 'advanceTimeInStore', where if the cache's (non-invalidated) value has time == timeInStore, // the value will be immediately returned. Otherwise, the 'acquire' call will block. kLatestKnown, }; template struct DefaultKeyHasher : DefaultHasher {}; template struct LruKeyTraits { using hasher = DefaultKeyHasher; using comparator = std::equal_to; }; template <> struct LruKeyTraits { using hasher = StringMapHasher; using comparator = std::equal_to<>; }; template using LruKeyHasher = typename LruKeyTraits::hasher; template using LruKeyComparator = typename LruKeyTraits::comparator; template struct IsTrustedHasher, Key> : std::true_type {}; /** * Extension built on top of 'LRUCache', which provides thread-safety, introspection and most * importantly the ability to invalidate each entry and/or associate a logical timestamp in order to * indicate to potential callers that the entry should not be used anymore. * * The type for 'Time' must support 'operator <' and its default constructor 'Time()' must provide * the lowest possible value for the time. */ template class InvalidatingLRUCache { /** * Data structure representing the values stored in the cache. */ struct StoredValue { /** * The 'owningCache' and 'key' values can be nullptr/boost::none in order to support the * detached mode of ValueHandle below. */ StoredValue(InvalidatingLRUCache* owningCache, uint64_t epoch, boost::optional&& key, Value&& value, const Time& time, const Time& timeInStore) : owningCache(owningCache), epoch(epoch), key(std::move(key)), value(std::move(value)), time(time), timeInStore(timeInStore), isValid(time == timeInStore) { invariant(time <= timeInStore); } ~StoredValue() { if (!owningCache) return; stdx::unique_lock ul(owningCache->_mutex); auto& evictedCheckedOutValues = owningCache->_evictedCheckedOutValues; auto it = evictedCheckedOutValues.find(*key); // The lookup above can encounter the following cases: // // 1) The 'key' is not on the evictedCheckedOutValues map, because a second value for it // was inserted, which was also evicted and all its handles expired (so it got removed) if (it == evictedCheckedOutValues.end()) return; auto storedValue = it->second.lock(); // 2) There are no more references to 'key', but it is stil on the map, which means // either we are running its destrutor, or some other thread is running the destructor // of a different epoch. In either case it is fine to remove the 'it' because we are // under a mutex. if (!storedValue) { evictedCheckedOutValues.erase(it); return; } ul.unlock(); // 3) The value for 'key' is for a different epoch, in which case we must dereference // the '.lock()'ed storedValue outside of the mutex in order to avoid reentrancy while // holding a mutex. invariant(storedValue->epoch != epoch); } // Copy and move constructors need to be deleted in order to avoid having to make the // destructor to account for the object having been moved StoredValue(StoredValue&) = delete; StoredValue& operator=(StoredValue&) = delete; StoredValue(StoredValue&&) = delete; StoredValue& operator=(StoredValue&&) = delete; // The cache which stores this key/value pair InvalidatingLRUCache* const owningCache; // Identity associated with this value. See the destructor for its usage. const uint64_t epoch; // The key/value pair. See the comments on the constructor about why the key is optional. const boost::optional key; Value value; // Timestamp associated with the current 'value'. The semantics of the time is entirely up // to the user of the cache, but it must be monotonically increasing for the same key. const Time time; // Timestamp which the store has indicated as available for 'key' (through a call to // 'advanceTimeInStore'). Starts as equal to 'time' and always moves forward, under // '_mutex'. Time timeInStore; // Can be read without synchronisation. Transitions to false only once, under `_mutex` in // order to mark the entry as invalid either as a result of 'invalidate' or // 'advanceTimeInStore'. AtomicWord isValid; }; using Cache = LRUCache, LruKeyHasher, LruKeyComparator>; public: template static constexpr bool IsComparable = Cache::template IsComparable; /** * The 'cacheSize' parameter specifies the maximum size of the cache before the least recently * used entries start getting evicted. It is allowed to be zero, in which case no entries will * actually be cached, which is only meaningful for the behaviour of `insertOrAssignAndGet`. */ explicit InvalidatingLRUCache(size_t cacheSize) : _cache(cacheSize) {} ~InvalidatingLRUCache() { invariant(_evictedCheckedOutValues.empty()); } /** * Wraps the entries returned from the cache. */ class ValueHandle { public: // The three constructors below are present in order to offset the fact that the cache // doesn't support pinning items. Their only usage must be in the authorization mananager // for the internal authentication user. explicit ValueHandle(Value&& value) : _value(std::make_shared( nullptr, 0, boost::none, std::move(value), Time(), Time())) {} explicit ValueHandle(Value&& value, const Time& t) : _value( std::make_shared(nullptr, 0, boost::none, std::move(value), t, t)) {} ValueHandle() = default; operator bool() const { return bool(_value); } bool isValid() const { invariant(bool(*this)); return _value->isValid.loadRelaxed(); } const Time& getTime() const { invariant(bool(*this)); return _value->time; } Value* get() { invariant(bool(*this)); return &_value->value; } const Value* get() const { invariant(bool(*this)); return &_value->value; } Value& operator*() { return *get(); } const Value& operator*() const { return *get(); } Value* operator->() { return get(); } const Value* operator->() const { return get(); } private: friend class InvalidatingLRUCache; explicit ValueHandle(std::shared_ptr value) : _value(std::move(value)) {} std::shared_ptr _value; }; /** * Inserts or updates a key with a new value. If 'key' was checked-out at the time this method * was called, it will become invalidated. * * The 'time' parameter is mandatory for causally-consistent caches, but not needed otherwise * (since the time never changes). */ void insertOrAssign(const Key& key, Value&& value) { MONGO_STATIC_ASSERT_MSG( !isCausallyConsistent