You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2024/10/25 20:54:26 UTC

(tomcat) branch main updated (4853583722 -> 3b16fb8c14)

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

markt pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git


    from 4853583722 Create HttpParser earlier
     new 93c12d4add Remove unused code
     new 3b16fb8c14 Cache class loader non-found resources

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 java/org/apache/catalina/core/StandardContext.java |  13 ++
 .../catalina/loader/WebappClassLoaderBase.java     | 184 ++++++++++-----------
 .../util/collections/ConcurrentLruCache.java       | 119 +++++++++++++
 .../util/collections/TestConcurrentLruCache.java   |  96 +++++++++++
 webapps/docs/config/context.xml                    |  11 ++
 5 files changed, 327 insertions(+), 96 deletions(-)
 create mode 100644 java/org/apache/tomcat/util/collections/ConcurrentLruCache.java
 create mode 100644 test/org/apache/tomcat/util/collections/TestConcurrentLruCache.java


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


Re: (tomcat) 02/02: Cache class loader non-found resources

Posted by Rémy Maucherat <re...@apache.org>.
On Fri, Oct 25, 2024 at 10:55 PM <ma...@apache.org> wrote:
>
> This is an automated email from the ASF dual-hosted git repository.
>
> markt pushed a commit to branch main
> in repository https://gitbox.apache.org/repos/asf/tomcat.git
>
> commit 3b16fb8c1444e7ce76fb33bcd38378a4bb987c23
> Author: Mark Thomas <ma...@apache.org>
> AuthorDate: Fri Oct 25 21:54:12 2024 +0100
>
>     Cache class loader non-found resources
>
>     Some components (e.g. DriverManager) will attempt to load the same class
>     over and over again even if it isn't found the first time. If the web
>     application is large, this search can take a noticeable amount of time.
>     This cache avoids those issues. It can be disabled if not wanted /
>     required.

I know that, this existed a long time ago:
https://github.com/apache/tomcat/blob/7.0.x/java/org/apache/catalina/loader/WebappClassLoaderBase.java#L341
It was quite significant then since everything was much slower.

Rémy

