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

First POC for external extension loading #2881

Merged
merged 25 commits into from
May 18, 2021
Merged

Conversation

iNikem
Copy link
Contributor

@iNikem iNikem commented Apr 28, 2021

Alternative approach to #2774

private static byte[] remapClassBytes(InputStream in) throws IOException {
ClassWriter cw = new ClassWriter(0);
ClassReader cr = new ClassReader(in);
cr.accept(new ClassRemapper(cw, remapper), ClassReader.EXPAND_FRAMES);
Copy link
Contributor

Choose a reason for hiding this comment

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

out of curiosity is EXPAND_FRAMES really needed for remapping?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No idea, this was copy-paste from ExporterClassLoader

@iNikem iNikem marked this pull request as draft May 6, 2021 11:04
@iNikem iNikem marked this pull request as ready for review May 6, 2021 12:55
@iNikem
Copy link
Contributor Author

iNikem commented May 6, 2021

I think this PR is now ready for review and then merge. It is still an open question, should we go with URLHandler or move everything into classloader itself. But in anyway I think we should have one way to do that, both in this new classloader and in AgentClassloder. Thus discussion in #2912 is very relevant here as well (/cc @laurit )

@iNikem iNikem requested a review from laurit May 6, 2021 12:57
Constructor constructor =
loaderClass.getDeclaredConstructor(URL.class, String.class, ClassLoader.class);
return (ClassLoader) constructor.newInstance(bootstrapUrl, innerJarFilename, agentParent);
Constructor<ClassLoader> constructor =
Copy link
Contributor

Choose a reason for hiding this comment

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

is suspect that reflection isn't really necessary here, could probably just use new AgentClassLoader

ClassLoader agentClassLoader =
constructor.newInstance(bootstrapUrl, innerJarFilename, agentParent);

Class<?> extensionClassLoaderClass =
Copy link
Contributor

Choose a reason for hiding this comment

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

reflection here could be avoided by either doing this inside AgentInstaller or moving ExtensionClassLoader out of inst/

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 will touch this code again when doing support for multiple extension jars.

URL extension =
parseLocation(
System.getProperty(
"otel.javaagent.experimental.extensions",
Copy link
Contributor

Choose a reason for hiding this comment

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

System property is named extensions but seems like it only accepts 1 file. Should it be possible to only add 1 extension or multiple?
If multiple do they all live in the same class loader or would each get its own?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As class comment says, we want to support scanning the folder for several jars. In that case every jar should get a separate classloader, I think. This is a goal for a subsequent PR.

}

public static ClassLoader getInstance(ClassLoader parent) {
// TODO add support for old deprecated property otel.javaagent.experimental.exporter.jar
Copy link
Contributor

Choose a reason for hiding this comment

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

Dunno if it's worth it vs just keeping ExporterClassLoader for a little bit

extension =
parseLocation(
System.getProperty(
"otel.javaagent.experimental.initializer.jar",
Copy link
Contributor

Choose a reason for hiding this comment

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

Similarly, I'd probably just leave our current code until we get rid of the property instead of complicating this class with backwards compatibility.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When I add support for several jars, this will not be a big complication...

private final JarFile delegateJarFile;
private final JarEntry entry;

private InputStream cachedResult;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is only one URLConnection ever instantiated? Not sure when openConnection is called during classload

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Certainly not one, why do you ask?

Copy link
Contributor

Choose a reason for hiding this comment

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

If not once, I guess this cache is never used? Should it be a static cache?

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, that's the catch :) it IS read/called at least 2 times by URLClassoader. Old AgentClassLoader also had an optimisation for this double reading.

Copy link
Contributor

Choose a reason for hiding this comment

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

Caching Inputstream this way is weird. As streams can be read only once without using mark/reset then this caching would make sense only when someone opens the stream and doesn't use it at all and then reopens the stream and actually reads it. Perhaps this isn't really needed any more? If it is needed then caching byte array and returning a new ByteArrayInputStream from getInputStream would be nicer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

handler.getInputStream() is called twice:

  1. In jdk.internal.loader.URLClassPath.Loader#getResource(java.lang.String, boolean), where exactly "someone opens the stream and doesn't use it at all" happens
  2. in jdk.internal.loader.Resource#cachedInputStream, when stream is actually used afterwards.

But I agree with you that caching byte arrays makes more sense, will do it.

examples/extension/custom/build.gradle Outdated Show resolved Hide resolved
Copy link
Contributor

@anuraaga anuraaga left a comment

Choose a reason for hiding this comment

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

A bit uncertain of the caching but basically looks good

Copy link
Member

@trask trask left a comment

Choose a reason for hiding this comment

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

ExtensionClassLoader's URLStreamHandler/URLConnection approach has diverged from the new AgentClassLoader behavior implemented by @laurit in #2912, it's not clear if these differences are needed, or it's just drift since you started this work prior to his changes?

examples/extension/build.gradle Show resolved Hide resolved
examples/extension/settings.gradle Show resolved Hide resolved
return null;
}

// That will NOT remap the content of files under META-INF/services
Copy link
Member

Choose a reason for hiding this comment

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

is this a problem?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not for now

@iNikem
Copy link
Contributor Author

iNikem commented May 17, 2021

ExtensionClassLoader's URLStreamHandler/URLConnection approach has diverged from the new AgentClassLoader behavior implemented by @laurit in #2912, it's not clear if these differences are needed, or it's just drift since you started this work prior to his changes?

We discussed this with Lauri and agreed that it is Ok for now to have different ways to do things. I will review it later.

@trask
Copy link
Member

trask commented May 17, 2021

I will review it later.

👍

static void setupSpec() {
backend =
new GenericContainer<>(
"open-telemetry-docker-dev.bintray.io/java/smoke-fake-backend:latest")
Copy link
Contributor

Choose a reason for hiding this comment

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

Smoketests use ghcr.io/open-telemetry/java-test-containers:smoke-fake-backend-20210324.684269693

@iNikem iNikem merged commit e3cf8ec into open-telemetry:main May 18, 2021
@iNikem iNikem deleted the urlhandler branch May 18, 2021 19:59
@trask
Copy link
Member

trask commented May 19, 2021

🥳

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

Successfully merging this pull request may close these issues.

5 participants