// Copyright (c) 2016-present, Facebook, Inc. All rights reserved. // This source code is licensed under both the GPLv2 (found in the // COPYING file in the root directory) and Apache 2.0 License // (found in the LICENSE.Apache file in the root directory). #pragma once #include #include #include #include #include #include #include #include #include "rocksdb/status.h" namespace ROCKSDB_NAMESPACE { class Customizable; class Logger; class ObjectLibrary; // Returns a new T when called with a string. Populates the std::unique_ptr // argument if granting ownership to caller. template using FactoryFunc = std::function*, std::string*)>; // The signature of the function for loading factories // into an object library. This method is expected to register // factory functions in the supplied ObjectLibrary. // The ObjectLibrary is the library in which the factories will be loaded. // The std::string is the argument passed to the loader function. // The RegistrarFunc should return the number of objects loaded into this // library using RegistrarFunc = std::function; template using ConfigureFunc = std::function; class ObjectLibrary { private: // Base class for an Entry in the Registry. class Entry { public: virtual ~Entry() {} virtual bool Matches(const std::string& target) const = 0; virtual const char* Name() const = 0; }; public: // Class for matching target strings to a pattern. // Entries consist of a name that starts the pattern and attributes // The following attributes can be added to the entry: // -Suffix: Comparable to name(suffix) // -Separator: Comparable to name(separator).+ or name(separator).* // -Number: Comparable to name(separator).[0-9]+ // -AltName: Comparable to (name|alt) // -Optional: Comparable to name(separator)? // Multiple separators can be combined and cause multiple matches. // For example, Pattern("A").AnotherName("B").AddSeparator("@").AddNumber("#") // is roughly equivalent to "(A|B)@.+#.+" // // Note that though this class does provide some regex-style matching, // it is not a full regex parser and has some key differences: // - Separators are matched left-most. For example, an entry // Name("Hello").AddSeparator(" ").AddSuffix("!") would match // "Hello world!", but not "Hello world!!" // - No backtracking is necessary, enabling reliably efficient matching class PatternEntry : public Entry { private: enum Quantifier { kMatchZeroOrMore, // [suffix].* kMatchAtLeastOne, // [suffix].+ kMatchExact, // [suffix] kMatchInteger, // [suffix][0-9]+ kMatchDecimal, // [suffix][0-9]+[.][0-9]+ }; public: // Short-cut for creating an entry that matches to a // Customizable::IndividualId static PatternEntry AsIndividualId(const std::string& name) { PatternEntry entry(name, true); entry.AddSeparator("@"); entry.AddSeparator("#"); return entry; } // Creates a new PatternEntry for "name". If optional is true, // Matches will also return true if name==target explicit PatternEntry(const std::string& name, bool optional = true) : name_(name), optional_(optional), slength_(0) { nlength_ = name_.size(); } // Adds a suffix (exact match of separator with no trailing characters) to // the separator PatternEntry& AddSuffix(const std::string& suffix) { separators_.emplace_back(suffix, kMatchExact); slength_ += suffix.size(); return *this; } // Adds a separator (exact match of separator with trailing characters) to // the entry // If at_least_one is true, the separator must be followed by at least // one character (e.g. separator.+). // If at_least_one is false, the separator may be followed by zero or // more characters (e.g. separator.*). PatternEntry& AddSeparator(const std::string& separator, bool at_least_one = true) { slength_ += separator.size(); if (at_least_one) { separators_.emplace_back(separator, kMatchAtLeastOne); ++slength_; } else { separators_.emplace_back(separator, kMatchZeroOrMore); } return *this; } // Adds a separator (exact match of separator with trailing numbers) to the // entry PatternEntry& AddNumber(const std::string& separator, bool is_int = true) { separators_.emplace_back(separator, (is_int) ? kMatchInteger : kMatchDecimal); slength_ += separator.size() + 1; return *this; } // Sets another name that this entry will match, similar to (name|alt) PatternEntry& AnotherName(const std::string& alt) { names_.emplace_back(alt); return *this; } // Sets whether the separators are required -- similar to name(separator)? // If optional is true, then name(separator)? would match // If optional is false, then the separators must also match PatternEntry& SetOptional(bool optional) { optional_ = optional; return *this; } // Checks to see if the target matches this entry bool Matches(const std::string& target) const override; const char* Name() const override { return name_.c_str(); } private: size_t MatchSeparatorAt(size_t start, Quantifier mode, const std::string& target, size_t tlen, const std::string& pattern) const; bool MatchesTarget(const std::string& name, size_t nlen, const std::string& target, size_t ylen) const; std::string name_; // The base name for this entry size_t nlength_; // The length of name_ std::vector names_; // Alternative names for this entry bool optional_; // Whether matching of separators is required size_t slength_; // The minimum required length to match the separators std::vector> separators_; // What to match }; // End class Entry private: // An Entry containing a FactoryFunc for creating new Objects template class FactoryEntry : public Entry { public: FactoryEntry(Entry* e, FactoryFunc f) : entry_(e), factory_(std::move(f)) {} bool Matches(const std::string& target) const override { return entry_->Matches(target); } const char* Name() const override { return entry_->Name(); } // Creates a new T object. T* NewFactoryObject(const std::string& target, std::unique_ptr* guard, std::string* msg) const { return factory_(target, guard, msg); } const FactoryFunc& GetFactory() const { return factory_; } private: std::unique_ptr entry_; // What to match for this entry FactoryFunc factory_; }; // End class FactoryEntry public: explicit ObjectLibrary(const std::string& id) { id_ = id; } const std::string& GetID() const { return id_; } // Finds the factory function for the input target. // @see PatternEntry for the matching rules to target // @return If matched, the FactoryFunc for this target, else nullptr template FactoryFunc FindFactory(const std::string& target) const { std::unique_lock lock(mu_); auto factories = factories_.find(T::Type()); if (factories != factories_.end()) { for (const auto& e : factories->second) { if (e->Matches(target)) { const auto* fe = static_cast*>(e.get()); return fe->GetFactory(); } } } return nullptr; } // Returns the total number of factories registered for this library. // This method returns the sum of all factories registered for all types. // @param num_types returns how many unique types are registered. size_t GetFactoryCount(size_t* num_types) const; // Returns the number of factories registered for this library // for the input type. // @param num_types returns how many unique types are registered. size_t GetFactoryCount(const std::string& type) const; // Returns the registered factory names for the input type // names is updated to include the names for the type void GetFactoryNames(const std::string& type, std::vector* names) const; void GetFactoryTypes(std::unordered_set* types) const; void Dump(Logger* logger) const; // Registers the factory with the library for the name. // If name==target, the factory may be used to create a new object. template const FactoryFunc& AddFactory(const std::string& name, const FactoryFunc& func) { std::unique_ptr entry( new FactoryEntry(new PatternEntry(name), func)); AddFactoryEntry(T::Type(), std::move(entry)); return func; } // Registers the factory with the library for the entry. // If the entry matches the target, the factory may be used to create a new // object. // @see PatternEntry for the matching rules. // NOTE: This function replaces the old ObjectLibrary::Register() template const FactoryFunc& AddFactory(const PatternEntry& entry, const FactoryFunc& func) { std::unique_ptr factory( new FactoryEntry(new PatternEntry(entry), func)); AddFactoryEntry(T::Type(), std::move(factory)); return func; } // Invokes the registrar function with the supplied arg for this library. int Register(const RegistrarFunc& registrar, const std::string& arg) { return registrar(*this, arg); } // Returns the default ObjectLibrary static std::shared_ptr& Default(); private: void AddFactoryEntry(const char* type, std::unique_ptr&& entry) { std::unique_lock lock(mu_); auto& factories = factories_[type]; factories.emplace_back(std::move(entry)); } // Protects the entry map mutable std::mutex mu_; // ** FactoryFunctions for this loader, organized by type std::unordered_map>> factories_; // The name for this library std::string id_; }; // The ObjectRegistry is used to register objects that can be created by a // name/pattern at run-time where the specific implementation of the object may // not be known in advance. class ObjectRegistry { public: static std::shared_ptr NewInstance(); static std::shared_ptr NewInstance( const std::shared_ptr& parent); static std::shared_ptr Default(); explicit ObjectRegistry(const std::shared_ptr& parent) : parent_(parent) {} explicit ObjectRegistry(const std::shared_ptr& library); std::shared_ptr AddLibrary(const std::string& id) { auto library = std::make_shared(id); AddLibrary(library); return library; } void AddLibrary(const std::shared_ptr& library) { std::unique_lock lock(library_mutex_); libraries_.push_back(library); } void AddLibrary(const std::string& id, const RegistrarFunc& registrar, const std::string& arg) { auto library = AddLibrary(id); library->Register(registrar, arg); } // Finds the factory for target and instantiates a new T. // Returns NotSupported if no factory is found // Returns InvalidArgument if a factory is found but the factory failed. template Status NewObject(const std::string& target, T** object, std::unique_ptr* guard) { assert(guard != nullptr); guard->reset(); auto factory = FindFactory(target); if (factory != nullptr) { std::string errmsg; *object = factory(target, guard, &errmsg); if (*object != nullptr) { return Status::OK(); } else if (errmsg.empty()) { return Status::InvalidArgument( std::string("Could not load ") + T::Type(), target); } else { return Status::InvalidArgument(errmsg, target); } } else { return Status::NotSupported(std::string("Could not load ") + T::Type(), target); } } // Creates a new unique T using the input factory functions. // Returns OK if a new unique T was successfully created // Returns NotSupported if the type/target could not be created // Returns InvalidArgument if the factory return an unguarded object // (meaning it cannot be managed by a unique ptr) template Status NewUniqueObject(const std::string& target, std::unique_ptr* result) { T* ptr = nullptr; std::unique_ptr guard; Status s = NewObject(target, &ptr, &guard); if (!s.ok()) { return s; } else if (guard) { result->reset(guard.release()); return Status::OK(); } else { return Status::InvalidArgument(std::string("Cannot make a unique ") + T::Type() + " from unguarded one ", target); } } // Creates a new shared T using the input factory functions. // Returns OK if a new shared T was successfully created // Returns NotSupported if the type/target could not be created // Returns InvalidArgument if the factory return an unguarded object // (meaning it cannot be managed by a shared ptr) template Status NewSharedObject(const std::string& target, std::shared_ptr* result) { std::unique_ptr guard; T* ptr = nullptr; Status s = NewObject(target, &ptr, &guard); if (!s.ok()) { return s; } else if (guard) { result->reset(guard.release()); return Status::OK(); } else { return Status::InvalidArgument(std::string("Cannot make a shared ") + T::Type() + " from unguarded one ", target); } } // Creates a new static T using the input factory functions. // Returns OK if a new static T was successfully created // Returns NotSupported if the type/target could not be created // Returns InvalidArgument if the factory return a guarded object // (meaning it is managed by a unique ptr) template Status NewStaticObject(const std::string& target, T** result) { std::unique_ptr guard; T* ptr = nullptr; Status s = NewObject(target, &ptr, &guard); if (!s.ok()) { return s; } else if (guard.get()) { return Status::InvalidArgument(std::string("Cannot make a static ") + T::Type() + " from a guarded one ", target); } else { *result = ptr; return Status::OK(); } } // Sets the object for the given id/type to be the input object // If the registry does not contain this id/type, the object is added and OK // is returned. If the registry contains a different object, an error is // returned. If the registry contains the input object, OK is returned. template Status SetManagedObject(const std::shared_ptr& object) { assert(object != nullptr); return SetManagedObject(object->GetId(), object); } template Status SetManagedObject(const std::string& id, const std::shared_ptr& object) { const auto c = std::static_pointer_cast(object); return SetManagedObject(T::Type(), id, c); } // Returns the object for the given id, if one exists. // If the object is not found in the registry, a nullptr is returned template std::shared_ptr GetManagedObject(const std::string& id) const { auto c = GetManagedObject(T::Type(), id); return std::static_pointer_cast(c); } // Returns the set of managed objects found in the registry matching // the input type and ID. // If the input id is not empty, then only objects of that class // (IsInstanceOf(id)) will be returned (for example, only return LRUCache // objects) If the input id is empty, then all objects of that type (all Cache // objects) template Status ListManagedObjects(const std::string& id, std::vector>* results) const { std::vector> customizables; results->clear(); Status s = ListManagedObjects(T::Type(), id, &customizables); if (s.ok()) { for (const auto& c : customizables) { results->push_back(std::static_pointer_cast(c)); } } return s; } template Status ListManagedObjects(std::vector>* results) const { return ListManagedObjects("", results); } // Creates a new ManagedObject in the registry for the id if one does not // currently exist. If an object with that ID already exists, the current // object is returned. // // The ID is the identifier of the object to be returned/created and returned // in result // If a new object is created (using the object factories), the cfunc // parameter will be invoked to configure the new object. template Status GetOrCreateManagedObject(const std::string& id, std::shared_ptr* result, const ConfigureFunc& cfunc = nullptr) { if (parent_ != nullptr) { auto object = parent_->GetManagedObject(T::Type(), id); if (object != nullptr) { *result = std::static_pointer_cast(object); return Status::OK(); } } { std::unique_lock lock(objects_mutex_); auto key = ToManagedObjectKey(T::Type(), id); auto iter = managed_objects_.find(key); if (iter != managed_objects_.end()) { auto object = iter->second.lock(); if (object != nullptr) { *result = std::static_pointer_cast(object); return Status::OK(); } } std::shared_ptr object; Status s = NewSharedObject(id, &object); if (s.ok() && cfunc != nullptr) { s = cfunc(object.get()); } if (s.ok()) { auto c = std::static_pointer_cast(object); if (id != c->Name()) { // If the ID is not the base name of the class, add the new // object under the input ID managed_objects_[key] = c; } if (id != c->GetId() && c->GetId() != c->Name()) { // If the input and current ID do not match, and the // current ID is not the base bame, add the new object under // its new ID key = ToManagedObjectKey(T::Type(), c->GetId()); managed_objects_[key] = c; } *result = object; } return s; } } // Returns the number of factories registered for this library // for the input type. // @param num_types returns how many unique types are registered. size_t GetFactoryCount(const std::string& type) const; // Returns the names of registered factories for the input type. // names is updated to include the names for the type void GetFactoryNames(const std::string& type, std::vector* names) const; void GetFactoryTypes(std::unordered_set* types) const; // Dump the contents of the registry to the logger void Dump(Logger* logger) const; // Invokes the input function to retrieve the properties for this plugin. int RegisterPlugin(const std::string& name, const RegistrarFunc& func); private: static std::string ToManagedObjectKey(const std::string& type, const std::string& id) { return type + "://" + id; } // Returns the Customizable managed object associated with the key (Type/ID). // If not found, nullptr is returned. std::shared_ptr GetManagedObject(const std::string& type, const std::string& id) const; Status ListManagedObjects( const std::string& type, const std::string& pattern, std::vector>* results) const; // Sets the managed object associated with the key (Type/ID) to c. // If the named managed object does not exist, the object is added and OK is // returned If the object exists and is the same as c, OK is returned // Otherwise, an error status is returned. Status SetManagedObject(const std::string& type, const std::string& id, const std::shared_ptr& c); // Searches (from back to front) the libraries looking for the // factory that matches this name. // Returns the factory if it is found, and nullptr otherwise template const FactoryFunc FindFactory(const std::string& name) const { { std::unique_lock lock(library_mutex_); for (auto iter = libraries_.crbegin(); iter != libraries_.crend(); ++iter) { const auto factory = iter->get()->FindFactory(name); if (factory != nullptr) { return factory; } } } if (parent_ == nullptr) { return nullptr; } else { return parent_->FindFactory(name); } } // The set of libraries to search for factories for this registry. // The libraries are searched in reverse order (back to front) when // searching for entries. std::vector> libraries_; std::vector plugins_; static std::unordered_map builtins_; std::map> managed_objects_; std::shared_ptr parent_; mutable std::mutex objects_mutex_; // Mutex for managed objects mutable std::mutex library_mutex_; // Mutex for managed libraries }; } // namespace ROCKSDB_NAMESPACE