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

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


The following commit(s) were added to refs/heads/9.0.x by this push:
     new 101476c  Use utility executor for scanning
101476c is described below

commit 101476c3536ac09a105d0b603b2c51dd0f3770e9
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 |   6 ++
 .../catalina/startup/LocalStrings.properties       |   1 +
 test/org/apache/tomcat/unittest/TesterContext.java |   6 ++
 webapps/docs/changelog.xml                         |   4 +
 webapps/docs/config/context.xml                    |   8 ++
 9 files changed, 156 insertions(+), 22 deletions(-)

diff --git a/java/org/apache/catalina/Context.java 
b/java/org/apache/catalina/Context.java
index 3e647dc..5aac225 100644
--- a/java/org/apache/catalina/Context.java
+++ b/java/org/apache/catalina/Context.java
@@ -761,6 +761,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 12641d7..06f7621 100644
--- a/java/org/apache/catalina/core/StandardContext.java
+++ b/java/org/apache/catalina/core/StandardContext.java
@@ -828,6 +828,8 @@ public class StandardContext extends ContainerBase
     private boolean createUploadTargets = false;
 
 
+    private boolean parallelAnnotationScanning = false;
+
     // ----------------------------------------------------- Context Properties
 
     @Override
@@ -1404,6 +1406,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 d7b5a77..3dc1638 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 javax.servlet.MultipartConfigElement;
 import javax.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 39d7b8a..7919943 100644
--- a/java/org/apache/catalina/startup/FailedContext.java
+++ b/java/org/apache/catalina/startup/FailedContext.java
@@ -822,4 +822,10 @@ public class FailedContext extends LifecycleMBeanBase 
implements Context {
     public void setCreateUploadTargets(boolean createUploadTargets) { /* NO-OP 
*/}
     @Override
     public boolean getCreateUploadTargets() { return false; }
+
+    @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 c6d080a..d2cfe78 100644
--- a/test/org/apache/tomcat/unittest/TesterContext.java
+++ b/test/org/apache/tomcat/unittest/TesterContext.java
@@ -1288,4 +1288,10 @@ public class TesterContext implements Context {
     public void setCreateUploadTargets(boolean createUploadTargets) { /* NO-OP 
*/}
     @Override
     public boolean getCreateUploadTargets() { return false; }
+
+    @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 f3871b7..333f115 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -66,6 +66,10 @@
         <bug>64715</bug>: Add PasswordValidationCallback to the JASPIC
         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 e1ddd20..ea30612 100644
--- a/webapps/docs/config/context.xml
+++ b/webapps/docs/config/context.xml
@@ -427,6 +427,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