> ---
>  java/org/apache/catalina/core/StandardContext.java |  13 ++
>  .../catalina/loader/WebappClassLoaderBase.java     | 139 +++++++++++++--------
>  .../util/collections/ConcurrentLruCache.java       | 119 ++++++++++++++++++
>  .../util/collections/TestConcurrentLruCache.java   |  96 ++++++++++++++
>  webapps/docs/config/context.xml                    |  11 ++
>  5 files changed, 327 insertions(+), 51 deletions(-)
>
> diff --git a/java/org/apache/catalina/core/StandardContext.java b/java/org/apache/catalina/core/StandardContext.java
> index f4fb6f8bf0..2994082059 100644
> --- a/java/org/apache/catalina/core/StandardContext.java
> +++ b/java/org/apache/catalina/core/StandardContext.java
> @@ -798,9 +798,21 @@ public class StandardContext extends ContainerBase implements Context, Notificat
>
>      private boolean parallelAnnotationScanning = false;
>
> +    private int notFoundClassResourceCacheSize = 1000;
> +
>
>      // ----------------------------------------------------- Context Properties
>
> +    public int getNotFoundClassResourceCacheSize() {
> +        return notFoundClassResourceCacheSize;
> +    }
> +
> +
> +    public void setNotFoundClassResourceCacheSize(int notFoundClassResourceCacheSize) {
> +        this.notFoundClassResourceCacheSize = notFoundClassResourceCacheSize;
> +    }
> +
> +
>      @Override
>      public void setCreateUploadTargets(boolean createUploadTargets) {
>          this.createUploadTargets = createUploadTargets;
> @@ -4258,6 +4270,7 @@ public class StandardContext extends ContainerBase implements Context, Notificat
>                      cl.setClearReferencesHttpClientKeepAliveThread(getClearReferencesHttpClientKeepAliveThread());
>                      cl.setClearReferencesThreadLocals(getClearReferencesThreadLocals());
>                      cl.setSkipMemoryLeakChecksOnJvmShutdown(getSkipMemoryLeakChecksOnJvmShutdown());
> +                    cl.setNotFoundClassResourceCacheSize(getNotFoundClassResourceCacheSize());
>                  }
>
>                  // By calling unbindThread and bindThread in a row, we setup the
> diff --git a/java/org/apache/catalina/loader/WebappClassLoaderBase.java b/java/org/apache/catalina/loader/WebappClassLoaderBase.java
> index eb41edfeeb..2164378b47 100644
> --- a/java/org/apache/catalina/loader/WebappClassLoaderBase.java
> +++ b/java/org/apache/catalina/loader/WebappClassLoaderBase.java
> @@ -67,6 +67,7 @@ import org.apache.juli.logging.LogFactory;
>  import org.apache.tomcat.InstrumentableClassLoader;
>  import org.apache.tomcat.util.ExceptionUtils;
>  import org.apache.tomcat.util.IntrospectionUtils;
> +import org.apache.tomcat.util.collections.ConcurrentLruCache;
>  import org.apache.tomcat.util.compat.JreCompat;
>  import org.apache.tomcat.util.res.StringManager;
>  import org.apache.tomcat.util.threads.ThreadPoolExecutor;
> @@ -311,9 +312,26 @@ public abstract class WebappClassLoaderBase extends URLClassLoader
>
>      private volatile LifecycleState state = LifecycleState.NEW;
>
> +    /*
> +     * Class resources are not cached since they are loaded on first use and the resource is then no longer required.
> +     * It does help, however, to cache classes that are not found as in some scenarios the same class will be searched
> +     * for many times and the greater the number of JARs/classes, the longer that lookup will take.
> +     */
> +    private final ConcurrentLruCache<String> notFoundClassResources = new ConcurrentLruCache<>(1000);
> +
>
>      // ------------------------------------------------------------- Properties
>
> +    public void setNotFoundClassResourceCacheSize(int notFoundClassResourceCacheSize) {
> +        notFoundClassResources.setLimit(notFoundClassResourceCacheSize);
> +    }
> +
> +
> +    public int getNotFoundClassResourceCacheSize() {
> +        return notFoundClassResources.getLimit();
> +    }
> +
> +
>      /**
>       * Set associated resources.
>       *
> @@ -684,14 +702,20 @@ public abstract class WebappClassLoaderBase extends URLClassLoader
>
>          String path = nameToPath(name);
>
> -        WebResource resource = resources.getClassLoaderResource(path);
> -        if (resource.exists()) {
> -            url = resource.getURL();
> -            trackLastModified(path, resource);
> -        }
> +        if (!notFoundClassResources.contains(path)) {
> +            WebResource resource = resources.getClassLoaderResource(path);
> +            if (resource.exists()) {
> +                url = resource.getURL();
> +                trackLastModified(path, resource);
> +            }
> +
> +            if (url == null && hasExternalRepositories) {
> +                url = super.findResource(name);
> +            }
>
> -        if (url == null && hasExternalRepositories) {
> -            url = super.findResource(name);
> +            if (url == null) {
> +                notFoundClassResources.add(path);
> +            }
>          }
>
>          if (log.isTraceEnabled()) {
> @@ -878,60 +902,64 @@ public abstract class WebappClassLoaderBase extends URLClassLoader
>              log.trace("  Searching local repositories");
>          }
>          String path = nameToPath(name);
> -        WebResource resource = resources.getClassLoaderResource(path);
> -        if (resource.exists()) {
> -            stream = resource.getInputStream();
> -            // Filter out .class resources through the ClassFileTranformer
> -            if (name.endsWith(CLASS_FILE_SUFFIX) && transformers.size() > 0) {
> -                // If the resource is a class, decorate it with any attached transformers
> -                ByteArrayOutputStream baos = new ByteArrayOutputStream();
> -                byte[] buf = new byte[8192];
> -                int numRead;
> -                try {
> -                    while ((numRead = stream.read(buf)) >= 0) {
> -                        baos.write(buf, 0, numRead);
> -                    }
> -                } catch (IOException e) {
> -                    log.error(sm.getString("webappClassLoader.transformError", name), e);
> -                    return null;
> -                } finally {
> -                    try {
> -                        stream.close();
> -                    } catch (IOException e) {
> -                    }
> -                }
> -                byte[] binaryContent = baos.toByteArray();
> -                String internalName = path.substring(1, path.length() - CLASS_FILE_SUFFIX.length());
> -                for (ClassFileTransformer transformer : this.transformers) {
> +        if (!notFoundClassResources.contains(path)) {
> +            WebResource resource = resources.getClassLoaderResource(path);
> +            if (resource.exists()) {
> +                stream = resource.getInputStream();
> +                // Filter out .class resources through the ClassFileTranformer
> +                if (name.endsWith(CLASS_FILE_SUFFIX) && transformers.size() > 0) {
> +                    // If the resource is a class, decorate it with any attached transformers
> +                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
> +                    byte[] buf = new byte[8192];
> +                    int numRead;
>                      try {
> -                        byte[] transformed = transformer.transform(this, internalName, null, null, binaryContent);
> -                        if (transformed != null) {
> -                            binaryContent = transformed;
> +                        while ((numRead = stream.read(buf)) >= 0) {
> +                            baos.write(buf, 0, numRead);
>                          }
> -                    } catch (IllegalClassFormatException e) {
> +                    } catch (IOException e) {
>                          log.error(sm.getString("webappClassLoader.transformError", name), e);
>                          return null;
> +                    } finally {
> +                        try {
> +                            stream.close();
> +                        } catch (IOException e) {
> +                        }
> +                    }
> +                    byte[] binaryContent = baos.toByteArray();
> +                    String internalName = path.substring(1, path.length() - CLASS_FILE_SUFFIX.length());
> +                    for (ClassFileTransformer transformer : this.transformers) {
> +                        try {
> +                            byte[] transformed = transformer.transform(this, internalName, null, null, binaryContent);
> +                            if (transformed != null) {
> +                                binaryContent = transformed;
> +                            }
> +                        } catch (IllegalClassFormatException e) {
> +                            log.error(sm.getString("webappClassLoader.transformError", name), e);
> +                            return null;
> +                        }
>                      }
> +                    stream = new ByteArrayInputStream(binaryContent);
>                  }
> -                stream = new ByteArrayInputStream(binaryContent);
> +                trackLastModified(path, resource);
>              }
> -            trackLastModified(path, resource);
> -        }
> -        try {
> -            if (hasExternalRepositories && stream == null) {
> -                URL url = super.findResource(name);
> -                if (url != null) {
> -                    stream = url.openStream();
> +            try {
> +                if (hasExternalRepositories && stream == null) {
> +                    URL url = super.findResource(name);
> +                    if (url != null) {
> +                        stream = url.openStream();
> +                    }
>                  }
> +            } catch (IOException e) {
> +                // Ignore
>              }
> -        } catch (IOException e) {
> -            // Ignore
> -        }
> -        if (stream != null) {
> -            if (log.isTraceEnabled()) {
> -                log.trace("  --> Returning stream from local");
> +            if (stream != null) {
> +                if (log.isTraceEnabled()) {
> +                    log.trace("  --> Returning stream from local");
> +                }
> +                return stream;
>              }
> -            return stream;
> +
> +            notFoundClassResources.add(path);
>          }
>
>          // (3) Delegate to parent unconditionally
> @@ -1263,6 +1291,7 @@ public abstract class WebappClassLoaderBase extends URLClassLoader
>          state = LifecycleState.STOPPING;
>
>          resourceEntries.clear();
> +        notFoundClassResources.clear();
>          jarModificationTimes.clear();
>          resources = null;
>
> @@ -1964,9 +1993,13 @@ public abstract class WebappClassLoaderBase extends URLClassLoader
>          WebResource resource = null;
>
>          if (entry == null) {
> +            if (notFoundClassResources.contains(path)) {
> +                return null;
> +            }
>              resource = resources.getClassLoaderResource(path);
>
>              if (!resource.exists()) {
> +                notFoundClassResources.add(path);
>                  return null;
>              }
>
> @@ -1999,10 +2032,14 @@ public abstract class WebappClassLoaderBase extends URLClassLoader
>              }
>
>              if (resource == null) {
> +                if (notFoundClassResources.contains(path)) {
> +                    return null;
> +                }
>                  resource = resources.getClassLoaderResource(path);
>              }
>
>              if (!resource.exists()) {
> +                notFoundClassResources.add(path);
>                  return null;
>              }
>
> diff --git a/java/org/apache/tomcat/util/collections/ConcurrentLruCache.java b/java/org/apache/tomcat/util/collections/ConcurrentLruCache.java
> new file mode 100644
> index 0000000000..51e29b969c
> --- /dev/null
> +++ b/java/org/apache/tomcat/util/collections/ConcurrentLruCache.java
> @@ -0,0 +1,119 @@
> +/*
> + * 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.tomcat.util.collections;
> +
> +import java.util.LinkedHashMap;
> +import java.util.Map;
> +
> +public class ConcurrentLruCache<T> {
> +
> +    private volatile LimitedLinkedHashMap<T,T> map;
> +    private final Object lock = new Object();
> +
> +    public ConcurrentLruCache(int limit) {
> +        setLimit(limit);
> +    }
> +
> +
> +    public void add(T entry) {
> +        if (map == null) {
> +            return;
> +        }
> +        synchronized (lock) {
> +            if (map == null) {
> +                return;
> +            }
> +            map.put(entry, entry);
> +        }
> +    }
> +
> +
> +    public boolean contains(T entry) {
> +        if (map == null) {
> +            return false;
> +        }
> +        synchronized (lock) {
> +            if (map == null) {
> +                return false;
> +            }
> +            return map.get(entry) != null;
> +        }
> +    }
> +
> +
> +    public void clear() {
> +        if (map == null) {
> +            return;
> +        }
> +        synchronized (lock) {
> +            if (map == null) {
> +                return;
> +            }
> +            map.clear();
> +        }
> +    }
> +
> +
> +    public void setLimit(int limit) {
> +        synchronized (lock) {
> +            if (limit > 0) {
> +                Map<T,T> oldMap = map;
> +                map = new LimitedLinkedHashMap<>(limit);
> +                if (oldMap != null) {
> +                    map.putAll(oldMap);
> +                }
> +           } else {
> +                map = null;
> +            }
> +        }
> +    }
> +
> +
> +    public int getLimit() {
> +        synchronized (lock) {
> +            if (map == null) {
> +                return -1;
> +            } else {
> +                return map.getLimit();
> +            }
> +        }
> +    }
> +
> +
> +    private static class LimitedLinkedHashMap<K,V> extends LinkedHashMap<K,V> {
> +        private static final long serialVersionUID = 1L;
> +
> +        private volatile int limit;
> +
> +        LimitedLinkedHashMap(int limit) {
> +            super(16, 0.75F, true);
> +            this.limit = limit;
> +        }
> +
> +        @Override
> +        protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
> +            if (size() > limit) {
> +                return true;
> +            }
> +            return false;
> +        }
> +
> +        private int getLimit() {
> +            return limit;
> +        }
> +    }
> +}
> diff --git a/test/org/apache/tomcat/util/collections/TestConcurrentLruCache.java b/test/org/apache/tomcat/util/collections/TestConcurrentLruCache.java
> new file mode 100644
> index 0000000000..94fa447903
> --- /dev/null
> +++ b/test/org/apache/tomcat/util/collections/TestConcurrentLruCache.java
> @@ -0,0 +1,96 @@
> +/*
> + * 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.tomcat.util.collections;
> +
> +import org.junit.Assert;
> +import org.junit.Test;
> +
> +public class TestConcurrentLruCache {
> +
> +    @Test
> +    public void testLimit() {
> +        ConcurrentLruCache<String> cache = getStartingCache();
> +
> +        cache.add("D");
> +
> +        Assert.assertFalse(cache.contains("A"));
> +        Assert.assertTrue(cache.contains("B"));
> +        Assert.assertTrue(cache.contains("C"));
> +        Assert.assertTrue(cache.contains("D"));
> +    }
> +
> +
> +    @Test
> +    public void testLRU() {
> +        ConcurrentLruCache<String> cache = getStartingCache();
> +
> +        cache.contains("A");
> +        cache.add("D");
> +
> +        Assert.assertTrue(cache.contains("A"));
> +        Assert.assertFalse(cache.contains("B"));
> +        Assert.assertTrue(cache.contains("C"));
> +        Assert.assertTrue(cache.contains("D"));
> +    }
> +
> +
> +    @Test
> +    public void testIncreaseLimit() {
> +        ConcurrentLruCache<String> cache = getStartingCache();
> +
> +        cache.setLimit(4);
> +
> +        cache.add("D");
> +
> +        Assert.assertTrue(cache.contains("A"));
> +        Assert.assertTrue(cache.contains("B"));
> +        Assert.assertTrue(cache.contains("C"));
> +        Assert.assertTrue(cache.contains("D"));
> +
> +        cache.add("E");
> +
> +        Assert.assertFalse(cache.contains("A"));
> +        Assert.assertTrue(cache.contains("B"));
> +        Assert.assertTrue(cache.contains("C"));
> +        Assert.assertTrue(cache.contains("D"));
> +        Assert.assertTrue(cache.contains("E"));
> +    }
> +
> +
> +    @Test
> +    public void testReduceLimit() {
> +        ConcurrentLruCache<String> cache = getStartingCache();
> +
> +        cache.contains("A");
> +        cache.setLimit(2);
> +
> +        Assert.assertTrue(cache.contains("A"));
> +        Assert.assertFalse(cache.contains("B"));
> +        Assert.assertTrue(cache.contains("C"));
> +    }
> +
> +
> +    private ConcurrentLruCache<String> getStartingCache() {
> +        ConcurrentLruCache<String> cache = new ConcurrentLruCache<>(3);
> +
> +        cache.add("A");
> +        cache.add("B");
> +        cache.add("C");
> +
> +        return cache;
> +    }
> +}
> diff --git a/webapps/docs/config/context.xml b/webapps/docs/config/context.xml
> index 9e59e39bb9..b141edc8dc 100644
> --- a/webapps/docs/config/context.xml
> +++ b/webapps/docs/config/context.xml
> @@ -870,6 +870,17 @@
>          of <code>true</code> will be used.</p>
>        </attribute>
>
> +      <attribute name="notFoundClassResourceCacheSize" required="false">
> +        <p>Class resources are not cached by the standard resources
> +        implementation since they are loaded on first use and then the resource
> +        is then no longer required. It does help, however, to cache classes that
> +        are not found as in some scenarios the same class will be searched for
> +        many times and the greater the number of JARs/classes, the longer that
> +        search will take. An LRU cache is used and this attribute controls the
> +        size of that cache. A value of less than 1 disables the cache. If not
> +        specified, the default value of 1000 will be used.</p>
> +      </attribute>
> +
>        <attribute name="renewThreadsWhenStoppingContext" required="false">
>          <p>If <code>true</code>, when this context is stopped, Tomcat renews all
>          the threads from the thread pool that was used to serve this context.
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
> For additional commands, e-mail: dev-help@tomcat.apache.org
>

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


(tomcat) 02/02: Cache class loader non-found resources

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 3b16fb8c1444e7ce76fb33bcd38378a4bb987c23
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Fri Oct 25 21:54:12 2024 +0100

    Cache class loader non-found resources
    
    Some components (e.g. DriverManager) will attempt to load the same class
    over and over again even if it isn't found the first time. If the web
    application is large, this search can take a noticeable amount of time.
    This cache avoids those issues. It can be disabled if not wanted /
    required.
---
 java/org/apache/catalina/core/StandardContext.java |  13 ++
 .../catalina/loader/WebappClassLoaderBase.java     | 139 +++++++++++++--------
 .../util/collections/ConcurrentLruCache.java       | 119 ++++++++++++++++++
 .../util/collections/TestConcurrentLruCache.java   |  96 ++++++++++++++
 webapps/docs/config/context.xml                    |  11 ++
 5 files changed, 327 insertions(+), 51 deletions(-)

diff --git a/java/org/apache/catalina/core/StandardContext.java b/java/org/apache/catalina/core/StandardContext.java
index f4fb6f8bf0..2994082059 100644
--- a/java/org/apache/catalina/core/StandardContext.java
+++ b/java/org/apache/catalina/core/StandardContext.java
@@ -798,9 +798,21 @@ public class StandardContext extends ContainerBase implements Context, Notificat
 
     private boolean parallelAnnotationScanning = false;
 
+    private int notFoundClassResourceCacheSize = 1000;
+
 
     // ----------------------------------------------------- Context Properties
 
+    public int getNotFoundClassResourceCacheSize() {
+        return notFoundClassResourceCacheSize;
+    }
+
+
+    public void setNotFoundClassResourceCacheSize(int notFoundClassResourceCacheSize) {
+        this.notFoundClassResourceCacheSize = notFoundClassResourceCacheSize;
+    }
+
+
     @Override
     public void setCreateUploadTargets(boolean createUploadTargets) {
         this.createUploadTargets = createUploadTargets;
@@ -4258,6 +4270,7 @@ public class StandardContext extends ContainerBase implements Context, Notificat
                     cl.setClearReferencesHttpClientKeepAliveThread(getClearReferencesHttpClientKeepAliveThread());
                     cl.setClearReferencesThreadLocals(getClearReferencesThreadLocals());
                     cl.setSkipMemoryLeakChecksOnJvmShutdown(getSkipMemoryLeakChecksOnJvmShutdown());
+                    cl.setNotFoundClassResourceCacheSize(getNotFoundClassResourceCacheSize());
                 }
 
                 // By calling unbindThread and bindThread in a row, we setup the
diff --git a/java/org/apache/catalina/loader/WebappClassLoaderBase.java b/java/org/apache/catalina/loader/WebappClassLoaderBase.java
index eb41edfeeb..2164378b47 100644
--- a/java/org/apache/catalina/loader/WebappClassLoaderBase.java
+++ b/java/org/apache/catalina/loader/WebappClassLoaderBase.java
@@ -67,6 +67,7 @@ import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.InstrumentableClassLoader;
 import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.IntrospectionUtils;
+import org.apache.tomcat.util.collections.ConcurrentLruCache;
 import org.apache.tomcat.util.compat.JreCompat;
 import org.apache.tomcat.util.res.StringManager;
 import org.apache.tomcat.util.threads.ThreadPoolExecutor;
@@ -311,9 +312,26 @@ public abstract class WebappClassLoaderBase extends URLClassLoader
 
     private volatile LifecycleState state = LifecycleState.NEW;
 
+    /*
+     * Class resources are not cached since they are loaded on first use and the resource is then no longer required.
+     * It does help, however, to cache classes that are not found as in some scenarios the same class will be searched
+     * for many times and the greater the number of JARs/classes, the longer that lookup will take.
+     */
+    private final ConcurrentLruCache<String> notFoundClassResources = new ConcurrentLruCache<>(1000);
+
 
     // ------------------------------------------------------------- Properties
 
+    public void setNotFoundClassResourceCacheSize(int notFoundClassResourceCacheSize) {
+        notFoundClassResources.setLimit(notFoundClassResourceCacheSize);
+    }
+
+
+    public int getNotFoundClassResourceCacheSize() {
+        return notFoundClassResources.getLimit();
+    }
+
+
     /**
      * Set associated resources.
      *
@@ -684,14 +702,20 @@ public abstract class WebappClassLoaderBase extends URLClassLoader
 
         String path = nameToPath(name);
 
-        WebResource resource = resources.getClassLoaderResource(path);
-        if (resource.exists()) {
-            url = resource.getURL();
-            trackLastModified(path, resource);
-        }
+        if (!notFoundClassResources.contains(path)) {
+            WebResource resource = resources.getClassLoaderResource(path);
+            if (resource.exists()) {
+                url = resource.getURL();
+                trackLastModified(path, resource);
+            }
+
+            if (url == null && hasExternalRepositories) {
+                url = super.findResource(name);
+            }
 
-        if (url == null && hasExternalRepositories) {
-            url = super.findResource(name);
+            if (url == null) {
+                notFoundClassResources.add(path);
+            }
         }
 
         if (log.isTraceEnabled()) {
@@ -878,60 +902,64 @@ public abstract class WebappClassLoaderBase extends URLClassLoader
             log.trace("  Searching local repositories");
         }
         String path = nameToPath(name);
-        WebResource resource = resources.getClassLoaderResource(path);
-        if (resource.exists()) {
-            stream = resource.getInputStream();
-            // Filter out .class resources through the ClassFileTranformer
-            if (name.endsWith(CLASS_FILE_SUFFIX) && transformers.size() > 0) {
-                // If the resource is a class, decorate it with any attached transformers
-                ByteArrayOutputStream baos = new ByteArrayOutputStream();
-                byte[] buf = new byte[8192];
-                int numRead;
-                try {
-                    while ((numRead = stream.read(buf)) >= 0) {
-                        baos.write(buf, 0, numRead);
-                    }
-                } catch (IOException e) {
-                    log.error(sm.getString("webappClassLoader.transformError", name), e);
-                    return null;
-                } finally {
-                    try {
-                        stream.close();
-                    } catch (IOException e) {
-                    }
-                }
-                byte[] binaryContent = baos.toByteArray();
-                String internalName = path.substring(1, path.length() - CLASS_FILE_SUFFIX.length());
-                for (ClassFileTransformer transformer : this.transformers) {
+        if (!notFoundClassResources.contains(path)) {
+            WebResource resource = resources.getClassLoaderResource(path);
+            if (resource.exists()) {
+                stream = resource.getInputStream();
+                // Filter out .class resources through the ClassFileTranformer
+                if (name.endsWith(CLASS_FILE_SUFFIX) && transformers.size() > 0) {
+                    // If the resource is a class, decorate it with any attached transformers
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    byte[] buf = new byte[8192];
+                    int numRead;
                     try {
-                        byte[] transformed = transformer.transform(this, internalName, null, null, binaryContent);
-                        if (transformed != null) {
-                            binaryContent = transformed;
+                        while ((numRead = stream.read(buf)) >= 0) {
+                            baos.write(buf, 0, numRead);
                         }
-                    } catch (IllegalClassFormatException e) {
+                    } catch (IOException e) {
                         log.error(sm.getString("webappClassLoader.transformError", name), e);
                         return null;
+                    } finally {
+                        try {
+                            stream.close();
+                        } catch (IOException e) {
+                        }
+                    }
+                    byte[] binaryContent = baos.toByteArray();
+                    String internalName = path.substring(1, path.length() - CLASS_FILE_SUFFIX.length());
+                    for (ClassFileTransformer transformer : this.transformers) {
+                        try {
+                            byte[] transformed = transformer.transform(this, internalName, null, null, binaryContent);
+                            if (transformed != null) {
+                                binaryContent = transformed;
+                            }
+                        } catch (IllegalClassFormatException e) {
+                            log.error(sm.getString("webappClassLoader.transformError", name), e);
+                            return null;
+                        }
                     }
+                    stream = new ByteArrayInputStream(binaryContent);
                 }
-                stream = new ByteArrayInputStream(binaryContent);
+                trackLastModified(path, resource);
             }
-            trackLastModified(path, resource);
-        }
-        try {
-            if (hasExternalRepositories && stream == null) {
-                URL url = super.findResource(name);
-                if (url != null) {
-                    stream = url.openStream();
+            try {
+                if (hasExternalRepositories && stream == null) {
+                    URL url = super.findResource(name);
+                    if (url != null) {
+                        stream = url.openStream();
+                    }
                 }
+            } catch (IOException e) {
+                // Ignore
             }
-        } catch (IOException e) {
-            // Ignore
-        }
-        if (stream != null) {
-            if (log.isTraceEnabled()) {
-                log.trace("  --> Returning stream from local");
+            if (stream != null) {
+                if (log.isTraceEnabled()) {
+                    log.trace("  --> Returning stream from local");
+                }
+                return stream;
             }
-            return stream;
+
+            notFoundClassResources.add(path);
         }
 
         // (3) Delegate to parent unconditionally
@@ -1263,6 +1291,7 @@ public abstract class WebappClassLoaderBase extends URLClassLoader
         state = LifecycleState.STOPPING;
 
         resourceEntries.clear();
+        notFoundClassResources.clear();
         jarModificationTimes.clear();
         resources = null;
 
@@ -1964,9 +1993,13 @@ public abstract class WebappClassLoaderBase extends URLClassLoader
         WebResource resource = null;
 
         if (entry == null) {
+            if (notFoundClassResources.contains(path)) {
+                return null;
+            }
             resource = resources.getClassLoaderResource(path);
 
             if (!resource.exists()) {
+                notFoundClassResources.add(path);
                 return null;
             }
 
@@ -1999,10 +2032,14 @@ public abstract class WebappClassLoaderBase extends URLClassLoader
             }
 
             if (resource == null) {
+                if (notFoundClassResources.contains(path)) {
+                    return null;
+                }
                 resource = resources.getClassLoaderResource(path);
             }
 
             if (!resource.exists()) {
+                notFoundClassResources.add(path);
                 return null;
             }
 
diff --git a/java/org/apache/tomcat/util/collections/ConcurrentLruCache.java b/java/org/apache/tomcat/util/collections/ConcurrentLruCache.java
new file mode 100644
index 0000000000..51e29b969c
--- /dev/null
+++ b/java/org/apache/tomcat/util/collections/ConcurrentLruCache.java
@@ -0,0 +1,119 @@
+/*
+ * 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.tomcat.util.collections;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class ConcurrentLruCache<T> {
+
+    private volatile LimitedLinkedHashMap<T,T> map;
+    private final Object lock = new Object();
+
+    public ConcurrentLruCache(int limit) {
+        setLimit(limit);
+    }
+
+
+    public void add(T entry) {
+        if (map == null) {
+            return;
+        }
+        synchronized (lock) {
+            if (map == null) {
+                return;
+            }
+            map.put(entry, entry);
+        }
+    }
+
+
+    public boolean contains(T entry) {
+        if (map == null) {
+            return false;
+        }
+        synchronized (lock) {
+            if (map == null) {
+                return false;
+            }
+            return map.get(entry) != null;
+        }
+    }
+
+
+    public void clear() {
+        if (map == null) {
+            return;
+        }
+        synchronized (lock) {
+            if (map == null) {
+                return;
+            }
+            map.clear();
+        }
+    }
+
+
+    public void setLimit(int limit) {
+        synchronized (lock) {
+            if (limit > 0) {
+                Map<T,T> oldMap = map;
+                map = new LimitedLinkedHashMap<>(limit);
+                if (oldMap != null) {
+                    map.putAll(oldMap);
+                }
+           } else {
+                map = null;
+            }
+        }
+    }
+
+
+    public int getLimit() {
+        synchronized (lock) {
+            if (map == null) {
+                return -1;
+            } else {
+                return map.getLimit();
+            }
+        }
+    }
+
+
+    private static class LimitedLinkedHashMap<K,V> extends LinkedHashMap<K,V> {
+        private static final long serialVersionUID = 1L;
+
+        private volatile int limit;
+
+        LimitedLinkedHashMap(int limit) {
+            super(16, 0.75F, true);
+            this.limit = limit;
+        }
+
+        @Override
+        protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
+            if (size() > limit) {
+                return true;
+            }
+            return false;
+        }
+
+        private int getLimit() {
+            return limit;
+        }
+    }
+}
diff --git a/test/org/apache/tomcat/util/collections/TestConcurrentLruCache.java b/test/org/apache/tomcat/util/collections/TestConcurrentLruCache.java
new file mode 100644
index 0000000000..94fa447903
--- /dev/null
+++ b/test/org/apache/tomcat/util/collections/TestConcurrentLruCache.java
@@ -0,0 +1,96 @@
+/*
+ * 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.tomcat.util.collections;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestConcurrentLruCache {
+
+    @Test
+    public void testLimit() {
+        ConcurrentLruCache<String> cache = getStartingCache();
+
+        cache.add("D");
+
+        Assert.assertFalse(cache.contains("A"));
+        Assert.assertTrue(cache.contains("B"));
+        Assert.assertTrue(cache.contains("C"));
+        Assert.assertTrue(cache.contains("D"));
+    }
+
+
+    @Test
+    public void testLRU() {
+        ConcurrentLruCache<String> cache = getStartingCache();
+
+        cache.contains("A");
+        cache.add("D");
+
+        Assert.assertTrue(cache.contains("A"));
+        Assert.assertFalse(cache.contains("B"));
+        Assert.assertTrue(cache.contains("C"));
+        Assert.assertTrue(cache.contains("D"));
+    }
+
+
+    @Test
+    public void testIncreaseLimit() {
+        ConcurrentLruCache<String> cache = getStartingCache();
+
+        cache.setLimit(4);
+
+        cache.add("D");
+
+        Assert.assertTrue(cache.contains("A"));
+        Assert.assertTrue(cache.contains("B"));
+        Assert.assertTrue(cache.contains("C"));
+        Assert.assertTrue(cache.contains("D"));
+
+        cache.add("E");
+
+        Assert.assertFalse(cache.contains("A"));
+        Assert.assertTrue(cache.contains("B"));
+        Assert.assertTrue(cache.contains("C"));
+        Assert.assertTrue(cache.contains("D"));
+        Assert.assertTrue(cache.contains("E"));
+    }
+
+
+    @Test
+    public void testReduceLimit() {
+        ConcurrentLruCache<String> cache = getStartingCache();
+
+        cache.contains("A");
+        cache.setLimit(2);
+
+        Assert.assertTrue(cache.contains("A"));
+        Assert.assertFalse(cache.contains("B"));
+        Assert.assertTrue(cache.contains("C"));
+    }
+
+
+    private ConcurrentLruCache<String> getStartingCache() {
+        ConcurrentLruCache<String> cache = new ConcurrentLruCache<>(3);
+
+        cache.add("A");
+        cache.add("B");
+        cache.add("C");
+
+        return cache;
+    }
+}
diff --git a/webapps/docs/config/context.xml b/webapps/docs/config/context.xml
index 9e59e39bb9..b141edc8dc 100644
--- a/webapps/docs/config/context.xml
+++ b/webapps/docs/config/context.xml
@@ -870,6 +870,17 @@
         of <code>true</code> will be used.</p>
       </attribute>
 
+      <attribute name="notFoundClassResourceCacheSize" required="false">
+        <p>Class resources are not cached by the standard resources
+        implementation since they are loaded on first use and then the resource
+        is then no longer required. It does help, however, to cache classes that
+        are not found as in some scenarios the same class will be searched for
+        many times and the greater the number of JARs/classes, the longer that
+        search will take. An LRU cache is used and this attribute controls the
+        size of that cache. A value of less than 1 disables the cache. If not
+        specified, the default value of 1000 will be used.</p>
+      </attribute>
+
       <attribute name="renewThreadsWhenStoppingContext" required="false">
         <p>If <code>true</code>, when this context is stopped, Tomcat renews all
         the threads from the thread pool that was used to serve this context.


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


(tomcat) 01/02: Remove unused code

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 93c12d4add7b7f68914287de8ba9dbd08c13fd69
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Thu Oct 10 16:58:39 2024 -0600

    Remove unused code
    
    This should have been removed when we removed SecurityManager support
---
 .../catalina/loader/WebappClassLoaderBase.java     | 45 ----------------------
 1 file changed, 45 deletions(-)

diff --git a/java/org/apache/catalina/loader/WebappClassLoaderBase.java b/java/org/apache/catalina/loader/WebappClassLoaderBase.java
index f6cfa08e4c..eb41edfeeb 100644
--- a/java/org/apache/catalina/loader/WebappClassLoaderBase.java
+++ b/java/org/apache/catalina/loader/WebappClassLoaderBase.java
@@ -31,7 +31,6 @@ import java.net.URLClassLoader;
 import java.security.CodeSource;
 import java.security.Permission;
 import java.security.PermissionCollection;
-import java.security.PrivilegedAction;
 import java.security.ProtectionDomain;
 import java.security.cert.Certificate;
 import java.util.ArrayList;
@@ -125,50 +124,6 @@ public abstract class WebappClassLoaderBase extends URLClassLoader
         JVM_THREAD_GROUP_NAMES.add("RMI Runtime");
     }
 
-    protected class PrivilegedFindClassByName implements PrivilegedAction<Class<?>> {
-
-        private final String name;
-
-        PrivilegedFindClassByName(String name) {
-            this.name = name;
-        }
-
-        @Override
-        public Class<?> run() {
-            return findClassInternal(name);
-        }
-    }
-
-
-    protected static final class PrivilegedGetClassLoader implements PrivilegedAction<ClassLoader> {
-
-        private final Class<?> clazz;
-
-        public PrivilegedGetClassLoader(Class<?> clazz) {
-            this.clazz = clazz;
-        }
-
-        @Override
-        public ClassLoader run() {
-            return clazz.getClassLoader();
-        }
-    }
-
-
-    protected final class PrivilegedJavaseGetResource implements PrivilegedAction<URL> {
-
-        private final String name;
-
-        public PrivilegedJavaseGetResource(String name) {
-            this.name = name;
-        }
-
-        @Override
-        public URL run() {
-            return javaseClassLoader.getResource(name);
-        }
-    }
-
 
     // ------------------------------------------------------- Static Variables
 


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