This is an automated email from the ASF dual-hosted git repository.

jackie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new 0aeb42eebc Fix the race condition of reflection scanning classes 
(#9167)
0aeb42eebc is described below

commit 0aeb42eebc0846611762a9ff2cdc1e1ede600727
Author: Xiaotian (Jackie) Jiang <17555551+jackie-ji...@users.noreply.github.com>
AuthorDate: Fri Aug 5 01:17:35 2022 -0700

    Fix the race condition of reflection scanning classes (#9167)
---
 .../broker/broker/BrokerAdminApiApplication.java   |  4 +-
 .../pinot/common/function/FunctionRegistry.java    | 14 +---
 .../api/ControllerAdminApiApplication.java         | 15 ++--
 .../controller/tuner/TableConfigTunerRegistry.java | 27 ++-----
 .../pinot/minion/MinionAdminApiApplication.java    |  4 +-
 .../spi/loader/SegmentDirectoryLoaderRegistry.java | 20 ++---
 .../server/starter/helix/AdminApiApplication.java  | 12 ++-
 .../pinot/spi/utils/PinotReflectionUtils.java      | 91 +++++++++++++++++++---
 .../PinotServiceManagerAdminApiApplication.java    |  4 +-
 9 files changed, 109 insertions(+), 82 deletions(-)

diff --git 
a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerAdminApiApplication.java
 
b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerAdminApiApplication.java
index fcf151fab6..fb8c4d6f0a 100644
--- 
a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerAdminApiApplication.java
+++ 
b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerAdminApiApplication.java
@@ -80,9 +80,7 @@ public class BrokerAdminApiApplication extends ResourceConfig 
{
     } catch (IOException e) {
       throw new RuntimeException("Failed to start http server", e);
     }
-    synchronized (PinotReflectionUtils.getReflectionLock()) {
-      setupSwagger();
-    }
+    PinotReflectionUtils.runWithLock(this::setupSwagger);
   }
 
   private void setupSwagger() {
diff --git 
a/pinot-common/src/main/java/org/apache/pinot/common/function/FunctionRegistry.java
 
b/pinot-common/src/main/java/org/apache/pinot/common/function/FunctionRegistry.java
index db8864aed7..7633e2391c 100644
--- 
a/pinot-common/src/main/java/org/apache/pinot/common/function/FunctionRegistry.java
+++ 
b/pinot-common/src/main/java/org/apache/pinot/common/function/FunctionRegistry.java
@@ -27,11 +27,7 @@ import java.util.Set;
 import javax.annotation.Nullable;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.pinot.spi.annotations.ScalarFunction;
-import org.reflections.Reflections;
-import org.reflections.scanners.MethodAnnotationsScanner;
-import org.reflections.util.ClasspathHelper;
-import org.reflections.util.ConfigurationBuilder;
-import org.reflections.util.FilterBuilder;
+import org.apache.pinot.spi.utils.PinotReflectionUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -54,12 +50,8 @@ public class FunctionRegistry {
    */
   static {
     long startTimeMs = System.currentTimeMillis();
-    Reflections reflections = new Reflections(
-        new 
ConfigurationBuilder().setUrls(ClasspathHelper.forPackage("org.apache.pinot"))
-            .filterInputsBy(new FilterBuilder.Include(".*\\.function\\..*"))
-            .setScanners(new MethodAnnotationsScanner()));
-    Set<Method> methodSet = 
reflections.getMethodsAnnotatedWith(ScalarFunction.class);
-    for (Method method : methodSet) {
+    Set<Method> methods = 
PinotReflectionUtils.getMethodsThroughReflection(".*\\.function\\..*", 
ScalarFunction.class);
+    for (Method method : methods) {
       if (!Modifier.isPublic(method.getModifiers())) {
         continue;
       }
diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/ControllerAdminApiApplication.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/ControllerAdminApiApplication.java
index f7ef335ee1..d1a77d6789 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/ControllerAdminApiApplication.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/ControllerAdminApiApplication.java
@@ -40,12 +40,9 @@ import org.glassfish.hk2.utilities.binding.AbstractBinder;
 import org.glassfish.jersey.jackson.JacksonFeature;
 import org.glassfish.jersey.media.multipart.MultiPartFeature;
 import org.glassfish.jersey.server.ResourceConfig;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 
 public class ControllerAdminApiApplication extends ResourceConfig {
-  private static final Logger LOGGER = 
LoggerFactory.getLogger(ControllerAdminApiApplication.class);
   public static final String PINOT_CONFIGURATION = "pinotConfiguration";
 
   private final String _controllerResourcePackages;
@@ -86,9 +83,7 @@ public class ControllerAdminApiApplication extends 
ResourceConfig {
     } catch (IOException e) {
       throw new RuntimeException("Failed to start http server", e);
     }
-    synchronized (PinotReflectionUtils.getReflectionLock()) {
-      setupSwagger(_httpServer);
-    }
+    PinotReflectionUtils.runWithLock(this::setupSwagger);
 
     ClassLoader classLoader = 
ControllerAdminApiApplication.class.getClassLoader();
 
@@ -105,7 +100,7 @@ public class ControllerAdminApiApplication extends 
ResourceConfig {
     _httpServer.getServerConfiguration().addHttpHandler(new 
CLStaticHttpHandler(classLoader, "/webapp/js/"), "/js/");
   }
 
-  private void setupSwagger(HttpServer httpServer) {
+  private void setupSwagger() {
     BeanConfig beanConfig = new BeanConfig();
     beanConfig.setTitle("Pinot Controller API");
     beanConfig.setDescription("APIs for accessing Pinot Controller 
information");
@@ -124,12 +119,12 @@ public class ControllerAdminApiApplication extends 
ResourceConfig {
     ClassLoader loader = this.getClass().getClassLoader();
     CLStaticHttpHandler apiStaticHttpHandler = new CLStaticHttpHandler(loader, 
"/api/");
     // map both /api and /help to swagger docs. /api because it looks nice. 
/help for backward compatibility
-    httpServer.getServerConfiguration().addHttpHandler(apiStaticHttpHandler, 
"/api/");
-    httpServer.getServerConfiguration().addHttpHandler(apiStaticHttpHandler, 
"/help/");
+    _httpServer.getServerConfiguration().addHttpHandler(apiStaticHttpHandler, 
"/api/");
+    _httpServer.getServerConfiguration().addHttpHandler(apiStaticHttpHandler, 
"/help/");
 
     URL swaggerDistLocation = 
loader.getResource("META-INF/resources/webjars/swagger-ui/3.23.11/");
     CLStaticHttpHandler swaggerDist = new CLStaticHttpHandler(new 
URLClassLoader(new URL[]{swaggerDistLocation}));
-    httpServer.getServerConfiguration().addHttpHandler(swaggerDist, 
"/swaggerui-dist/");
+    _httpServer.getServerConfiguration().addHttpHandler(swaggerDist, 
"/swaggerui-dist/");
   }
 
   public void stop() {
diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/tuner/TableConfigTunerRegistry.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/tuner/TableConfigTunerRegistry.java
index 2913e72d12..173069846b 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/tuner/TableConfigTunerRegistry.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/tuner/TableConfigTunerRegistry.java
@@ -19,25 +19,17 @@
 package org.apache.pinot.controller.tuner;
 
 import com.google.common.base.Preconditions;
-import java.net.URL;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
-import org.reflections.Reflections;
-import org.reflections.scanners.ResourcesScanner;
-import org.reflections.scanners.SubTypesScanner;
-import org.reflections.scanners.TypeAnnotationsScanner;
-import org.reflections.util.ClasspathHelper;
-import org.reflections.util.ConfigurationBuilder;
-import org.reflections.util.FilterBuilder;
+import org.apache.pinot.spi.utils.PinotReflectionUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 
 /**
- * Helper class to dynamically register all annotated {@link Tuner} methods
+ * Helper class to dynamically register all annotated {@link Tuner} classes.
  */
 public class TableConfigTunerRegistry {
   private TableConfigTunerRegistry() {
@@ -63,16 +55,9 @@ public class TableConfigTunerRegistry {
     }
     long startTime = System.currentTimeMillis();
 
-    List<URL> urls = new ArrayList<>();
-    for (String pack : packages) {
-      urls.addAll(ClasspathHelper.forPackage(pack));
-    }
-
-    Reflections reflections = new Reflections(
-        new ConfigurationBuilder().setUrls(urls).filterInputsBy(new 
FilterBuilder.Include(".*\\.tuner\\..*"))
-            .setScanners(new ResourcesScanner(), new TypeAnnotationsScanner(), 
new SubTypesScanner()));
-    Set<Class<?>> classes = reflections.getTypesAnnotatedWith(Tuner.class);
-    classes.forEach(tunerClass -> {
+    Set<Class<?>> tunerClasses =
+        PinotReflectionUtils.getClassesThroughReflection(packages, 
".*\\.tuner\\..*", Tuner.class);
+    for (Class<?> tunerClass : tunerClasses) {
       Tuner tunerAnnotation = tunerClass.getAnnotation(Tuner.class);
       if (tunerAnnotation.enabled()) {
         if (tunerAnnotation.name().isEmpty()) {
@@ -88,7 +73,7 @@ public class TableConfigTunerRegistry {
           }
         }
       }
-    });
+    }
 
     _init = true;
     LOGGER.info("Initialized TableConfigTunerRegistry with {} tuners: {} in {} 
ms", CONFIG_TUNER_MAP.size(),
diff --git 
a/pinot-minion/src/main/java/org/apache/pinot/minion/MinionAdminApiApplication.java
 
b/pinot-minion/src/main/java/org/apache/pinot/minion/MinionAdminApiApplication.java
index 4616f06432..97eae3ff7d 100644
--- 
a/pinot-minion/src/main/java/org/apache/pinot/minion/MinionAdminApiApplication.java
+++ 
b/pinot-minion/src/main/java/org/apache/pinot/minion/MinionAdminApiApplication.java
@@ -74,9 +74,7 @@ public class MinionAdminApiApplication extends ResourceConfig 
{
     } catch (IOException e) {
       throw new RuntimeException("Failed to start http server", e);
     }
-    synchronized (PinotReflectionUtils.getReflectionLock()) {
-      setupSwagger();
-    }
+    PinotReflectionUtils.runWithLock(this::setupSwagger);
   }
 
   private void setupSwagger() {
diff --git 
a/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/loader/SegmentDirectoryLoaderRegistry.java
 
b/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/loader/SegmentDirectoryLoaderRegistry.java
index 407ef32419..0e43826fd0 100644
--- 
a/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/loader/SegmentDirectoryLoaderRegistry.java
+++ 
b/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/loader/SegmentDirectoryLoaderRegistry.java
@@ -21,13 +21,7 @@ package org.apache.pinot.segment.spi.loader;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
-import org.reflections.Reflections;
-import org.reflections.scanners.ResourcesScanner;
-import org.reflections.scanners.SubTypesScanner;
-import org.reflections.scanners.TypeAnnotationsScanner;
-import org.reflections.util.ClasspathHelper;
-import org.reflections.util.ConfigurationBuilder;
-import org.reflections.util.FilterBuilder;
+import org.apache.pinot.spi.utils.PinotReflectionUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -46,12 +40,10 @@ public class SegmentDirectoryLoaderRegistry {
   static {
     long startTime = System.currentTimeMillis();
 
-    Reflections reflections = new Reflections(
-        new 
ConfigurationBuilder().setUrls(ClasspathHelper.forPackage("org.apache.pinot.segment"))
-            .filterInputsBy(new FilterBuilder.Include(".*\\.loader\\..*"))
-            .setScanners(new ResourcesScanner(), new TypeAnnotationsScanner(), 
new SubTypesScanner()));
-    Set<Class<?>> classes = 
reflections.getTypesAnnotatedWith(SegmentLoader.class);
-    classes.forEach(loaderClass -> {
+    Set<Class<?>> loaderClasses =
+        
PinotReflectionUtils.getClassesThroughReflection("org.apache.pinot.segment", 
".*\\.loader\\..*",
+            SegmentLoader.class);
+    for (Class<?> loaderClass : loaderClasses) {
       SegmentLoader segmentLoaderAnnotation = 
loaderClass.getAnnotation(SegmentLoader.class);
       if (segmentLoaderAnnotation.enabled()) {
         if (segmentLoaderAnnotation.name().isEmpty()) {
@@ -69,7 +61,7 @@ public class SegmentDirectoryLoaderRegistry {
           }
         }
       }
-    });
+    }
 
     LOGGER.info("Initialized SegmentDirectoryLoaderRegistry with {} 
segmentDirectoryLoaders: {} in {} ms",
         SEGMENT_DIRECTORY_LOADER_MAP.size(), 
SEGMENT_DIRECTORY_LOADER_MAP.keySet(),
diff --git 
a/pinot-server/src/main/java/org/apache/pinot/server/starter/helix/AdminApiApplication.java
 
b/pinot-server/src/main/java/org/apache/pinot/server/starter/helix/AdminApiApplication.java
index aa645005a3..dec6ca8085 100644
--- 
a/pinot-server/src/main/java/org/apache/pinot/server/starter/helix/AdminApiApplication.java
+++ 
b/pinot-server/src/main/java/org/apache/pinot/server/starter/helix/AdminApiApplication.java
@@ -101,15 +101,13 @@ public class AdminApiApplication extends ResourceConfig {
     if 
(pinotConfiguration.getProperty(CommonConstants.Server.CONFIG_OF_SWAGGER_SERVER_ENABLED,
         CommonConstants.Server.DEFAULT_SWAGGER_SERVER_ENABLED)) {
       LOGGER.info("Starting swagger for the Pinot server.");
-      synchronized (PinotReflectionUtils.getReflectionLock()) {
-        setupSwagger(_httpServer, pinotConfiguration);
-      }
+      PinotReflectionUtils.runWithLock(() -> setupSwagger(pinotConfiguration));
     }
     _started = true;
     return true;
   }
 
-  private void setupSwagger(HttpServer httpServer, PinotConfiguration 
pinotConfiguration) {
+  private void setupSwagger(PinotConfiguration pinotConfiguration) {
     BeanConfig beanConfig = new BeanConfig();
     beanConfig.setTitle("Pinot Server API");
     beanConfig.setDescription("APIs for accessing Pinot server information");
@@ -133,13 +131,13 @@ public class AdminApiApplication extends ResourceConfig {
     CLStaticHttpHandler staticHttpHandler =
         new CLStaticHttpHandler(AdminApiApplication.class.getClassLoader(), 
"/api/");
     // map both /api and /help to swagger docs. /api because it looks nice. 
/help for backward compatibility
-    httpServer.getServerConfiguration().addHttpHandler(staticHttpHandler, 
"/api/");
-    httpServer.getServerConfiguration().addHttpHandler(staticHttpHandler, 
"/help/");
+    _httpServer.getServerConfiguration().addHttpHandler(staticHttpHandler, 
"/api/");
+    _httpServer.getServerConfiguration().addHttpHandler(staticHttpHandler, 
"/help/");
 
     URL swaggerDistLocation =
         
AdminApiApplication.class.getClassLoader().getResource("META-INF/resources/webjars/swagger-ui/3.23.11/");
     CLStaticHttpHandler swaggerDist = new CLStaticHttpHandler(new 
URLClassLoader(new URL[]{swaggerDistLocation}));
-    httpServer.getServerConfiguration().addHttpHandler(swaggerDist, 
"/swaggerui-dist/");
+    _httpServer.getServerConfiguration().addHttpHandler(swaggerDist, 
"/swaggerui-dist/");
   }
 
   public void stop() {
diff --git 
a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/PinotReflectionUtils.java 
b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/PinotReflectionUtils.java
index 20ee074320..bcc55cd530 100644
--- 
a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/PinotReflectionUtils.java
+++ 
b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/PinotReflectionUtils.java
@@ -19,28 +19,98 @@
 package org.apache.pinot.spi.utils;
 
 import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Set;
 import org.reflections.Reflections;
-import org.reflections.scanners.TypeAnnotationsScanner;
+import org.reflections.scanners.MethodAnnotationsScanner;
 import org.reflections.util.ClasspathHelper;
 import org.reflections.util.ConfigurationBuilder;
 import org.reflections.util.FilterBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 
 public class PinotReflectionUtils {
   private PinotReflectionUtils() {
   }
 
-  private static final String PINOT_PACKAGE_PREFIX = "org.apache.pinot";
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(PinotReflectionUtils.class);
+  private static final String PINOT_PACKAGE_NAME = "org.apache.pinot";
+
+  // We use a lock to prevent multiple threads accessing the same jar in the 
same time which can cause exception
+  // See https://github.com/ronmamo/reflections/issues/81 for more details
   private static final Object REFLECTION_LOCK = new Object();
 
-  public static Set<Class<?>> getClassesThroughReflection(final String 
regexPattern,
-      final Class<? extends Annotation> annotation) {
-    synchronized (getReflectionLock()) {
-      Reflections reflections = new Reflections(
-          new 
ConfigurationBuilder().setUrls(ClasspathHelper.forPackage(PINOT_PACKAGE_PREFIX))
-              .filterInputsBy(new 
FilterBuilder.Include(regexPattern)).setScanners(new TypeAnnotationsScanner()));
-      return reflections.getTypesAnnotatedWith(annotation, true);
+  public static Set<Class<?>> getClassesThroughReflection(String regexPattern, 
Class<? extends Annotation> annotation) {
+    return getClassesThroughReflection(PINOT_PACKAGE_NAME, regexPattern, 
annotation);
+  }
+
+  public static Set<Class<?>> getClassesThroughReflection(String packageName, 
String regexPattern,
+      Class<? extends Annotation> annotation) {
+    try {
+      synchronized (REFLECTION_LOCK) {
+        return new Reflections(new 
ConfigurationBuilder().setUrls(ClasspathHelper.forPackage(packageName))
+            .filterInputsBy(new 
FilterBuilder.Include(regexPattern))).getTypesAnnotatedWith(annotation);
+      }
+    } catch (Throwable t) {
+      // Log an error then re-throw it because this method is usually called 
in a static block, where exception might
+      // not be properly handled
+      LOGGER.error("Error scanning classes within package: '{}' with regex 
pattern: '{}', annotation: {}", packageName,
+          regexPattern, annotation.getSimpleName(), t);
+      throw t;
+    }
+  }
+
+  public static Set<Class<?>> getClassesThroughReflection(List<String> 
packages, String regexPattern,
+      Class<? extends Annotation> annotation) {
+    try {
+      synchronized (REFLECTION_LOCK) {
+        List<URL> urls = new ArrayList<>();
+        for (String packageName : packages) {
+          urls.addAll(ClasspathHelper.forPackage(packageName));
+        }
+        return new Reflections(new ConfigurationBuilder().setUrls(urls)
+            .filterInputsBy(new 
FilterBuilder.Include(regexPattern))).getTypesAnnotatedWith(annotation);
+      }
+    } catch (Throwable t) {
+      // Log an error then re-throw it because this method is usually called 
in a static block, where exception might
+      // not be properly handled
+      LOGGER.error("Error scanning classes within packages: {} with regex 
pattern: '{}', annotation: {}", packages,
+          regexPattern, annotation.getSimpleName(), t);
+      throw t;
+    }
+  }
+
+  public static Set<Method> getMethodsThroughReflection(String regexPattern, 
Class<? extends Annotation> annotation) {
+    return getMethodsThroughReflection(PINOT_PACKAGE_NAME, regexPattern, 
annotation);
+  }
+
+  public static Set<Method> getMethodsThroughReflection(String packageName, 
String regexPattern,
+      Class<? extends Annotation> annotation) {
+    try {
+      synchronized (REFLECTION_LOCK) {
+        return new Reflections(new 
ConfigurationBuilder().setUrls(ClasspathHelper.forPackage(packageName))
+            .filterInputsBy(new FilterBuilder.Include(regexPattern))
+            .setScanners(new 
MethodAnnotationsScanner())).getMethodsAnnotatedWith(annotation);
+      }
+    } catch (Throwable t) {
+      // Log an error then re-throw it because this method is usually called 
in a static block, where exception might
+      // not be properly handled
+      LOGGER.error("Error scanning methods within package: '{}' with regex 
pattern: '{}', annotation: {}", packageName,
+          regexPattern, annotation.getSimpleName(), t);
+      throw t;
+    }
+  }
+
+  /**
+   * Executes the given runnable within the reflection lock.
+   */
+  public static void runWithLock(Runnable runnable) {
+    synchronized (REFLECTION_LOCK) {
+      runnable.run();
     }
   }
 
@@ -48,8 +118,9 @@ public class PinotReflectionUtils {
    * Due to the multi-threading issue in org.reflections.vfs.ZipDir, we need 
to put a lock before calling the
    * reflection related methods.
    *
-   * @return
+   * Deprecated: use {@link #runWithLock(Runnable)} instead
    */
+  @Deprecated
   public static Object getReflectionLock() {
     return REFLECTION_LOCK;
   }
diff --git 
a/pinot-tools/src/main/java/org/apache/pinot/tools/service/PinotServiceManagerAdminApiApplication.java
 
b/pinot-tools/src/main/java/org/apache/pinot/tools/service/PinotServiceManagerAdminApiApplication.java
index 20f5392401..c04286d1bf 100644
--- 
a/pinot-tools/src/main/java/org/apache/pinot/tools/service/PinotServiceManagerAdminApiApplication.java
+++ 
b/pinot-tools/src/main/java/org/apache/pinot/tools/service/PinotServiceManagerAdminApiApplication.java
@@ -58,9 +58,7 @@ public class PinotServiceManagerAdminApiApplication extends 
ResourceConfig {
     Preconditions.checkArgument(httpPort > 0);
     _baseUri = URI.create("http://0.0.0.0:"; + httpPort + "/");
     _httpServer = GrizzlyHttpServerFactory.createHttpServer(_baseUri, this);
-    synchronized (PinotReflectionUtils.getReflectionLock()) {
-      setupSwagger();
-    }
+    PinotReflectionUtils.runWithLock(this::setupSwagger);
   }
 
   private void setupSwagger() {


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@pinot.apache.org
For additional commands, e-mail: commits-h...@pinot.apache.org

Reply via email to