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

Add generic interface for loading service providers from plugins #88082

Merged
merged 15 commits into from
Jun 30, 2022

Conversation

grcevski
Copy link
Contributor

Since plugins/modules are loaded in their own class loader, loading service providers (SPI implementations) from various service interfaces in plugins is not a simple call to ServiceLoader.load. This PR adds a helper method on PluginsService to load providers from plugins/modules given a service class.

Example usage of this helper can be found here (in the related PR #86224):

https://github.com/elastic/elasticsearch/pull/86224/files#diff-b56bed42f5b0025886eb243ecb48678ac048bfba0ef047545dbb6504e357edf2R901

Relates to #86224

@grcevski grcevski added >non-issue :Core/Infra/Core Core issues without another label Team:Core/Infra Meta label for core/infra team v8.4.0 labels Jun 27, 2022
@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-core-infra (Team:Core/Infra)

@@ -170,4 +170,7 @@ grant {

// system memory on Linux systems affected by JDK bug (#66629)
permission java.io.FilePermission "/proc/meminfo", "read";

// needed for PluginsServiceTests
permission java.lang.RuntimePermission "createClassLoader";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've made a mistake here, wrong policy file. I'll submit a new commit.

Copy link
Member

@rjernst rjernst left a comment

Choose a reason for hiding this comment

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

I have a few questions

* For example:
*
* var pluginHandlers = pluginsService.loadServiceProviders(OperatorHandlerProvider.class);
* </p>
Copy link
Member

Choose a reason for hiding this comment

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

nit: I think the closing p tag is normally omitted in javadoc (or must be?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought it was allowed, I see we use it in few places, but it's stripped now and I'll remove it from other PR.

* <p>
* For example:
*
* var pluginHandlers = pluginsService.loadServiceProviders(OperatorHandlerProvider.class);
Copy link
Member

Choose a reason for hiding this comment

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

This should be in a <pre> block?

*/
public <T> List<? extends T> loadServiceProviders(Class<T> service) {
List<T> result = new ArrayList<>();
getClass().getModule().addUses(service);
Copy link
Member

Choose a reason for hiding this comment

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

Why is this necessary? If a type is known in server, then it can be declared explicitly in server's module-info?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought of it as a convenience, but we can always explicitly declare the use in module-info. I'll remove it.


service = spy(service);

doReturn(List.of(provider1).iterator()).when(service).providersIterator(eq(OperatorHandlerProvider.class), eq(fakeClassLoader));
Copy link
Member

Choose a reason for hiding this comment

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

Since we are dealing with different classloaders, we should probably test more real-world situations. Can the test use real classloaders instead of mocking them? We have utilities for creating dummy jars with module infos, classes, etc. It seems doable to actually test the service loading.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I brought back the real classloader test version.

Copy link
Member

Choose a reason for hiding this comment

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

I still see mocking? To clarify what I was suggesting: dynamically build a dummy plugin jar and zip (with the utilities I mentioned) so the providersIterator method can actually be tested with the real ServiceLoader

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah I see, okay, I'll go further in building dummy plugins. This way I think I probably won't need to add extra permissions for the test framework.

@grcevski
Copy link
Contributor Author

@elasticmachine update branch

Copy link
Member

@rjernst rjernst left a comment

Choose a reason for hiding this comment

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

Sorry I had these comments stuck pending 🤦

* <pre>
* For example:
*
* var pluginHandlers = pluginsService.loadServiceProviders(OperatorHandlerProvider.class);
Copy link
Member

Choose a reason for hiding this comment

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

Nit: I think only the code should be in the <pre>?


service = spy(service);

doReturn(List.of(provider1).iterator()).when(service).providersIterator(eq(OperatorHandlerProvider.class), eq(fakeClassLoader));
Copy link
Member

Choose a reason for hiding this comment

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

I still see mocking? To clarify what I was suggesting: dynamically build a dummy plugin jar and zip (with the utilities I mentioned) so the providersIterator method can actually be tested with the real ServiceLoader

@grcevski grcevski requested a review from rjernst June 29, 2022 21:05
@grcevski
Copy link
Contributor Author

Thanks for the review @rjernst, I removed all mocking, real SPI loading now.

Copy link
Member

@rjernst rjernst left a comment

Choose a reason for hiding this comment

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

The new test looks much better! I'm glad the recently added utilities help make testing this easier. Just one last nit on the testing.

@@ -293,6 +294,33 @@ static void loadExtensions(Collection<LoadedPlugin> plugins) {
}
}

// package private for testing
<T> Iterator<T> providersIterator(Class<T> service, ClassLoader classLoader) {
Copy link
Member

Choose a reason for hiding this comment

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

I think this can be folded into loadServiceProviders now that the tests don't mock it?

import org.elasticsearch.plugins.spi.TestService;
import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.plugins.Plugin;
public final class FooPlugin extends Plugin implements ActionPlugin, TestService {
Copy link
Member

Choose a reason for hiding this comment

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

I would separate this out. Due to the current plugin design, a Plugin subclass must be present, but this class should not be required to be the one that implements all service providers. In fact, I think it is important we test them decoupled, since that is the future we are working towards.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure thing, I was just lazy :). I'll create a separate TestService provider class.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done now. I've split the provider class into separate file.

@grcevski grcevski requested a review from rjernst June 29, 2022 23:30
Copy link
Member

@rjernst rjernst left a comment

Choose a reason for hiding this comment

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

Thanks for the iterations. LGTM!

@grcevski grcevski merged commit bc9478d into elastic:master Jun 30, 2022
@grcevski grcevski deleted the operator/action_spi branch June 30, 2022 11:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
:Core/Infra/Core Core issues without another label >non-issue Team:Core/Infra Meta label for core/infra team v8.4.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants