mirror of
https://github.com/scylladb/scylladb.git
synced 2026-05-01 21:55:50 +00:00
loading_shared_values/loading_cache'es iterators interface is dangerous/fragile because iterator doesn't "lock" the entry it points to and if there is a preemption point between aquiring non-end() iterator and its dereferencing the corresponding cache entry may had already got evicted (for whatever reason, e.g. cache size constraints or expiration) and then dereferencing may end up in a use-after-free and we don't have any protection against it in the value_extractor_fn today. And this is in addition to #8920. So, instead of trying to fix the iterator interface this patch kills two birds in a single shot: we are ditching the iterators interface completely and return value_ptr from find(...) instead - the same one we are returning from loading_cache::get_ptr(...) asyncronous APIs. A similar rework is done to a loading_shared_values loading_cache is based on: we drop iterators interface and return loading_shared_values::entry_ptr from find(...) instead. loading_cache::value_ptr already takes care of "lock"ing the returned value so that it would relain readable even if it's evicted from the cache by the time one tries to read it. And of course it also takes care of updating the last read time stamp and moving the corresponding item to the top of the MRU list. Fixes #8920 Signed-off-by: Vlad Zolotarov <vladz@scylladb.com> Message-Id: <20210817222404.3097708-1-vladz@scylladb.com>
180 lines
5.8 KiB
C++
180 lines
5.8 KiB
C++
/*
|
|
* Copyright (C) 2017-present ScyllaDB
|
|
*
|
|
* Modified by ScyllaDB
|
|
*/
|
|
|
|
/*
|
|
* This file is part of Scylla.
|
|
*
|
|
* Scylla is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Scylla 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
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include "utils/loading_cache.hh"
|
|
#include "utils/hash.hh"
|
|
#include "cql3/statements/prepared_statement.hh"
|
|
#include "cql3/column_specification.hh"
|
|
|
|
namespace cql3 {
|
|
|
|
using prepared_cache_entry = std::unique_ptr<statements::prepared_statement>;
|
|
|
|
struct prepared_cache_entry_size {
|
|
size_t operator()(const prepared_cache_entry& val) {
|
|
// TODO: improve the size approximation
|
|
return 10000;
|
|
}
|
|
};
|
|
|
|
typedef bytes cql_prepared_id_type;
|
|
typedef int32_t thrift_prepared_id_type;
|
|
|
|
/// \brief The key of the prepared statements cache
|
|
///
|
|
/// We are going to store the CQL and Thrift prepared statements in the same cache therefore we need generate the key
|
|
/// that is going to be unique in both cases. Thrift use int32_t as a prepared statement ID, CQL - MD5 digest.
|
|
///
|
|
/// We are going to use an std::pair<CQL_PREP_ID_TYPE, int64_t> as a key. For CQL statements we will use {CQL_PREP_ID, std::numeric_limits<int64_t>::max()} as a key
|
|
/// and for Thrift - {CQL_PREP_ID_TYPE(0), THRIFT_PREP_ID}. This way CQL and Thrift keys' values will never collide.
|
|
class prepared_cache_key_type {
|
|
public:
|
|
using cache_key_type = std::pair<cql_prepared_id_type, int64_t>;
|
|
|
|
private:
|
|
cache_key_type _key;
|
|
|
|
public:
|
|
prepared_cache_key_type() = default;
|
|
explicit prepared_cache_key_type(cql_prepared_id_type cql_id) : _key(std::move(cql_id), std::numeric_limits<int64_t>::max()) {}
|
|
explicit prepared_cache_key_type(thrift_prepared_id_type thrift_id) : _key(cql_prepared_id_type(), thrift_id) {}
|
|
|
|
cache_key_type& key() { return _key; }
|
|
const cache_key_type& key() const { return _key; }
|
|
|
|
static const cql_prepared_id_type& cql_id(const prepared_cache_key_type& key) {
|
|
return key.key().first;
|
|
}
|
|
static thrift_prepared_id_type thrift_id(const prepared_cache_key_type& key) {
|
|
return key.key().second;
|
|
}
|
|
|
|
bool operator==(const prepared_cache_key_type& other) const {
|
|
return _key == other._key;
|
|
}
|
|
|
|
bool operator!=(const prepared_cache_key_type& other) const {
|
|
return !(*this == other);
|
|
}
|
|
};
|
|
|
|
class prepared_statements_cache {
|
|
public:
|
|
struct stats {
|
|
uint64_t prepared_cache_evictions = 0;
|
|
};
|
|
|
|
static stats& shard_stats() {
|
|
static thread_local stats _stats;
|
|
return _stats;
|
|
}
|
|
|
|
struct prepared_cache_stats_updater {
|
|
static void inc_hits() noexcept {}
|
|
static void inc_misses() noexcept {}
|
|
static void inc_blocks() noexcept {}
|
|
static void inc_evictions() noexcept {
|
|
++shard_stats().prepared_cache_evictions;
|
|
}
|
|
};
|
|
|
|
private:
|
|
using cache_key_type = typename prepared_cache_key_type::cache_key_type;
|
|
using cache_type = utils::loading_cache<cache_key_type, prepared_cache_entry, utils::loading_cache_reload_enabled::no, prepared_cache_entry_size, utils::tuple_hash, std::equal_to<cache_key_type>, prepared_cache_stats_updater>;
|
|
using cache_value_ptr = typename cache_type::value_ptr;
|
|
using checked_weak_ptr = typename statements::prepared_statement::checked_weak_ptr;
|
|
|
|
public:
|
|
static const std::chrono::minutes entry_expiry;
|
|
|
|
using key_type = prepared_cache_key_type;
|
|
using value_type = checked_weak_ptr;
|
|
using statement_is_too_big = typename cache_type::entry_is_too_big;
|
|
|
|
private:
|
|
cache_type _cache;
|
|
|
|
public:
|
|
prepared_statements_cache(logging::logger& logger, size_t size)
|
|
: _cache(size, entry_expiry, logger)
|
|
{}
|
|
|
|
template <typename LoadFunc>
|
|
future<value_type> get(const key_type& key, LoadFunc&& load) {
|
|
return _cache.get_ptr(key.key(), [load = std::forward<LoadFunc>(load)] (const cache_key_type&) { return load(); }).then([] (cache_value_ptr v_ptr) {
|
|
return make_ready_future<value_type>((*v_ptr)->checked_weak_from_this());
|
|
});
|
|
}
|
|
|
|
value_type find(const key_type& key) {
|
|
cache_value_ptr vp = _cache.find(key.key());
|
|
if (vp) {
|
|
return (*vp)->checked_weak_from_this();
|
|
}
|
|
return value_type();
|
|
}
|
|
|
|
template <typename Pred>
|
|
void remove_if(Pred&& pred) {
|
|
static_assert(std::is_same<bool, std::result_of_t<Pred(::shared_ptr<cql_statement>)>>::value, "Bad Pred signature");
|
|
|
|
_cache.remove_if([&pred] (const prepared_cache_entry& e) {
|
|
return pred(e->statement);
|
|
});
|
|
}
|
|
|
|
size_t size() const {
|
|
return _cache.size();
|
|
}
|
|
|
|
size_t memory_footprint() const {
|
|
return _cache.memory_footprint();
|
|
}
|
|
|
|
future<> stop() {
|
|
return _cache.stop();
|
|
}
|
|
};
|
|
}
|
|
|
|
namespace std { // for prepared_statements_cache log printouts
|
|
inline std::ostream& operator<<(std::ostream& os, const typename cql3::prepared_cache_key_type::cache_key_type& p) {
|
|
os << "{cql_id: " << p.first << ", thrift_id: " << p.second << "}";
|
|
return os;
|
|
}
|
|
|
|
inline std::ostream& operator<<(std::ostream& os, const cql3::prepared_cache_key_type& p) {
|
|
os << p.key();
|
|
return os;
|
|
}
|
|
|
|
template<>
|
|
struct hash<cql3::prepared_cache_key_type> final {
|
|
size_t operator()(const cql3::prepared_cache_key_type& k) const {
|
|
return utils::tuple_hash()(k.key());
|
|
}
|
|
};
|
|
}
|