Stable Plugin API: Add Ubermodule ClassLoader for non-modularized stable plugins#89365
Conversation
|
Pinging @elastic/es-core-infra (Team:Core/Infra) |
|
|
||
| this.internalLoader = new URLClassLoader(jarURLs); | ||
| // code source is always the first jar on the list | ||
| this.codeSource = new CodeSource(jarURLs[0], (CodeSigner[]) null); |
There was a problem hiding this comment.
we'll need to revisit this, but it is fine for the initial implementation.
| .stream() | ||
| .filter(Module::isNamed) | ||
| .filter(m -> "java.base".equals(m.getName()) == false) | ||
| .filter(m -> moduleDenyList.contains(m.getName()) == false) |
There was a problem hiding this comment.
deny-list is great, but at some point it might be more straightforward to have an allow-list, say to allow readability to all the JDK modules and a subset of the ES modules.
| return defineClass(name, bytes, 0, bytes.length, codeSource); | ||
| } catch (IOException e) { | ||
| // TODO | ||
| throw new IllegalStateException(e); |
There was a problem hiding this comment.
we should probably log here or something, right?
There was a problem hiding this comment.
I switched it to UncheckedIOException, since that's what we're throwing in EmbeddedImplClassLoader.
|
|
||
| /** | ||
| * This classloader does not restrict access to resources in its jars. Users should | ||
| * expect the same behavior as that provided by {@link URLClassLoader}. |
| // if cn's package is in this ubermodule, look here only (or just first?) | ||
| String packageName = packageName(cn); | ||
|
|
||
| // TODO: do we need to check the security manager here? |
There was a problem hiding this comment.
let's revisit this later, I wanna spend a bit more time thinking on it.
server/src/test/java/org/elasticsearch/plugins/UberModuleClassLoaderTests.java
Outdated
Show resolved
Hide resolved
server/src/main/java/org/elasticsearch/plugins/UberModuleClassLoader.java
Outdated
Show resolved
Hide resolved
|
@elasticsearchmachine please run elasticsearch-ci/part-1-windows |
| // code source is always the first jar on the list | ||
| this.codeSource = new CodeSource(jarURLs[0], (CodeSigner[]) null); | ||
| // we need a module layer to bind our module to this classloader | ||
| this.moduleController = ModuleLayer.defineModules(cf, List.of(mparent), s -> this); |
There was a problem hiding this comment.
I wonder if it would be helpful to have a more detailed comment how this binding works. I think it might not be obvious at first and I feel like this is the crucial bit?
There was a problem hiding this comment.
Sounds good. I'll add that comment before merging.
|
@elasticmachine update branch |
This PR's UberModuleClassLoader takes a group of jars and loads them as a single synthetic module.
The motivation for this work is the Stable Plugin API project, but the classloader is general enough for other use cases. In the case of stable plugins, we don't want to require that plugin developers modularize their plugins. (If a plugin developer chooses to modularize their plugin, then we can load it using JDK-provided utilities.)
The advantage of a synthetic module is that we can programmatically use module layers to control what the code from the synthetic module has access to. A non-modularized plugin will be provided in the form of one or more jars. When this classloader loads a class from one of those jars, that class will be bound to the synthetic module. We can use a deny-list to prevent the class from accessing packages from particular modules.
In the world of modules, each module owns the packages it contains, and it is forbidden to split packages across module boundaries. This means that when we load a class, we can check its package name. If our synthetic module owns the package, then we don't need to search for the class in parent classloaders. When fetching jar resources, however, we simply delegate to the internal URLClassLoader.