You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sirona.apache.org by rm...@apache.org on 2015/11/13 18:09:41 UTC
svn commit: r1714239 - in /incubator/sirona/trunk:
agent/javaagent/src/main/java/org/apache/sirona/javaagent/
agent/javaagent/src/main/java/org/apache/sirona/javaagent/classloader/
agent/javaagent/src/test/java/org/apache/sirona/javaagent/ agent/javaag...
Author: rmannibucau
Date: Fri Nov 13 17:09:41 2015
New Revision: 1714239
URL: http://svn.apache.org/viewvc?rev=1714239&view=rev
Log:
SIRONA-55 using a TempClassLoader for getCommonSuperType
Added:
incubator/sirona/trunk/agent/javaagent/src/main/java/org/apache/sirona/javaagent/classloader/
incubator/sirona/trunk/agent/javaagent/src/main/java/org/apache/sirona/javaagent/classloader/LoadFirstClassLoader.java
Modified:
incubator/sirona/trunk/agent/javaagent/src/main/java/org/apache/sirona/javaagent/SironaAgent.java
incubator/sirona/trunk/agent/javaagent/src/main/java/org/apache/sirona/javaagent/SironaTransformer.java
incubator/sirona/trunk/agent/javaagent/src/test/java/org/apache/sirona/javaagent/InJvmTransformerRunner.java
incubator/sirona/trunk/agent/javaagent/src/test/java/org/apache/test/sirona/javaagent/EnsureInstrumationDoesntFailTest.java
incubator/sirona/trunk/api/src/main/java/org/apache/sirona/util/ClassLoaders.java
Modified: incubator/sirona/trunk/agent/javaagent/src/main/java/org/apache/sirona/javaagent/SironaAgent.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/agent/javaagent/src/main/java/org/apache/sirona/javaagent/SironaAgent.java?rev=1714239&r1=1714238&r2=1714239&view=diff
==============================================================================
--- incubator/sirona/trunk/agent/javaagent/src/main/java/org/apache/sirona/javaagent/SironaAgent.java (original)
+++ incubator/sirona/trunk/agent/javaagent/src/main/java/org/apache/sirona/javaagent/SironaAgent.java Fri Nov 13 17:09:41 2015
@@ -27,6 +27,7 @@ import java.net.URLClassLoader;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
+import java.util.Timer;
import java.util.jar.JarFile;
import static java.util.Arrays.asList;
@@ -71,6 +72,8 @@ public class SironaAgent {
}
final boolean debug = "true".equalsIgnoreCase(extractConfig(agentArgs, "debug="));
+ final boolean skipTempLoader = "true".equalsIgnoreCase(extractConfig(agentArgs, "skipTempLoader="));
+ final boolean autoEvictClassLoaders = "true".equalsIgnoreCase(extractConfig(agentArgs, "autoEvictClassLoaders="));
final String tempClassLoaders = extractConfig(agentArgs, "tempClassLoaders=");
final boolean envrtDebug = debug || "true".equalsIgnoreCase(extractConfig(agentArgs, "environment-debug="));
@@ -123,7 +126,24 @@ public class SironaAgent {
System.out.println("Sirona debugging activated, find instrumented classes in /tmp/sirona-dump/");
}
- final SironaTransformer transformer = new SironaTransformer(debug, tempClassLoaders);
+ final SironaTransformer transformer = new SironaTransformer(debug, skipTempLoader, tempClassLoaders);
+ if (autoEvictClassLoaders) {
+ final String evictTimeoutStr = extractConfig(agentArgs, "classLoaderEvictionTimeout=");
+ final long timeout = evictTimeoutStr != null && !evictTimeoutStr.isEmpty() ? Long.parseLong(evictTimeoutStr) : 60000;
+ final Thread evictThread = new Thread(new Runnable() {
+ public void run() {
+ try {
+ Thread.sleep(timeout);
+ } catch (final InterruptedException e) {
+ Thread.interrupted();
+ return;
+ }
+ transformer.evictClassLoaders();
+ }
+ });
+ evictThread.setName("sirona-classloader-cleanup");
+ evictThread.setDaemon(true);
+ }
final boolean reloadable = instrumentation.isRetransformClassesSupported() && FORCE_RELOAD;
instrumentation.addTransformer(transformer, reloadable);
Modified: incubator/sirona/trunk/agent/javaagent/src/main/java/org/apache/sirona/javaagent/SironaTransformer.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/agent/javaagent/src/main/java/org/apache/sirona/javaagent/SironaTransformer.java?rev=1714239&r1=1714238&r2=1714239&view=diff
==============================================================================
--- incubator/sirona/trunk/agent/javaagent/src/main/java/org/apache/sirona/javaagent/SironaTransformer.java (original)
+++ incubator/sirona/trunk/agent/javaagent/src/main/java/org/apache/sirona/javaagent/SironaTransformer.java Fri Nov 13 17:09:41 2015
@@ -16,7 +16,9 @@
*/
package org.apache.sirona.javaagent;
+import org.apache.sirona.javaagent.classloader.LoadFirstClassLoader;
import org.apache.sirona.javaagent.logging.SironaAgentLogging;
+import org.apache.sirona.util.ClassLoaders;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
@@ -25,15 +27,22 @@ import java.io.FileOutputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
public class SironaTransformer implements ClassFileTransformer {
private static final String DELEGATING_CLASS_LOADER = "sun.reflect.DelegatingClassLoader";
+ // used to not load classes while loading and create linkage errors
+ private final ConcurrentMap<ClassLoader, ClassLoader> tempClassLoaders = new ConcurrentHashMap<ClassLoader, ClassLoader>();
+
private final boolean debug;
private final String[] autoClassLoaderExcludes;
+ private final boolean skipTempLoader;
- public SironaTransformer(final boolean debug, final String tempClassLoaders) {
+ public SironaTransformer(final boolean debug, final boolean skipTempLoader, final String tempClassLoaders) {
this.debug = debug || Boolean.getBoolean("sirona.javaagent.debug");
+ this.skipTempLoader = skipTempLoader || Boolean.getBoolean("sirona.javaagent.skipTempLoader");
final String excludes = System.getProperty(
"sirona.javaagent.dontAutoClassLoaderExclude",
@@ -43,6 +52,10 @@ public class SironaTransformer implement
this.autoClassLoaderExcludes = excludes.split(" *, *");
}
+ public void evictClassLoaders() { // we will recreate them if needed
+ tempClassLoaders.clear();
+ }
+
@Override
public byte[] transform(final ClassLoader loader, final String className, final Class<?> classBeingRedefined,
final ProtectionDomain protectionDomain, final byte[] classfileBuffer) throws IllegalClassFormatException {
@@ -63,13 +76,13 @@ public class SironaTransformer implement
return true;
}
}
- return false;
+ return LoadFirstClassLoader.class.getName().equals(name); // of course we exclude our internal loader
}
protected byte[] doTransform(final String className, final byte[] classfileBuffer) {
try {
final ClassReader reader = new ClassReader(classfileBuffer);
- final ClassWriter writer = new SironaClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
+ final ClassWriter writer = new SironaClassWriter(skipTempLoader ? null : tempClassLoaders, reader, ClassWriter.COMPUTE_FRAMES);
final SironaClassVisitor advisor = new SironaClassVisitor(writer, className, classfileBuffer);
reader.accept(advisor, ClassReader.SKIP_FRAMES);
@@ -105,12 +118,12 @@ public class SironaTransformer implement
}
public static class SironaClassWriter extends ClassWriter {
- private SironaClassWriter(int flags) {
- super(flags);
- }
+ private final ConcurrentMap<ClassLoader, ClassLoader> tempClassLoaders;
- public SironaClassWriter(ClassReader classReader, int flags) {
+ public SironaClassWriter(final ConcurrentMap<ClassLoader, ClassLoader> tempClassLoaders,
+ final ClassReader classReader, final int flags) {
super(classReader, flags);
+ this.tempClassLoaders = tempClassLoaders;
}
/**
@@ -122,10 +135,11 @@ public class SironaTransformer implement
*/
@Override
protected String getCommonSuperClass(final String type1, final String type2) {
+ final ClassLoader loader = createTempLoader();
Class<?> c, d;
try {
- c = findClass(type1.replace('/', '.'));
- d = findClass(type2.replace('/', '.'));
+ c = findClass(loader, type1.replace('/', '.'));
+ d = findClass(loader, type2.replace('/', '.'));
} catch (final Exception e) {
throw new RuntimeException(e.toString());
} catch (final ClassCircularityError e) {
@@ -147,13 +161,25 @@ public class SironaTransformer implement
}
}
- protected Class<?> findClass(final String className)
- throws ClassNotFoundException {
- try { // first TCCL
- ClassLoader tccl = Thread.currentThread().getContextClassLoader();
- if (tccl == null) {
- tccl = getClass().getClassLoader();
+ private ClassLoader createTempLoader() {
+ final ClassLoader tccl = ClassLoaders.current();
+ if (tempClassLoaders != null) {
+ ClassLoader temp = tempClassLoaders.get(tccl);
+ if (temp == null) {
+ temp = new LoadFirstClassLoader(tccl);
+ final ClassLoader existing = tempClassLoaders.putIfAbsent(tccl, temp);
+ if (existing != null) {
+ temp = existing;
+ }
}
+ return temp;
+ }
+ return tccl;
+ }
+
+ protected Class<?> findClass(final ClassLoader tccl, final String className)
+ throws ClassNotFoundException {
+ try {
return Class.forName(className, false, tccl);
} catch (ClassNotFoundException e) {
return Class.forName(className, false, getClass().getClassLoader());
Added: incubator/sirona/trunk/agent/javaagent/src/main/java/org/apache/sirona/javaagent/classloader/LoadFirstClassLoader.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/agent/javaagent/src/main/java/org/apache/sirona/javaagent/classloader/LoadFirstClassLoader.java?rev=1714239&view=auto
==============================================================================
--- incubator/sirona/trunk/agent/javaagent/src/main/java/org/apache/sirona/javaagent/classloader/LoadFirstClassLoader.java (added)
+++ incubator/sirona/trunk/agent/javaagent/src/main/java/org/apache/sirona/javaagent/classloader/LoadFirstClassLoader.java Fri Nov 13 17:09:41 2015
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sirona.javaagent.classloader;
+
+import org.apache.sirona.SironaException;
+import org.apache.sirona.util.ClassLoaders;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+public class LoadFirstClassLoader extends URLClassLoader {
+ private final ClassLoader jvmLoader;
+
+ public LoadFirstClassLoader(final ClassLoader parent) {
+ super(findUrls(parent), parent);
+ jvmLoader = ClassLoader.getSystemClassLoader().getParent();
+ }
+
+ private static URL[] findUrls(final ClassLoader loader) {
+ try {
+ return ClassLoaders.findUrls(loader);
+ } catch (IOException e) {
+ if (URLClassLoader.class.isInstance(loader)) {
+ return URLClassLoader.class.cast(loader).getURLs();
+ }
+ throw new SironaException(e);
+ }
+ }
+
+ @Override
+ public synchronized Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
+ // synchronized (getClassLoadingLock(name)) { // j7
+ Class<?> clazz = findLoadedClass(name);
+ if (clazz != null) {
+ if (resolve) {
+ resolveClass(clazz);
+ }
+ return clazz;
+ }
+
+ try {
+ clazz = jvmLoader.loadClass(name);
+ if (clazz != null) {
+ if (resolve) {
+ resolveClass(clazz);
+ }
+ return clazz;
+ }
+ } catch (final ClassNotFoundException ignored) {
+ // no-op
+ }
+
+ // look for it in this classloader
+ clazz = loadInternal(name, resolve);
+ if (clazz != null) {
+ return clazz;
+ }
+
+ // finally delegate
+ clazz = loadFromParent(name, resolve);
+ if (clazz != null) {
+ return clazz;
+ }
+
+ throw new ClassNotFoundException(name);
+ // } // j7
+ }
+
+ private Class<?> loadFromParent(final String name, final boolean resolve) {
+ ClassLoader parent = getParent();
+ if (parent == null) {
+ parent = jvmLoader;
+ }
+ try {
+ final Class<?> clazz = Class.forName(name, false, parent);
+ if (clazz != null) {
+ if (resolve) {
+ resolveClass(clazz);
+ }
+ return clazz;
+ }
+ } catch (final ClassNotFoundException ignored) {
+ // no-op
+ }
+ return null;
+ }
+
+ public Class<?> loadInternal(final String name, final boolean resolve) {
+ try {
+ final Class<?> clazz = findClass(name);
+ if (clazz != null) {
+ if (resolve) {
+ resolveClass(clazz);
+ }
+ return clazz;
+ }
+ } catch (final ClassNotFoundException ignored) {
+ // no-op
+ }
+ return null;
+ }
+}
Modified: incubator/sirona/trunk/agent/javaagent/src/test/java/org/apache/sirona/javaagent/InJvmTransformerRunner.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/agent/javaagent/src/test/java/org/apache/sirona/javaagent/InJvmTransformerRunner.java?rev=1714239&r1=1714238&r2=1714239&view=diff
==============================================================================
--- incubator/sirona/trunk/agent/javaagent/src/test/java/org/apache/sirona/javaagent/InJvmTransformerRunner.java (original)
+++ incubator/sirona/trunk/agent/javaagent/src/test/java/org/apache/sirona/javaagent/InJvmTransformerRunner.java Fri Nov 13 17:09:41 2015
@@ -154,7 +154,7 @@ public class InJvmTransformerRunner exte
byte[] buffer = IOUtils.toByteArray(is);
for (final Class<?> t : transformers) {
if (SironaTransformer.class.equals(t)) {
- final SironaTransformer transformer = new SironaTransformer(false, null);
+ final SironaTransformer transformer = new SironaTransformer(false, false, null);
buffer = transformer.transform(this, className, null, null, buffer);
} else if (PCClassFileTransformer.class.equals(t)) {
buffer = handleJpa(name, buffer);
Modified: incubator/sirona/trunk/agent/javaagent/src/test/java/org/apache/test/sirona/javaagent/EnsureInstrumationDoesntFailTest.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/agent/javaagent/src/test/java/org/apache/test/sirona/javaagent/EnsureInstrumationDoesntFailTest.java?rev=1714239&r1=1714238&r2=1714239&view=diff
==============================================================================
--- incubator/sirona/trunk/agent/javaagent/src/test/java/org/apache/test/sirona/javaagent/EnsureInstrumationDoesntFailTest.java (original)
+++ incubator/sirona/trunk/agent/javaagent/src/test/java/org/apache/test/sirona/javaagent/EnsureInstrumationDoesntFailTest.java Fri Nov 13 17:09:41 2015
@@ -26,7 +26,7 @@ import java.net.URLClassLoader;
public class EnsureInstrumationDoesntFailTest {
@Test // just check it doesn't throw an exception, mainly a debug test
public void run() throws IllegalClassFormatException {
- new SironaTransformer(true, null)
+ new SironaTransformer(true, false, null)
.transform(
new URLClassLoader(new URL[0]), App.class.getName().replace('.', '/'),
App.class, App.class.getProtectionDomain(), new byte[]{
Modified: incubator/sirona/trunk/api/src/main/java/org/apache/sirona/util/ClassLoaders.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/api/src/main/java/org/apache/sirona/util/ClassLoaders.java?rev=1714239&r1=1714238&r2=1714239&view=diff
==============================================================================
--- incubator/sirona/trunk/api/src/main/java/org/apache/sirona/util/ClassLoaders.java (original)
+++ incubator/sirona/trunk/api/src/main/java/org/apache/sirona/util/ClassLoaders.java Fri Nov 13 17:09:41 2015
@@ -16,7 +16,23 @@
*/
package org.apache.sirona.util;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
public class ClassLoaders {
+ private static final boolean DONT_USE_GET_URLS = Boolean.getBoolean("xbean.finder.use.get-resources");
+ private static final ClassLoader SYSTEM = ClassLoader.getSystemClassLoader();
+ private static final boolean UNIX = !System.getProperty("os.name").toLowerCase().contains("win");
+ private static final URL[] NO_URL = new URL[0];
+
public static ClassLoader current() {
final ClassLoader tccl = Thread.currentThread().getContextClassLoader();
if (tccl != null) {
@@ -25,6 +41,152 @@ public class ClassLoaders {
return ClassLoaders.class.getClassLoader();
}
+ public static URL[] findUrls(final ClassLoader classLoader) throws IOException {
+ if (classLoader == null || (SYSTEM.getParent() != null && classLoader == SYSTEM.getParent())) {
+ return NO_URL;
+ }
+
+ final Set<URL> urls = new HashSet<URL>();
+
+ if (URLClassLoader.class.isInstance(classLoader) && !DONT_USE_GET_URLS) {
+ if (!isSurefire(classLoader)) {
+ for (final URL[] item : new URL[][] { URLClassLoader.class.cast(classLoader).getURLs(), findUrls(classLoader.getParent()) }) {
+ for (final URL url : item) {
+ addIfNotSo(urls, url);
+ }
+ }
+ } else { // http://jira.codehaus.org/browse/SUREFIRE-928 - we could reuse findUrlFromResources but this seems faster
+ urls.addAll(fromClassPath());
+ }
+ }
+
+ // DONT_USE_GET_URLS ||Â java -jar xxx.jar and use MANIFEST.MF Class-Path?
+ // here perf is not an issue since we would either miss all the classpath or we have a single jar
+ if (urls.size() <= 1) {
+ final Set<URL> urlFromResources = findUrlFromResources(classLoader);
+ if (!urls.isEmpty()) {
+ final URL theUrl = urls.iterator().next();
+ if ("file".equals(theUrl.getProtocol())) { // theUrl can be file:xxxx but it is the same entry actually
+ urlFromResources.remove(new URL("jar:" + theUrl.toExternalForm() + "!/"));
+ }
+ }
+ urls.addAll(urlFromResources);
+ }
+
+ return urls.toArray(new URL[urls.size()]);
+ }
+
+ private static void addIfNotSo(final Set<URL> urls, final URL url) {
+ if (UNIX && isNative(url)) {
+ return;
+ }
+
+ urls.add(url);
+ }
+
+ public static boolean isNative(final URL url) {
+ final File file = toFile(url);
+ if (file != null) {
+ final String name = file.getName();
+ if (!name.endsWith(".jar") && !file.isDirectory()
+ && name.contains(".so") && file.getAbsolutePath().startsWith("/usr/lib")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static File toFile(final URL url) {
+ if ("jar".equals(url.getProtocol())) {
+ try {
+ final String spec = url.getFile();
+ final int separator = spec.indexOf('!');
+ if (separator == -1) {
+ return null;
+ }
+ return toFile(new URL(spec.substring(0, separator + 1)));
+ } catch (final MalformedURLException e) {
+ return null;
+ }
+ } else if ("file".equals(url.getProtocol())) {
+ String path = decode(url.getFile());
+ if (path.endsWith("!")) {
+ path = path.substring(0, path.length() - 1);
+ }
+ return new File(path);
+ }
+ return null;
+ }
+
+ public static String decode(String fileName) {
+ if (fileName.indexOf('%') == -1) return fileName;
+
+ StringBuilder result = new StringBuilder(fileName.length());
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ for (int i = 0; i < fileName.length();) {
+ char c = fileName.charAt(i);
+
+ if (c == '%') {
+ out.reset();
+ do {
+ if (i + 2 >= fileName.length()) {
+ throw new IllegalArgumentException("Incomplete % sequence at: " + i);
+ }
+
+ int d1 = Character.digit(fileName.charAt(i + 1), 16);
+ int d2 = Character.digit(fileName.charAt(i + 2), 16);
+
+ if (d1 == -1 || d2 == -1) {
+ throw new IllegalArgumentException("Invalid % sequence (" + fileName.substring(i, i + 3) + ") at: " + String.valueOf(i));
+ }
+
+ out.write((byte) ((d1 << 4) + d2));
+
+ i += 3;
+
+ } while (i < fileName.length() && fileName.charAt(i) == '%');
+
+
+ result.append(out.toString());
+
+ continue;
+ } else {
+ result.append(c);
+ }
+
+ i++;
+ }
+ return result.toString();
+ }
+
+ private static boolean isSurefire(ClassLoader classLoader) {
+ return System.getProperty("surefire.real.class.path") != null && classLoader == SYSTEM;
+ }
+
+ private static Collection<URL> fromClassPath() {
+ final String[] cp = System.getProperty("java.class.path").split(System.getProperty("path.separator", ":"));
+ final Set<URL> urls = new HashSet<URL>();
+ for (final String path : cp) {
+ try {
+ urls.add(new File(path).toURI().toURL()); // don't build the url in plain String since it is not portable
+ } catch (final MalformedURLException e) {
+ // ignore
+ }
+ }
+ return urls;
+ }
+
+ public static Set<URL> findUrlFromResources(final ClassLoader classLoader) throws IOException {
+ final Set<URL> set = new HashSet<URL>();
+ for (final URL url : Collections.list(classLoader.getResources("META-INF"))) {
+ final String externalForm = url.toExternalForm();
+ set.add(new URL(externalForm.substring(0, externalForm.lastIndexOf("META-INF"))));
+ }
+ set.addAll(Collections.list(classLoader.getResources("")));
+ return set;
+ }
+
private ClassLoaders() {
// no-op
}