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

KAFKA-18182: KIP-891 Connect Multiversion Support (Base PR with Plugin Loading Isolation Changes) #16984

Merged
merged 32 commits into from
Dec 7, 2024

Conversation

snehashisp
Copy link
Contributor

@snehashisp snehashisp commented Aug 23, 2024

The is one of a set of PRs for KIP-891. The list of total PRs given below all build one the previous one in the list. They can be reviewed individually, or if the complete set of changes is preferrable, please refer to the last PR.

This is PR#1 and contains changes to connects plugin loading package. It contains the set of changes to load versioned plugins based on maven versioning as described in the KIP and exposes a set of public methods for use in the other components of this KIP.

  1. KAFKA-18182: KIP-891 Connect Multiversion Support (Base PR with Plugin Loading Isolation Changes) #16984 - Plugins Loading Isolation Changes
  2. KAFKA-18215: KIP-891 Connect Multiversioning Support (Configs and Validation changes for Connectors and Converters) #17741 - Versioned Configs and Validation for Connector and Converters
  3. [WIP] KIP-891: Connect Multiversion Support (Transformation and Predicate Changes) #17742 - Transformation and Predicate Support
  4. [WIP] KIP-891: Connect Multiversion Support (Versioned Connector Creation and related changes) #17743 - Versioned Connector Creation
  5. [WIP] KIP-891: Connect Multiversion Support (Updates to status and metrics) #17988 - Updates to status and metrics

Committer Checklist (excluded from commit message)

  • Verify design and implementation
  • Verify test coverage and CI build status
  • Verify documentation (including upgrade notes)

@github-actions github-actions bot added the build Gradle build or GitHub Actions label Oct 28, 2024
@snehashisp snehashisp changed the title [WIP] Connect Multiversion Support (Plugin Loading Isolation Changes) (KIP-891) [WIP] KIP-891: Connect Multiversion Support (Base PR with Plugin Loading Isolation Changes) Nov 10, 2024
@snehashisp snehashisp marked this pull request as ready for review November 10, 2024 16:36
Copy link
Contributor

@gharris1727 gharris1727 left a comment

Choose a reason for hiding this comment

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

Thanks so much for the PR @snehashisp.

I apologize for the lateness of my review. I'm planning on reviewing this daily until the feature freeze on the 11th.

