/* * Copyright (C) 2017 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 . */ #pragma once #include #include #include #include namespace sstables { using index_list = std::vector; // Associative cache of summary index -> index_list // Entries stay around as long as there is any live external reference (list_ptr) to them. // Supports asynchronous insertion, ensures that only one entry will be loaded. class shared_index_lists { public: using key_type = uint64_t; struct stats { uint64_t hits = 0; // Number of times entry was found ready uint64_t misses = 0; // Number of times entry was not found uint64_t blocks = 0; // Number of times entry was not ready (>= misses) }; private: class entry : public enable_lw_shared_from_this { public: key_type key; index_list list; shared_promise<> loaded; shared_index_lists& parent; entry(shared_index_lists& parent, key_type key) : key(key), parent(parent) { } ~entry() { parent._lists.erase(key); } bool operator==(const entry& e) const { return key == e.key; } bool operator!=(const entry& e) const { return key != e.key; } }; std::unordered_map _lists; static thread_local stats _shard_stats; public: // Pointer to index_list class list_ptr { lw_shared_ptr _e; public: using element_type = index_list; list_ptr() = default; explicit list_ptr(lw_shared_ptr e) : _e(std::move(e)) {} explicit operator bool() const { return static_cast(_e); } index_list& operator*() { return _e->list; } const index_list& operator*() const { return _e->list; } index_list* operator->() { return &_e->list; } const index_list* operator->() const { return &_e->list; } index_list release() { auto res = _e.owned() ? index_list(std::move(_e->list)) : index_list(_e->list); _e = {}; return std::move(res); } }; shared_index_lists() = default; shared_index_lists(shared_index_lists&&) = delete; shared_index_lists(const shared_index_lists&) = delete; // Returns a future which resolves with a shared pointer to index_list for given key. // Always returns a valid pointer if succeeds. The pointer is never invalidated externally. // // If entry is missing, the loader is invoked. If list is already loading, this invocation // will wait for prior loading to complete and use its result when it's done. // // The loader object does not survive deferring, so the caller must deal with its liveness. template future get_or_load(key_type key, Loader&& loader) { auto i = _lists.find(key); lw_shared_ptr e; if (i != _lists.end()) { e = i->second->shared_from_this(); } else { ++_shard_stats.misses; e = make_lw_shared(*this, key); auto res = _lists.emplace(key, e.get()); assert(res.second); loader(key).then_wrapped([e](future&& f) mutable { if (f.failed()) { e->loaded.set_exception(f.get_exception()); } else { e->list = f.get0(); e->loaded.set_value(); } }); } future<> f = e->loaded.get_shared_future(); if (!f.available()) { ++_shard_stats.blocks; return f.then([e]() mutable { return list_ptr(std::move(e)); }); } else { ++_shard_stats.hits; return make_ready_future(list_ptr(std::move(e))); } } static const stats& shard_stats() { return _shard_stats; } }; }