You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by re...@apache.org on 2003/04/21 20:15:22 UTC

cvs commit: jakarta-tomcat-catalina/catalina/src/share/org/apache/naming/resources ProxyDirContext.java

remm        2003/04/21 11:15:22

  Modified:    catalina/src/share/org/apache/naming/resources
                        ProxyDirContext.java
  Log:
  - Update cache algorithm (note: size limitation alg not tested yet).
  - Expose some cache statistics through JMX.
  - The cache may be become pluggable and configuration setters added.
  
  Revision  Changes    Path
  1.6       +305 -15   jakarta-tomcat-catalina/catalina/src/share/org/apache/naming/resources/ProxyDirContext.java
  
  Index: ProxyDirContext.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat-catalina/catalina/src/share/org/apache/naming/resources/ProxyDirContext.java,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- ProxyDirContext.java	16 Dec 2002 14:19:07 -0000	1.5
  +++ ProxyDirContext.java	21 Apr 2003 18:15:22 -0000	1.6
  @@ -66,8 +66,8 @@
   
   import java.util.Collections;
   import java.util.Date;
  +import java.util.HashMap;
   import java.util.Hashtable;
  -import java.util.Map;
   import java.io.InputStream;
   import java.io.IOException;
   import java.io.ByteArrayInputStream;
  @@ -86,8 +86,6 @@
   
   import org.apache.naming.StringManager;
   
  -import org.apache.commons.collections.LRUMap;
  -
   /**
    * Proxy Directory Context implementation.
    *
  @@ -103,6 +101,7 @@
   
       public static final String CONTEXT = "context";
       public static final String HOST = "host";
  +    public static final int INCREMENT = 32;
   
   
       // ----------------------------------------------------------- Constructors
  @@ -118,10 +117,11 @@
               // Initialize parameters based on the associated dir context, like
               // the caching policy.
               if (((BaseDirContext) dirContext).isCached()) {
  -                cache = Collections.synchronizedMap(new LRUMap(cacheSize));
                   cacheTTL = ((BaseDirContext) dirContext).getCacheTTL();
                   cacheObjectMaxSize = 
                       ((BaseDirContext) dirContext).getCacheObjectMaxSize();
  +                cache = new CacheEntry[0];
  +                notFoundCache = new ThreadLocal();
               }
           }
           hostName = (String) env.get(HOST);
  @@ -133,24 +133,35 @@
        * Builds a clone of this proxy dir context, wrapping the given directory
        * context, and sharing the same cache.
        */
  +    // TODO: Refactor using the proxy field
  +    /*
       protected ProxyDirContext(ProxyDirContext proxyDirContext, 
                                 DirContext dirContext, String vPath) {
           this.env = proxyDirContext.env;
           this.dirContext = dirContext;
           this.vPath = vPath;
           this.cache = proxyDirContext.cache;
  +        this.cacheMaxSize = proxyDirContext.cacheMaxSize;
           this.cacheSize = proxyDirContext.cacheSize;
           this.cacheTTL = proxyDirContext.cacheTTL;
           this.cacheObjectMaxSize = proxyDirContext.cacheObjectMaxSize;
  +        this.notFoundCache = proxyDirContext.notFoundCache;
           this.hostName = proxyDirContext.hostName;
           this.contextName = proxyDirContext.contextName;
       }
  +    */
   
   
       // ----------------------------------------------------- Instance Variables
   
   
       /**
  +     * Proxy DirContext (either this or the real proxy).
  +     */
  +    protected ProxyDirContext proxy = this;
  +
  +
  +    /**
        * Environment.
        */
       protected Hashtable env;
  @@ -190,13 +201,19 @@
        * Cache.
        * Path -> Cache entry.
        */
  -    protected Map cache = null;
  +    protected CacheEntry[] cache = null;
   
   
       /**
  -     * Cache size
  +     * Current cache size in KB.
        */
  -    protected int cacheSize = 1000;
  +    protected int cacheSize = 0;
  +
  +
  +    /**
  +     * Thread local cache for not found resources.
  +     */
  +    protected ThreadLocal notFoundCache = null;
   
   
       /**
  @@ -208,7 +225,44 @@
       /**
        * Max size of resources which will have their content cached.
        */
  -    protected int cacheObjectMaxSize = 32768; // 32 KB
  +    protected int cacheMaxSize = 10240; // 10 MB
  +
  +
  +    /**
  +     * Max size of resources which will have their content cached.
  +     */
  +    protected int cacheObjectMaxSize = cacheMaxSize / 20; // 512 KB
  +
  +
  +    /**
  +     * Max amount of removals during a make space.
  +     */
  +    protected int maxMakeSpaceIterations = 10;
  +
  +
  +    /**
  +     * Entry hit ratio at which an entry will never be removed from the cache.
  +     * Compared with entry.access / hitsCount
  +     */
  +    protected long desiredEntryAccessRatio = 3;
  +
  +
  +    /**
  +     * Spare amount of not found entries.
  +     */
  +    protected int spareNotFoundEntries = 10;
  +
  +
  +    /**
  +     * Number of accesses to the cache.
  +     */
  +    protected long accessCount = 0;
  +
  +
  +    /**
  +     * Number of cache hits.
  +     */
  +    protected long hitsCount = 0;
   
   
       /**
  @@ -256,6 +310,34 @@
       }
   
   
  +    /**
  +     * Return the access count.
  +     * Note: Update is not synced, so the number may not be completely 
  +     * accurate.
  +     */
  +    public long getAccessCount() {
  +        return accessCount;
  +    }
  +
  +
  +    /**
  +     * Return the number of cache hits.
  +     * Note: Update is not synced, so the number may not be completely 
  +     * accurate.
  +     */
  +    public long getHitsCount() {
  +        return hitsCount;
  +    }
  +
  +
  +    /**
  +     * Return the current cache size in KB.
  +     */
  +    public int getCacheSize() {
  +        return cacheSize;
  +    }
  +
  +
       // -------------------------------------------------------- Context Methods
   
   
  @@ -1404,7 +1486,20 @@
           throws NamingException {
           if (cache == null)
               return (null);
  -        CacheEntry cacheEntry = (CacheEntry) cache.get(name);
  +        CacheEntry cacheEntry = null;
  +        accessCount++;
  +        int pos = find(cache, name);
  +        if ((pos != -1) && (name.equals(cache[pos].name))) {
  +            cacheEntry = cache[pos];
  +        }
  +        if (cacheEntry == null) {
  +            HashMap localCache = (HashMap) notFoundCache.get();
  +            if (localCache == null) {
  +                notFoundCache.set(new HashMap());
  +            }
  +            cacheEntry = 
  +                (CacheEntry) ((HashMap) notFoundCache.get()).get(name);
  +        }
           if (cacheEntry == null) {
               cacheEntry = new CacheEntry();
               cacheEntry.name = name;
  @@ -1420,6 +1515,8 @@
                           System.currentTimeMillis() + cacheTTL;
                   }
               }
  +            cacheEntry.accessCount++;
  +            hitsCount++;
           }
           if (!cacheEntry.exists) {
               throw notFoundException;
  @@ -1525,6 +1622,9 @@
               && (entry.attributes.getContentLength() >= 0)
               && (entry.attributes.getContentLength() < cacheObjectMaxSize)) {
               int length = (int) entry.attributes.getContentLength();
  +            // The entry size is 1 + the resource size in KB, if it will be 
  +            // cached
  +            entry.size += (entry.attributes.getContentLength() / 1024);
               InputStream is = null;
               try {
                   is = entry.resource.streamContent();
  @@ -1556,7 +1656,26 @@
           entry.timestamp = System.currentTimeMillis() + cacheTTL;
   
           // Add new entry to cache
  -        cache.put(name, entry);
  +        synchronized (this) {
  +            // Check cache size, and remove elements if too big
  +            if (!makeSpace(entry.size)) {
  +                // Don't cache
  +                return;
  +            }
  +            if (exists) {
  +                CacheEntry[] newCache = new CacheEntry[cache.length + 1];
  +                if (insertMap(cache, newCache, entry)) {
  +                    cache = newCache;
  +                }
  +            } else {
  +                HashMap localCache = (HashMap) notFoundCache.get();
  +                if (localCache == null) {
  +                    notFoundCache.set(new HashMap());
  +                }
  +                ((HashMap) notFoundCache.get()).put(name, entry);
  +            }
  +            cacheSize += entry.size;
  +        }
   
       }
   
  @@ -1567,7 +1686,174 @@
       protected boolean cacheUnload(String name) {
           if (cache == null)
               return false;
  -        return (cache.remove(name) != null);
  +        synchronized (this) {
  +            CacheEntry[] newCache = new CacheEntry[cache.length + 1];
  +            CacheEntry removedEntry = removeMap(cache, newCache, name);
  +            if (removedEntry != null) {
  +                cache = newCache;
  +                cacheSize -= removedEntry.size;
  +                return true;
  +            } else if (((HashMap) notFoundCache.get()).remove(name) != null) {
  +                cacheSize--;
  +                return true;
  +            }
  +            return false;
  +        }
  +    }
  +
  +
  +    /**
  +     * Make space for a new entry.
  +     */
  +    protected boolean makeSpace(int space) {
  +
  +        int toFree = space - (cacheMaxSize - cacheSize);
  +
  +        if (toFree < 0) {
  +            return true;
  +        }
  +
  +        HashMap localNotFoundCache = (HashMap) notFoundCache.get();
  +        int size = localNotFoundCache.size();
  +        if (size > spareNotFoundEntries) {
  +            localNotFoundCache.clear();
  +            cacheSize -= size;
  +            toFree -= size;
  +        }
  +
  +        if (toFree < 0) {
  +            return true;
  +        }
  +
  +        int attempts = 0;
  +        long totalSpace = 0;
  +        int[] toRemove = new int[maxMakeSpaceIterations];
  +        while (toFree > 0) {
  +            if (attempts == maxMakeSpaceIterations) {
  +                // Give up, no changes are made to the current cache
  +                return false;
  +            }
  +            if (toFree > 0) {
  +                // Randomly select an entry in the array
  +                int entryPos = -1;
  +                while (true) {
  +                    entryPos = (int) Math.round(Math.random() 
  +                                                * (cache.length - 1));
  +                    // Guarantee uniqueness
  +                    for (int i = 0; i < attempts; i++) {
  +                        if (toRemove[i] == entryPos) {
  +                            continue;
  +                        }
  +                    }
  +                    break;
  +                }
  +                long entryAccessRatio = 
  +                    ((cache[entryPos].accessCount * 100) / accessCount);
  +                if (entryAccessRatio < desiredEntryAccessRatio) {
  +                    toRemove[attempts] = entryPos;
  +                    totalSpace += cache[entryPos].size;
  +                    toFree -= cache[entryPos].size;
  +                }
  +            }
  +            attempts++;
  +        }
  +
  +        // Now remove the selected entries
  +        java.util.Arrays.sort(toRemove, 0, attempts);
  +        CacheEntry[] newCache = new CacheEntry[cache.length - attempts];
  +        int pos = 0;
  +        for (int i = 0; i < attempts; i++) {
  +            System.arraycopy(cache, pos, newCache, pos - i, toRemove[i] - pos);
  +            pos = toRemove[i] + 1;
  +            // Special case: last element
  +            if (pos == cache.length) {
  +                break;
  +            }
  +        }
  +        cache = newCache;
  +        cacheSize -= totalSpace;
  +
  +        return true;
  +
  +    }
  +
  +
  +    /**
  +     * Find a map elemnt given its name in a sorted array of map elements.
  +     * This will return the index for the closest inferior or equal item in the
  +     * given array.
  +     */
  +    private static final int find(CacheEntry[] map, String name) {
  +
  +        int a = 0;
  +        int b = map.length - 1;
  +
  +        // Special cases: -1 and 0
  +        if (b == -1) {
  +            return -1;
  +        }
  +        if (name.compareTo(map[0].name) < 0) {
  +            return -1;
  +        }
  +        if (b == 0) {
  +            return 0;
  +        }
  +
  +        int i = 0;
  +        while (true) {
  +            i = (b + a) / 2;
  +            int result = name.compareTo(map[i].name);
  +            if (result > 0) {
  +                a = i;
  +            } else if (result == 0) {
  +                return i;
  +            } else {
  +                b = i;
  +            }
  +            if ((b - a) == 1) {
  +                int result2 = name.compareTo(map[b].name);
  +                if (result2 < 0) {
  +                    return a;
  +                } else {
  +                    return b;
  +                }
  +            }
  +        }
  +
  +    }
  +
  +
  +    /**
  +     * Insert into the right place in a sorted MapElement array, and prevent
  +     * duplicates.
  +     */
  +    private static final boolean insertMap
  +        (CacheEntry[] oldMap, CacheEntry[] newMap, CacheEntry newElement) {
  +        int pos = find(oldMap, newElement.name);
  +        if ((pos != -1) && (newElement.name.equals(oldMap[pos].name))) {
  +            return false;
  +        }
  +        System.arraycopy(oldMap, 0, newMap, 0, pos + 1);
  +        newMap[pos + 1] = newElement;
  +        System.arraycopy
  +            (oldMap, pos + 1, newMap, pos + 2, oldMap.length - pos - 1);
  +        return true;
  +    }
  +
  +
  +    /**
  +     * Insert into the right place in a sorted MapElement array.
  +     */
  +    private static final CacheEntry removeMap
  +        (CacheEntry[] oldMap, CacheEntry[] newMap, String name) {
  +        int pos = find(oldMap, name);
  +        if ((pos != -1) && (name.equals(oldMap[pos].name))) {
  +            System.arraycopy(oldMap, 0, newMap, 0, pos);
  +            System.arraycopy(oldMap, pos + 1, newMap, pos, 
  +                             oldMap.length - pos - 1);
  +            return oldMap[pos];
  +        }
  +        return null;
       }
   
   
  @@ -1586,6 +1872,8 @@
           Resource resource = null;
           DirContext context = null;
           boolean exists = true;
  +        long accessCount = 0;
  +        int size = 1;
   
   
           // ----------------------------------------------------- Public Methods
  @@ -1598,6 +1886,8 @@
               resource = null;
               context = null;
               exists = true;
  +            accessCount = 0;
  +            size = 1;
           }
   
   
  
  
  

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