Comment on lines 212 to 224
plugin = super.loadClass(fullName, resolve);
// if we are loading a plugin class from the parent classloader, we need to check if the version
// matches the range
String pluginVersion;
try (LoaderSwap classLoader = PluginScanner.withClassLoader(plugin.getClassLoader())) {
pluginVersion = PluginScanner.versionFor(plugin.getDeclaredConstructor().newInstance());
} catch (ReflectiveOperationException | LinkageError e) {
throw new VersionedPluginLoadingException(String.format(
"Plugin %s was loaded with %s but failed to determine its version",
name,
plugin.getClassLoader()
), e);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This information should be computed ahead-of-time by the PluginScanner, not re-computed at runtime each time the class is requested. The parent loader of the DelegatingClassLoader will always be considered "classpath" in PluginDesc<> objects, which also include the version.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this part not really required? IIUC the plugins present in classpath are also scanned and part of the pluginLoaders map, and it should be found with the pluginClassLoader logic. super.loadClass should always throw a ClassNotFoundException. Otherwise, if there are edge cases where super.loadClass does find the class (maybe the plugin is part of the Bootstrap loader) then instantiation the class here is the only way to confirm version requirements are addressed.

Copy link
Contributor

Choose a reason for hiding this comment

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

IIUC the plugins present in classpath are also scanned and part of the pluginLoaders map, and it should be found with the pluginClassLoader logic.

pluginClassLoader() returns PluginClassLoader, and has an instanceof check to filter out the classpath plugins.

super.loadClass should always throw a ClassNotFoundException.

I don't know what you mean. If there's a copy of the requested plugin on the classpath, super.loadClass will find it, and it will already have been scanned and put in pluginLoaders with the location being "classpath"

Copy link
Contributor Author

@snehashisp snehashisp Dec 5, 2024

Choose a reason for hiding this comment

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

Ahh, I missed the instance of check in the pluginClassLoader. Yes in that case super.loadClass will load the plugin from classpath and we can avoid instantiation to do the version check. Will make the requested changes for this and the following comment.

String fullName = aliases.getOrDefault(connectorClassOrAlias, connectorClassOrAlias);
// if the plugin is not loaded via the plugin classloader, it might still be available in the parent delegating
// classloader, in order to check if the version satisfies the requirement we need to load the plugin class here
ClassLoader classLoader = loadVersionedPluginClass(fullName, range, false).getClassLoader();
Copy link
Contributor

Choose a reason for hiding this comment

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

There's a slight difference here: If the plugin is non-isolated, DelegatingClassLoader#connectorLoader(String) returns this (the delegating loader), but DelegatingClassLoader#connectorLoader(String, VersionRange) returns parent (the classpath).

For a similar reason, connectorLoader(String) doesn't throw ClassNotFoundException, it returns this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Similar query here (w.r.t my previous comment). Since the class path plugins are scanned and added to pluginLoaders dict, both should return the classpath loader. Its only if for some reason, a plugin is found that was not scanned we should get a different in the two methods.

I also saw the other comment in the other PR. I have actually replaced the use of connector loader with fetching the connector and using getClass().getClassLoader() as that avoids two calls. Now in both cases it should return classpath loader for non-isolated plugins based on my understanding. This would mean that instantiation plugins with these classloaders will bypass the delegating loader, which does not look correct, but that seems to be the current process. LMK if I am missing something here. I can check if the class loader here is an instance of PluginClassLoader and return the delegating classloader here if I am incorrect in my assumption.

Copy link
Contributor

Choose a reason for hiding this comment

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

but that seems to be the current process.

It's the process in your PR, but was not the process before. I'm saying you need to change this to return the DelegatingClassLoader.

Comment on lines 599 to 605
if (range != null && range.hasRestrictions() && !range.containsVersion(pluginVersion)) {
// this can happen if the current class loader is used
// if there are version restrictions then this should be captured, and we should load using the plugin class loader
if (classLoaderUsage == ClassLoaderUsage.CURRENT_CLASSLOADER) {
return getVersionedPlugin(config, classPropertyName, versionPropertyName, basePluginClass, ClassLoaderUsage.PLUGINS, availablePlugins);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we assert range == null || classLoaderUsage == PLUGINS earlier, to ensure that we never get into this situation where we loaded a class of an invalid version?

I think having this depend on range.containsVersion(pluginVersion) could lead to some strange behavior in the following situation:

  • key.converter.version = [1.0, 2.0)
  • Converter v1.0 is on the classpath
  • Converter v1.2 is in a different plugin
  • Converter v1.1 is in the local plugin

If you don't have 1.1 installed, 1.2 is selected. If you later install 1.1 (an older version), you get rolled back to 1.1.
But if you had specified [1.2] it wouldn't get rolled back.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, the situation you mentioned is exactly the reason why the public methods to create a converter with a version provided we don't have the option to pass in a class loader and defaults to using the plugins loader, otherwise it can potentially shadow a higher version of a plugin requirement. This code path should ideally never be executed. Will remove this and add the assertion suggestion.

@snehashisp
Copy link
Contributor Author

snehashisp commented Dec 4, 2024

Thanks for the reviews @gharris1727. Will take a look soon and try and give my full attention till 11th. What does the feature freeze encompass, does it include all unit testing and ITs or can it come later as part of stabilization? I believe we can make it if it does not include the latter, otherwise we will need to target the following release.

Edit: I just realized that I have other commitments next week as well. I'll try my best, but 11th seems a bit ambitious given the scope of work here.

@gharris1727
Copy link
Contributor

What does the feature freeze encompass, does it include all unit testing and ITs or can it come later as part of stabilization?

Earlier is always better, but we can prioritize the changes (with manual testing) before feature freeze, and unit/integration tests after feature freeze.

I'll try my best, but 11th seems a bit ambitious given the scope of work here.

I'll do my best to support you, please let me know if you see anything that I can help with, or if we need to involve someone else.

@github-actions github-actions bot added KIP-932 Queues for Kafka docker Official Docker image generator RPC and Record code generator transactions Transactions and EOS clients labels Dec 7, 2024
@snehashisp
Copy link
Contributor Author

Please work on getting this to compile and tests to pass.

A couple of minor adjustments were needed, otherwise the runtime tests have all passed locally. Hoping that it will go through on the CI 🤞. I accidentally pushed in some changes to rest of the files while merging latest trunk (git merge and push from native windoes seems to add a carriage return to the files 😢). If possible can you please remove the additional tags.

@gharris1727 gharris1727 removed streams core Kafka Broker producer consumer tools performance kraft mirror-maker-2 dependencies Pull requests that update a dependency file storage Pull requests that target the storage module tiered-storage Related to the Tiered Storage feature KIP-932 Queues for Kafka build Gradle build or GitHub Actions docker Official Docker image generator RPC and Record code generator transactions Transactions and EOS clients labels Dec 7, 2024
Copy link
Contributor

@gharris1727 gharris1727 left a comment

Choose a reason for hiding this comment

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

Thanks so much @snehashisp !

@gharris1727 gharris1727 changed the title [WIP] KIP-891: Connect Multiversion Support (Base PR with Plugin Loading Isolation Changes) KAFKA-18182: KIP-891 Connect Multiversion Support (Base PR with Plugin Loading Isolation Changes) Dec 7, 2024
@gharris1727 gharris1727 merged commit af0054b into apache:trunk Dec 7, 2024
8 checks passed
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.

2 participants