Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugin Registry #7949

Closed
wants to merge 30 commits into from
Closed

Plugin Registry #7949

wants to merge 30 commits into from

Conversation

mrambacher
Copy link
Contributor

Added a Plugin class to the ObjectRegistry. Enabled compile-time and program-time addition of plugins to the Registry.

@ajkr
Copy link
Contributor

ajkr commented Feb 10, 2021

For me to review, could there be a PR at the same scope as PR 7918 (just the mechanism for building-in external code)? I plan to not get involved with plugins beyond that scope.

@ajkr ajkr removed their request for review February 10, 2021 00:14
@yhr
Copy link
Contributor

yhr commented Feb 11, 2021

The code generation and plugin->object registry handling looks sane (and would let us drop -whole-archive / -u linking directives which is nice), but do we need versioning checks for builtins? the plugin would be built with the same version of rocksdb, right?

@mrambacher mrambacher force-pushed the PluginRegistry branch 2 times, most recently from 8a0276a to f726f47 Compare June 9, 2021 19:46
@mrambacher mrambacher marked this pull request as ready for review June 9, 2021 20:01
@mrambacher
Copy link
Contributor Author

The code generation and plugin->object registry handling looks sane (and would let us drop -whole-archive / -u linking directives which is nice), but do we need versioning checks for builtins? the plugin would be built with the same version of rocksdb, right?

The "version" check stuff does not necessarily need to be there, until we start using different compilation units (static or shared libraries) to grab the plugin properties. This field is initialized in the struct "for free" and the caller does not need to get/set it.

