Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8249009

Custom JFR event class transformation not thread safe

    XMLWordPrintable

    Details

    • Type: Bug
    • Status: Resolved
    • Priority: P4
    • Resolution: Duplicate
    • Affects Version/s: 13.0.1
    • Fix Version/s: None
    • Component/s: hotspot
    • Labels:
    • Subcomponent:
      jfr
    • CPU:
      x86
    • OS:
      os_x

      Description

      ADDITIONAL SYSTEM INFORMATION :
      Java versions: we only tested 11 and 13 and the both crash
      OS: happens on Linux and macOS

      A DESCRIPTION OF THE PROBLEM :
      The class transformation of custom JFR events does not seem to be thread save. If multiple threads are loading the same custom JFR event class, eg. because the classloader is parallel capable, then this results in a JVM crash.

      We are using a middleware which uses custom, parallel capable, classloaders. These classloaders implement #defineClass in a matter similar to the pseudocode below. They accept a race condition where multiple threads are calling #definceClass for the same class and rely on the JVM throwing a LinkageError for the loser. This works for normal classes but causes a crash for custom JFR event classes.


      public class CustomClassLoader extends ClassLoader {

        static {
          registerAsParallelCapable();
        }

        @Override
        protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
          Class<?> loadedClass = findLoadedClass(className);
          if (loadedClass != null) {
            if (resolve) {
              resolveClass(loadedClass);
            }
            return loadedClass;
          }

          Class<?> clazz;
          try {
            clazz = defineClass(className, byteCode, 0, byteCode.length);
          } catch (LinkageError e) {
            // we lost the race, somebody else loaded the class
            clazz = findLoadedClass(className);
          }
          if (resolve) {
            resolveClass(clazz);
          }
          return clazz;
        }

      }

      ERROR MESSAGES/STACK TRACES THAT OCCUR :
      #
      # A fatal error has been detected by the Java Runtime Environment:
      #
      # SIGSEGV (0xb) at pc=0x000000010e3ab66a, pid=21703, tid=26115
      #
      # JRE version: OpenJDK Runtime Environment (13.0.1+9) (build 13.0.1+9)
      # Java VM: OpenJDK 64-Bit Server VM (13.0.1+9, mixed mode, sharing, tiered, compressed oops, g1 gc, bsd-amd64)
      # Problematic frame:
      # V [libjvm.dylib+0x3ab66a] JfrEventClassTransformer::on_klass_creation(InstanceKlass*&, ClassFileParser&, Thread*)+0x3ea
      #
      # No core dump will be written. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
      #
      # An error report file with more information is saved as:
      # /Users/user/git/jfr-crasher/hs_err_pid21703.log
      #
      # If you would like to submit a bug report, please visit:
      # http://bugreport.java.com/bugreport/crash.jsp
      #


      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Run the attached program.

      ACTUAL -
      JVM does crash

      ---------- BEGIN SOURCE ----------
      package com.github.marschall.jfr.crasher;

      import java.io.ByteArrayOutputStream;
      import java.io.IOException;
      import java.io.InputStream;
      import java.io.UncheckedIOException;
      import java.util.concurrent.BrokenBarrierException;
      import java.util.concurrent.CyclicBarrier;
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;

      import jdk.jfr.Category;
      import jdk.jfr.Description;
      import jdk.jfr.Event;
      import jdk.jfr.Label;

      public final class JfrCrasher {

        private static final String RUNNABLE_EVENT = "com.github.marschall.jfr.crasher.JfrCrasher$RunnableEvent";

        private static final String JFR_RUNNABLE = "com.github.marschall.jfr.crasher.JfrCrasher$JfrRunnable";

        private volatile ClassLoader nextLoader;

        public void crash() {
          byte[] runnableClass = loadBytecode(JFR_RUNNABLE);
          byte[] eventClass = loadBytecode(RUNNABLE_EVENT);
          
          int numberOfThreads = Runtime.getRuntime().availableProcessors();
          if (numberOfThreads < 1) {
            throw new IllegalStateException("requies more than one thread");
          }
          ExecutorService threadPool = Executors.newFixedThreadPool(numberOfThreads);
          CyclicBarrier cyclicBarrier = new CyclicBarrier(numberOfThreads, () -> {
            this.nextLoader = new PredefinedClassLoader(runnableClass, eventClass);
          });
          for (int i = 0; i < numberOfThreads; i++) {
            threadPool.submit(new LoadingRunnable(cyclicBarrier));
          }
          threadPool.shutdown();
        }

        final class LoadingRunnable implements Runnable {

          private final CyclicBarrier barrier;

          LoadingRunnable(CyclicBarrier barrier) {
            this.barrier = barrier;
          }

          @Override
          public void run() {
            while (true) {
              try {
                this.barrier.await();
                Runnable runnable = loadJfrRunnable(nextLoader);
                runnable.run();
              } catch (InterruptedException | BrokenBarrierException e) {
                return;
              }
            }
          }

        }

        Runnable loadJfrRunnable(ClassLoader classLoader) {
          try {
            return Class.forName(JFR_RUNNABLE, true, classLoader).asSubclass(Runnable.class).getConstructor().newInstance();
          } catch (ReflectiveOperationException e) {
            System.err.println(e);
            throw new RuntimeException("could not load runnable", e);
          }
        }

        private static byte[] loadBytecode(String className) {
          String resource = toResourceName(className);
          ByteArrayOutputStream buffer = new ByteArrayOutputStream();
          try (InputStream inputStream = JfrCrasher.class.getClassLoader().getResourceAsStream(resource)) {
            inputStream.transferTo(buffer);
          } catch (IOException e) {
            System.err.println(e);
            throw new UncheckedIOException("could not get bytecode of class:" + className, e);
          }
          return buffer.toByteArray();
        }

        private static String toResourceName(String className) {
          return className.replace('.', '/') + ".class";
        }

        public static void main(String[] args) {
          new JfrCrasher().crash();
        }

        static final class PredefinedClassLoader extends ClassLoader {

          static {
            registerAsParallelCapable();
          }

          private final byte[] runnableClass;

          private final byte[] eventClass;

          PredefinedClassLoader(byte[] runnableClass, byte[] eventClass) {
            super(null); // null parent
            this.runnableClass = runnableClass;
            this.eventClass = eventClass;
          }

          @Override
          protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
            // Check if we have already loaded it..
            Class<?> loadedClass = findLoadedClass(className);
            if (loadedClass != null) {
              if (resolve) {
                resolveClass(loadedClass);
              }
              return loadedClass;
            }

            if (className.equals(JFR_RUNNABLE)) {
              return loadClassFromByteArray(className, resolve, runnableClass);
            } else if (className.equals(RUNNABLE_EVENT)) {
              return loadClassFromByteArray(className, resolve, eventClass);
            } else {
              return super.loadClass(className, resolve);
            }
          }

          private Class<?> loadClassFromByteArray(String className, boolean resolve, byte[] byteCode) throws ClassNotFoundException {
            Class<?> clazz;
            try {
              clazz = defineClass(className, byteCode, 0, byteCode.length);
            } catch (LinkageError e) {
              // we lost the race, somebody else loaded the class
              clazz = findLoadedClass(className);
            }
            if (resolve) {
              resolveClass(clazz);
            }
            return clazz;
          }

        }

        public static final class JfrRunnable implements Runnable {

          @Override
          public void run() {
            RunnableEvent event = new RunnableEvent();
            event.setRunnableClassName("JfrRunnable");
            event.begin();
            event.end();
            event.commit();
          }
        }

        @Label("Runnable")
        @Description("An executed Runnable")
        @Category("Custom JFR Events")
        static class RunnableEvent extends Event {

          @Label("Operation Name")
          @Description("The name of the JDBC operation")
          private String runnableClassName;

          String getRunnableClassName() {
            return this.runnableClassName;
          }

          void setRunnableClassName(String operationName) {
            this.runnableClassName = operationName;
          }

        }

      }

      ---------- END SOURCE ----------

      FREQUENCY : always


        Attachments

          Issue Links

            Activity

              People

              Assignee:
              mgronlun Markus Grönlund
              Reporter:
              webbuggrp Webbug Group
              Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

                Dates

                Created:
                Updated:
                Resolved: