-
Notifications
You must be signed in to change notification settings - Fork 6.3k
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
Plugin Registry #7949
Conversation
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. |
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? |
8a0276a
to
f726f47
Compare
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. |
f27082d
to
10439af
Compare
- 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.
10439af
to
018606f
Compare
struct Plugin { | ||
// The version of this structure. If the structure is changed, the version | ||
// should be incremented | ||
static constexpr int kVersion() { return 1; } |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this 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. |
There was a problem hiding this comment.
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*)>; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
utilities/object_registry.cc
Outdated
Plugin plugin; | ||
std::string errmsg; | ||
int code = plugin_func(&plugin, &errmsg); | ||
if (code != 0) { // TODO: Perhaps use different codes? |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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):
- 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
- The Registrar Func takes an argument. I am not sure where I am supposed to get that from (or just pass in "")
- 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.
There was a problem hiding this comment.
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)"
utilities/object_registry.cc
Outdated
@@ -185,5 +207,29 @@ void ObjectRegistry::Dump(Logger *logger) const { | |||
} | |||
} | |||
|
|||
Status ObjectRegistry::RegisterPlugin(const std::string &name, | |||
const PluginFunc &plugin_func) { | |||
Plugin plugin; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
@mrambacher has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
@mrambacher has updated the pull request. You must reimport the pull request before landing. |
There was a problem hiding this 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}") |
There was a problem hiding this comment.
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_; |
There was a problem hiding this comment.
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_
.
Be sure to fix CI errors |
@mrambacher has updated the pull request. You must reimport the pull request before landing. |
@mrambacher has updated the pull request. You must reimport the pull request before landing. |
@mrambacher has updated the pull request. You must reimport the pull request before landing. |
@mrambacher has updated the pull request. You must reimport the pull request before landing. |
@mrambacher has updated the pull request. You must reimport the pull request before landing. |
@mrambacher has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
Added a Plugin class to the ObjectRegistry. Enabled compile-time and program-time addition of plugins to the Registry.