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
     }