- Added a "PluginProperties" struct that defines the characteristics of a plugin (version, name, registration).
- Added ObjectRegistry::RegisterPlugin.  These methods validate a plugin and register it with the ObjectRegistry.
- Added builtin plugins to the ObjectRegistry.  These are discovered at compile time by looking in the plugin makefile.  The builtin plugins are automatically registered with the ObjectRegistry at startup.  The builtins are added to the "build_version.cc" when the file is generated.
- Added rudimentary support for plugins via cmake.
util/build_version.cc.in Outdated Show resolved Hide resolved
include/rocksdb/version.h Outdated Show resolved Hide resolved
include/rocksdb/utilities/object_registry.h Outdated Show resolved Hide resolved
struct Plugin {
// The version of this structure. If the structure is changed, the version
// should be incremented
static constexpr int kVersion() { return 1; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unused. What is it doing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally, I had a few versions in this struct:

  • The RocksDBVersion of the system/tool (hence the earlier changes to RocksDBVersion). RocksDB would pass this value into the Plugin so it could determine that it supported the RocksDB version. If the PluginFunc did not like the RocksDB version, it would return an error
  • The RocksDB version of the Plugin. This would be filled in by the plugin to say what version it was compiled against. RocksDB would then check that the version was "okay" with it and not add the plugin if it was not
  • The version of the Plugin struct. This version was to be used by the plugin to state what fields it was filling in and aware of. The the Plugin struct changed, the version of the struct would need to change so that the plugin would know.

Copy link
Contributor

@pdillinger pdillinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code still seems very haphazard and arbitrary. The tests don't give me many clues about what practical use will look like.

@@ -38,6 +39,23 @@ using FactoryFunc =
// library
using RegistrarFunc = std::function<int(ObjectLibrary&, const std::string&)>;

// The signature of the function for loading plugins
// Plugin contains the struct of properties associated with this Plugin.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment still doesn't make sense to me. Is arg a property associated with the Plugin?

// by the Plugin code.
// On success, this function should return 0. On failure, this function should
// return non-zero and set the std::string* to an appropriate error message.
using PluginFunc = std::function<int(Plugin*, std::string*)>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And can you tell me what problem is solved by the existence of PluginFunc? You can obviously call RegisterPlugin without it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PluginFunc is called to fill in the Plugin* with the appropriate values (at the moment this is only the registration function and its arg).

In the FB-controlled world, this method has one purpose and that is to initialize any plugins defined at compile time during startup. In the rest of the world, I could envision this function being used to register plugins that the user could load dynamically.

include/rocksdb/version.h Outdated Show resolved Hide resolved
Plugin plugin;
std::string errmsg;
int code = plugin_func(&plugin, &errmsg);
if (code != 0) { // TODO: Perhaps use different codes?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like I said before, why not use Status? I don't like creating new public APIs with known "TODO" items calling for breaking changes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we were ever to support dynamic plugins, Status could break ABI.

Additionally, some of the other functions in the ObjectRegistry (like NewFactoryObject) follow the same model (of returning something and an error string.

It would be possible to use the Status::Code enums as return codes (hence the TODO) to have this method return different Status for different cases (instead of always returning InvalidArgument).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we were ever to support dynamic plugins, Status could break ABI.

What use is a RocksDB plugin that is ABI-incompatible with the compiled RocksDB?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A Plugin that is not ABI-compatible is not good, which is why there used to be code in the Plugin that tried to detect that condition and prevented it from loading. If a Plugin was not ABI-compatible and returned a Status here that was not ABI-compatible, it could cause the program to crash and would not be easy to debug or decipher the problem.

// The signature of the function for loading plugins
// Plugin contains the struct of properties associated with this Plugin.
// Plugin is passed to the PluginFunc and the fields should be initalized
// by the Plugin code.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plugin doesn't have code, only data. Perhaps "plugin code."

// return non-zero and set the std::string* to an appropriate error message.
using PluginFunc = std::function<int(Plugin*, std::string*)>;

struct Plugin {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What problem does this solve that is not solved by std::function<int(ObjectLibrary&)>? (Noting that passing non-const ref violates Google C++ guidelines)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few reasons for the Plugin (as opposed to a simple RegistrarFunc):

  1. If we ever intend to do error checking or provide more information about a plugin, we need some place to hold it. The Plugin struct was intended to be that place
  2. The Registrar Func takes an argument. I am not sure where I am supposed to get that from (or just pass in "")
  3. Without this struct, how could one determine what was loaded or available to the application? Using the Plugins and ObjectRegistry, we could add some useful information to tools to say what they are compiled with (e.g. this version of db_bench supports HDFS env). Without the Plugin struct/name, We would not have access to this information.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this does not violate the Google C++ guidelines: "while non-optional output and input/output parameters should usually be references (which cannot be null)"

@@ -185,5 +207,29 @@ void ObjectRegistry::Dump(Logger *logger) const {
}
}

Status ObjectRegistry::RegisterPlugin(const std::string &name,
const PluginFunc &plugin_func) {
Plugin plugin;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have a public type called "Plugin" then the plugin itself should control creation of the "Plugin" object, right? That's not what we have here. Best I can tell, here the object you are calling "Plugin" is a holder for a glorified callback for the ObjectRegistry. And we have a callback for populating the holder of the glorified callback. Why so many layers?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment, the Plugin contains the name of the plugin, its registrar function, and an argument field. As I mentioned earlier, the struct previously contained more fields.

I could also envision the PluginFunc doing additional work. For example, a plugin that supports OpenSSL encryption may load the OpenSSL library dynamically and make sure the functions are available. If they are not, it might return an appropriate error message and not initialize registration functions. Or it might still register functions that return an error if the factory method is called.

Using just the ObjectRegistry call, there would be no way to the caller that Open SSL is not supported, except via the Factory method itself.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the Plugin contains the name of the plugin

Not currently

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, it did and then I removed it as a duplication (there is also a name to the other methods).

Simpler implementation using RegisterPlugin(name, RegistrarFunc)
The Make processes were updated to support the new signature for RegisterPlugin.  Both also support old-style syntax where <plugin>_FUNC is not defined
@facebook-github-bot
Copy link
Contributor

@mrambacher has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@facebook-github-bot
Copy link
Contributor

@mrambacher has updated the pull request. You must reimport the pull request before landing.

Copy link
Contributor

@pdillinger pdillinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is appropriately simple and effective. AFAIK, if plugins want to check rocksdb version at run time, they can do that with GetRocksVersionAsString (though that doesn't provide for easy comparison; maybe we should provide a variant of the version string for which lexicographic ordering agrees with version ordering).

list(APPEND PLUGINS ${ROCKSDB_PLUGINS})
foreach(PLUGIN IN LISTS PLUGINS)
set(PLUGIN_ROOT "${CMAKE_SOURCE_DIR}/plugin/${PLUGIN}/")
message("including rocksb plugin ${PLUGIN_ROOT}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor typo "rocksb"

@@ -546,6 +548,8 @@ class ObjectRegistry {
// The libraries are searched in reverse order (back to front) when
// searching for entries.
std::vector<std::shared_ptr<ObjectLibrary>> libraries_;
std::vector<std::string> plugins_;
static std::unordered_map<std::string, RegistrarFunc> builtins_;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer if the name of this field had a hint that it is static, such as static_builtins_.

@pdillinger
Copy link
Contributor

Be sure to fix CI errors

@facebook-github-bot
Copy link
Contributor

@mrambacher has updated the pull request. You must reimport the pull request before landing.

@facebook-github-bot
Copy link
Contributor

@mrambacher has updated the pull request. You must reimport the pull request before landing.

@facebook-github-bot
Copy link
Contributor

@mrambacher has updated the pull request. You must reimport the pull request before landing.

@facebook-github-bot
Copy link
Contributor

@mrambacher has updated the pull request. You must reimport the pull request before landing.

@facebook-github-bot
Copy link
Contributor

@mrambacher has updated the pull request. You must reimport the pull request before landing.

@facebook-github-bot
Copy link
Contributor

@mrambacher has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants