Files
scylladb/schema_registry.hh
Duarte Nunes 65b21e3a99 schema_registry: Don't leak schemas
When loading a schema asynchronously, we're leaving a strong
reference to the loaded schema in the entry's shared future. This
patch fixed this by storing a shared_promised, which is reset when the
schema is loaded.

Signed-off-by: Duarte Nunes <duarte@scylladb.com>
Message-Id: <20170220193654.17439-1-duarte@scylladb.com>
2017-02-21 09:56:21 +01:00

163 lines
6.5 KiB
C++

/*
* Copyright 2015 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 <unordered_map>
#include <seastar/core/shared_ptr.hh>
#include <seastar/core/timer.hh>
#include <seastar/core/shared_future.hh>
#include "schema.hh"
#include "frozen_schema.hh"
class schema_registry;
using async_schema_loader = std::function<future<frozen_schema>(table_schema_version)>;
using schema_loader = std::function<frozen_schema(table_schema_version)>;
class schema_version_not_found : public std::runtime_error {
public:
schema_version_not_found(table_schema_version v);
};
class schema_version_loading_failed : public std::runtime_error {
public:
schema_version_loading_failed(table_schema_version v);
};
//
// Presence in schema_registry is controlled by different processes depending on
// life cycle stage:
// 1) Initially it's controlled by the loader. When loading fails, entry is removed by the loader.
// 2) When loading succeeds, the entry is controlled by live schema_ptr. It remains present as long as
// there's any live schema_ptr.
// 3) When last schema_ptr dies, entry is deactivated. Currently it is removed immediately, later we may
// want to keep it around for some time to reduce cache misses.
//
// In addition to the above the entry is controlled by lw_shared_ptr<> to cope with races between loaders.
//
class schema_registry_entry : public enable_lw_shared_from_this<schema_registry_entry> {
enum class state {
INITIAL, LOADING, LOADED
};
state _state;
table_schema_version _version; // always valid
schema_registry& _registry; // always valid
async_schema_loader _loader; // valid when state == LOADING
shared_promise<schema_ptr> _schema_promise; // valid when state == LOADING
std::experimental::optional<frozen_schema> _frozen_schema; // engaged when state == LOADED
// valid when state == LOADED
// This is != nullptr when there is an alive schema_ptr associated with this entry.
const ::schema* _schema = nullptr;
enum class sync_state { NOT_SYNCED, SYNCING, SYNCED };
sync_state _sync_state;
shared_promise<> _synced_promise; // valid when _sync_state == SYNCING
friend class schema_registry;
public:
schema_registry_entry(table_schema_version v, schema_registry& r);
schema_registry_entry(schema_registry_entry&&) = delete;
schema_registry_entry(const schema_registry_entry&) = delete;
~schema_registry_entry();
schema_ptr load(frozen_schema);
future<schema_ptr> start_loading(async_schema_loader);
schema_ptr get_schema(); // call only when state >= LOADED
// Can be called from other shards
bool is_synced() const;
// Initiates asynchronous schema sync or returns ready future when is already synced.
future<> maybe_sync(std::function<future<>()> sync);
// Marks this schema version as synced. Syncing cannot be in progress.
void mark_synced();
// Can be called from other shards
frozen_schema frozen() const;
// Can be called from other shards
table_schema_version version() const { return _version; }
public:
// Called by class schema
void detach_schema() noexcept;
};
//
// Keeps track of different versions of table schemas. A per-shard object.
//
// For every schema_ptr obtained through getters, as long as the schema pointed to is
// alive the registry will keep its entry. To ensure remote nodes can query current node
// for schema version, make sure that schema_ptr for the request is alive around the call.
//
class schema_registry {
std::unordered_map<table_schema_version, lw_shared_ptr<schema_registry_entry>> _entries;
friend class schema_registry_entry;
schema_registry_entry& get_entry(table_schema_version) const;
public:
// Looks up schema by version or loads it using supplied loader.
schema_ptr get_or_load(table_schema_version, const schema_loader&);
// Looks up schema by version or returns an empty pointer if not available.
schema_ptr get_or_null(table_schema_version) const;
// Like get_or_load() which takes schema_loader but the loader may be
// deferring. The loader is copied must be alive only until this method
// returns. If the loader fails, the future resolves with
// schema_version_loading_failed.
future<schema_ptr> get_or_load(table_schema_version, const async_schema_loader&);
// Looks up schema version. Throws schema_version_not_found when not found
// or loading is in progress.
schema_ptr get(table_schema_version) const;
// Looks up schema version. Throws schema_version_not_found when not found
// or loading is in progress.
frozen_schema get_frozen(table_schema_version) const;
// Attempts to add given schema to the registry. If the registry already
// knows about the schema, returns existing entry, otherwise returns back
// the schema which was passed as argument. Users should prefer to use the
// schema_ptr returned by this method instead of the one passed to it,
// because doing so ensures that the entry will be kept in the registry as
// long as the schema is actively used.
schema_ptr learn(const schema_ptr&);
};
schema_registry& local_schema_registry();
// Schema pointer which can be safely accessed/passed across shards via
// const&. Useful for ensuring that schema version obtained on one shard is
// automatically propagated to other shards, no matter how long the processing
// chain will last.
class global_schema_ptr {
schema_ptr _ptr;
unsigned _cpu_of_origin;
public:
// Note: the schema_ptr must come from the current shard and can't be nullptr.
global_schema_ptr(const schema_ptr&);
// The other may come from a different shard.
global_schema_ptr(const global_schema_ptr& other);
// The other must come from current shard.
global_schema_ptr(global_schema_ptr&& other);
// May be invoked across shards. Always returns an engaged pointer.
schema_ptr get() const;
operator schema_ptr() const { return get(); }
};