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

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


The following commit(s) were added to refs/heads/master by this push:
     new 27bb36b  Use utility executor for scanning
27bb36b is described below

commit 27bb36bdaa56d62fdcc28d3b8e3d4c25f4a44a50
Author: remm <r...@apache.org>
AuthorDate: Fri Sep 18 11:40:17 2020 +0200

    Use utility executor for scanning
    
    PR354 submitted by Jatin Kamnani.
    The flag in on the context, as that's where it belongs. Defaults to
    false, can be configured through the context defaults or per context.
---
 java/org/apache/catalina/Context.java              |  14 +++
 java/org/apache/catalina/core/StandardContext.java |  19 ++++
 .../apache/catalina/core/mbeans-descriptors.xml    |   4 +
 .../org/apache/catalina/startup/ContextConfig.java | 116 +++++++++++++++++----
 .../org/apache/catalina/startup/FailedContext.java |   5 +
 .../catalina/startup/LocalStrings.properties       |   1 +
 test/org/apache/tomcat/unittest/TesterContext.java |   5 +
 webapps/docs/changelog.xml                         |   4 +
 webapps/docs/config/context.xml                    |   8 ++
 9 files changed, 154 insertions(+), 22 deletions(-)

diff --git a/java/org/apache/catalina/Context.java 
b/java/org/apache/catalina/Context.java
index b8f55a0..4cf844c 100644
--- a/java/org/apache/catalina/Context.java
+++ b/java/org/apache/catalina/Context.java
@@ -762,6 +762,20 @@ public interface Context extends Container, ContextBind {
     public String getContainerSciFilter();
 
 
+    /**
+     * @return the value of the parallel annotation scanning flag.  If true,
+     * it will dispatch scanning to the utility executor.
+     */
+    public boolean isParallelAnnotationScanning();
+
+    /**
+     * Set the parallel annotation scanning value.
+     *
+     * @param parallelAnnotationScanning new parallel annotation scanning flag
+     */
+    public void setParallelAnnotationScanning(boolean 
parallelAnnotationScanning);
+
+
     // --------------------------------------------------------- Public Methods
 
     /**
diff --git a/java/org/apache/catalina/core/StandardContext.java 
b/java/org/apache/catalina/core/StandardContext.java
index ce51d6c..a34f14e 100644
--- a/java/org/apache/catalina/core/StandardContext.java
+++ b/java/org/apache/catalina/core/StandardContext.java
@@ -833,6 +833,8 @@ public class StandardContext extends ContainerBase
 
     private boolean dispatcherWrapsSameObject = 
Globals.STRICT_SERVLET_COMPLIANCE;
 
+    private boolean parallelAnnotationScanning = false;
+
     // ----------------------------------------------------- Context Properties
 
     @Override
@@ -1447,6 +1449,23 @@ public class StandardContext extends ContainerBase
     }
 
 
+    @Override
+    public void setParallelAnnotationScanning(boolean 
parallelAnnotationScanning) {
+
+        boolean oldParallelAnnotationScanning = 
this.parallelAnnotationScanning;
+        this.parallelAnnotationScanning = parallelAnnotationScanning;
+        support.firePropertyChange("parallelAnnotationScanning", 
oldParallelAnnotationScanning,
+                this.parallelAnnotationScanning);
+
+    }
+
+
+    @Override
+    public boolean isParallelAnnotationScanning() {
+        return this.parallelAnnotationScanning;
+    }
+
+
     /**
      * @return the Locale to character set mapper for this Context.
      */
diff --git a/java/org/apache/catalina/core/mbeans-descriptors.xml 
b/java/org/apache/catalina/core/mbeans-descriptors.xml
index 1c0733f..50be99f 100644
--- a/java/org/apache/catalina/core/mbeans-descriptors.xml
+++ b/java/org/apache/catalina/core/mbeans-descriptors.xml
@@ -208,6 +208,10 @@
                description="The name of this Context"
                type="java.lang.String"/>
 
+    <attribute name="parallelAnnotationScanning"
+               description="The parallel annotation scanning flag"
+               type="boolean"/>
+
     <attribute name="parentClassLoader"
                description="Parent class loader."
                type="java.lang.ClassLoader" />
diff --git a/java/org/apache/catalina/startup/ContextConfig.java 
b/java/org/apache/catalina/startup/ContextConfig.java
index c1fe029..a3cfcb8 100644
--- a/java/org/apache/catalina/startup/ContextConfig.java
+++ b/java/org/apache/catalina/startup/ContextConfig.java
@@ -39,6 +39,8 @@ import java.util.Map.Entry;
 import java.util.Properties;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
 
 import jakarta.servlet.MultipartConfigElement;
 import jakarta.servlet.ServletContainerInitializer;
@@ -122,7 +124,6 @@ public class ContextConfig implements LifecycleListener {
 
     private static final Log log = LogFactory.getLog(ContextConfig.class);
 
-
     /**
      * The string resources for this package.
      */
@@ -1374,7 +1375,14 @@ public class ContextConfig implements LifecycleListener {
     protected void processClasses(WebXml webXml, Set<WebXml> orderedFragments) 
{
         // Step 4. Process /WEB-INF/classes for annotations and
         // @HandlesTypes matches
-        Map<String, JavaClassCacheEntry> javaClassCache = new HashMap<>();
+
+        Map<String, JavaClassCacheEntry> javaClassCache;
+
+        if (context.isParallelAnnotationScanning()) {
+            javaClassCache = new ConcurrentHashMap<>();
+        } else {
+            javaClassCache = new HashMap<>();
+        }
 
         if (ok) {
             WebResource[] webResources =
@@ -2136,26 +2144,90 @@ public class ContextConfig implements LifecycleListener 
{
     }
 
     protected void processAnnotations(Set<WebXml> fragments,
-            boolean handlesTypesOnly, Map<String,JavaClassCacheEntry> 
javaClassCache) {
-        for(WebXml fragment : fragments) {
-            // Only need to scan for @HandlesTypes matches if any of the
-            // following are true:
-            // - it has already been determined only @HandlesTypes is required
-            //   (e.g. main web.xml has metadata-complete="true"
-            // - this fragment is for a container JAR (Servlet 3.1 section 8.1)
-            // - this fragment has metadata-complete="true"
-            boolean htOnly = handlesTypesOnly || !fragment.getWebappJar() ||
-                    fragment.isMetadataComplete();
-
-            WebXml annotations = new WebXml();
-            // no impact on distributable
-            annotations.setDistributable(true);
-            URL url = fragment.getURL();
-            processAnnotationsUrl(url, annotations, htOnly, javaClassCache);
-            Set<WebXml> set = new HashSet<>();
-            set.add(annotations);
-            // Merge annotations into fragment - fragment takes priority
-            fragment.merge(set);
+            boolean handlesTypesOnly, Map<String, JavaClassCacheEntry> 
javaClassCache) {
+
+        if (context.isParallelAnnotationScanning()) {
+            processAnnotationsInParallel(fragments, handlesTypesOnly, 
javaClassCache);
+        } else {
+            for (WebXml fragment : fragments) {
+                scanWebXmlFragment(handlesTypesOnly, fragment, javaClassCache);
+            }
+        }
+    }
+
+    private void scanWebXmlFragment(boolean handlesTypesOnly, WebXml fragment, 
Map<String, JavaClassCacheEntry> javaClassCache) {
+
+        // Only need to scan for @HandlesTypes matches if any of the
+        // following are true:
+        // - it has already been determined only @HandlesTypes is required
+        //   (e.g. main web.xml has metadata-complete="true"
+        // - this fragment is for a container JAR (Servlet 3.1 section 8.1)
+        // - this fragment has metadata-complete="true"
+        boolean htOnly = handlesTypesOnly || !fragment.getWebappJar() ||
+                fragment.isMetadataComplete();
+
+        WebXml annotations = new WebXml();
+        // no impact on distributable
+        annotations.setDistributable(true);
+        URL url = fragment.getURL();
+        processAnnotationsUrl(url, annotations, htOnly, javaClassCache);
+        Set<WebXml> set = new HashSet<>();
+        set.add(annotations);
+        // Merge annotations into fragment - fragment takes priority
+        fragment.merge(set);
+    }
+
+    /**
+     * Executable task to scan a segment for annotations. Each task does the
+     * same work as the for loop inside processAnnotations();
+     */
+    private class AnnotationScanTask implements Runnable {
+        private final WebXml fragment;
+        private final boolean handlesTypesOnly;
+        private Map<String, JavaClassCacheEntry> javaClassCache;
+
+        private AnnotationScanTask(WebXml fragment, boolean handlesTypesOnly, 
Map<String, JavaClassCacheEntry> javaClassCache) {
+            this.fragment = fragment;
+            this.handlesTypesOnly = handlesTypesOnly;
+            this.javaClassCache = javaClassCache;
+        }
+
+        @Override
+        public void run() {
+            scanWebXmlFragment(handlesTypesOnly, fragment, javaClassCache);
+        }
+
+    }
+
+    /**
+     * Parallelized version of processAnnotationsInParallel(). Constructs 
tasks,
+     * submits them as they're created, then waits for completion.
+     *
+     * @param fragments        Set of parallelizable scans
+     * @param handlesTypesOnly Important parameter for the underlying scan
+     */
+    protected void processAnnotationsInParallel(Set<WebXml> fragments, boolean 
handlesTypesOnly,
+                                                Map<String, 
JavaClassCacheEntry> javaClassCache) {
+        Server s = getServer();
+        ExecutorService pool = null;
+        try {
+            pool = s.getUtilityExecutor();
+            List<Future<?>> futures = new ArrayList<>(fragments.size());
+            for (WebXml fragment : fragments) {
+                Runnable task = new AnnotationScanTask(fragment, 
handlesTypesOnly, javaClassCache);
+                futures.add(pool.submit(task));
+            }
+            try {
+                for (Future<?> future : futures) {
+                    future.get();
+                }
+            } catch (Exception e) {
+                throw new 
RuntimeException(sm.getString("contextConfig.processAnnotationsInParallelFailure"),
 e);
+            }
+        } finally {
+            if (pool != null) {
+                pool.shutdownNow();
+            }
         }
     }
 
diff --git a/java/org/apache/catalina/startup/FailedContext.java 
b/java/org/apache/catalina/startup/FailedContext.java
index 9a93207..84b12f5 100644
--- a/java/org/apache/catalina/startup/FailedContext.java
+++ b/java/org/apache/catalina/startup/FailedContext.java
@@ -831,4 +831,9 @@ public class FailedContext extends LifecycleMBeanBase 
implements Context {
     @Override
     public void setDispatcherWrapsSameObject(boolean 
dispatcherWrapsSameObject) {}
 
+    @Override
+    public boolean isParallelAnnotationScanning() { return false; }
+    @Override
+    public void setParallelAnnotationScanning(boolean 
parallelAnnotationScanning) {}
+
 }
\ No newline at end of file
diff --git a/java/org/apache/catalina/startup/LocalStrings.properties 
b/java/org/apache/catalina/startup/LocalStrings.properties
index 5ea0c61..6d503e1 100644
--- a/java/org/apache/catalina/startup/LocalStrings.properties
+++ b/java/org/apache/catalina/startup/LocalStrings.properties
@@ -72,6 +72,7 @@ contextConfig.noAntiLocking=The value [{0}] configured for 
java.io.tmpdir does n
 contextConfig.processAnnotationsDir.debug=Scanning directory for class files 
with annotations [{0}]
 contextConfig.processAnnotationsJar.debug=Scanning jar file for class files 
with annotations [{0}]
 contextConfig.processAnnotationsWebDir.debug=Scanning web application 
directory for class files with annotations [{0}]
+contextConfig.processAnnotationsInParallelFailure=Parallel execution failed
 contextConfig.resourceJarFail=Failed to process JAR found at URL [{0}] for 
static resources to be included in context with name [{1}]
 contextConfig.role.auth=Security role name [{0}] used in an <auth-constraint> 
without being defined in a <security-role>
 contextConfig.role.link=Security role name [{0}] used in a <role-link> without 
being defined in a <security-role>
diff --git a/test/org/apache/tomcat/unittest/TesterContext.java 
b/test/org/apache/tomcat/unittest/TesterContext.java
index ebee177..33b1355 100644
--- a/test/org/apache/tomcat/unittest/TesterContext.java
+++ b/test/org/apache/tomcat/unittest/TesterContext.java
@@ -1290,4 +1290,9 @@ public class TesterContext implements Context {
     @Override
     public void setDispatcherWrapsSameObject(boolean 
dispatcherWrapsSameObject) {}
 
+    @Override
+    public boolean isParallelAnnotationScanning() { return false; }
+    @Override
+    public void setParallelAnnotationScanning(boolean 
parallelAnnotationScanning) {}
+
 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 95b172a..f1d7a76 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -67,6 +67,10 @@
         Authentication implementation. Patch provided by Robert Rodewald.
         (markt)
       </fix>
+      <update>
+        Allow using the utility executor for annotation scanning. Patch
+        provided by Jatin Kamnani. (remm)
+      </update>
     </changelog>
   </subsection>
   <subsection name="Coyote">
diff --git a/webapps/docs/config/context.xml b/webapps/docs/config/context.xml
index 04f28ca..2eaf349 100644
--- a/webapps/docs/config/context.xml
+++ b/webapps/docs/config/context.xml
@@ -457,6 +457,14 @@
         the same attribute explicitly for the Context.</p>
       </attribute>
 
+      <attribute name="parallelAnnotationScanning" required="false">
+        <p>When set to <code>true</code> annotation scanning will be performed
+        using the utility executor. It will allow processing scanning in
+        parrallel which may improve deployment type at the expense of higher
+        server load. If not specified, the default of <code>false</code> is
+        used.</p>
+      </attribute>
+
       <attribute name="path" required="false">
         <p>The <em>context path</em> of this web application, which is
         matched against the beginning of each request URI to select the


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

Reply via email to