Merge remote-tracking branch 'origin/jetty-9.3.x'

Conflicts:
	jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java
index 7ae190a..ec8ab2d 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java
@@ -68,6 +68,13 @@
     
     public interface Factory
     {
-        HttpContent getContent(String path) throws IOException;
+        /**
+         * @param path The path within the context to the resource
+         * @param maxBuffer The maximum buffer to allocated for this request.  For cached content, a larger buffer may have
+         * previously been allocated and returned by the {@link HttpContent#getDirectBuffer()} or {@link HttpContent#getIndirectBuffer()} calls.
+         * @return A {@link HttpContent}
+         * @throws IOException
+         */
+        HttpContent getContent(String path,int maxBuffer) throws IOException;
     }
 }
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/ResourceHttpContent.java b/jetty-http/src/main/java/org/eclipse/jetty/http/ResourceHttpContent.java
index 30d7ba5..c583ce2 100644
--- a/jetty-http/src/main/java/org/eclipse/jetty/http/ResourceHttpContent.java
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/ResourceHttpContent.java
@@ -125,7 +125,7 @@
     @Override
     public ByteBuffer getDirectBuffer()
     {
-        if (_resource.length()<=0 || _maxBuffer<_resource.length())
+        if (_resource.length()<=0 || _maxBuffer>0 && _maxBuffer<_resource.length())
             return null;
         try
         {
@@ -155,7 +155,7 @@
     @Override
     public ByteBuffer getIndirectBuffer()
     {
-        if (_resource.length()<=0 || _maxBuffer<_resource.length())
+        if (_resource.length()<=0 || _maxBuffer>0 && _maxBuffer<_resource.length())
             return null;
         try
         {
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
index df6f65f..1330a1f 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
@@ -787,6 +787,7 @@
             }
             break;
         }
+        
 
         ByteBuffer buffer = _channel.useDirectBuffers() ? httpContent.getDirectBuffer() : null;
         if (buffer == null)
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java
index a209bf1..6141c69 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java
@@ -22,6 +22,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
 import java.nio.channels.ReadableByteChannel;
 import java.util.Comparator;
 import java.util.SortedSet;
@@ -172,7 +173,7 @@
     public HttpContent lookup(String pathInContext)
         throws IOException
     {
-        return getContent(pathInContext);
+        return getContent(pathInContext,_maxCachedFileSize);
     }
 
     /* ------------------------------------------------------------ */
@@ -180,6 +181,8 @@
      * Get either a valid entry object or create a new one if possible.
      *
      * @param pathInContext The key into the cache
+     * @param maxBuffer The maximum buffer to allocated for this request.  For cached content, a larger buffer may have
+     * previously been allocated and returned by the {@link HttpContent#getDirectBuffer()} or {@link HttpContent#getIndirectBuffer()} calls.
      * @return The entry matching <code>pathInContext</code>, or a new entry 
      * if no matching entry was found. If the content exists but is not cachable, 
      * then a {@link ResourceHttpContent} instance is return. If 
@@ -187,7 +190,7 @@
      * @throws IOException Problem loading the resource
      */
     @Override
-    public HttpContent getContent(String pathInContext)
+    public HttpContent getContent(String pathInContext,int maxBufferSize)
         throws IOException
     {
         // Is the content in this cache?
@@ -197,14 +200,14 @@
        
         // try loading the content from our factory.
         Resource resource=_factory.getResource(pathInContext);
-        HttpContent loaded = load(pathInContext,resource);
+        HttpContent loaded = load(pathInContext,resource,maxBufferSize);
         if (loaded!=null)
             return loaded;
         
         // Is the content in the parent cache?
         if (_parent!=null)
         {
-            HttpContent httpContent=_parent.lookup(pathInContext);
+            HttpContent httpContent=_parent.getContent(pathInContext,maxBufferSize);
             if (httpContent!=null)
                 return httpContent;
         }
@@ -225,14 +228,13 @@
         long len = resource.length();
 
         // Will it fit in the cache?
-        return  (len>0 && len<_maxCachedFileSize && len<_maxCacheSize);
+        return  (len>0 && (_useFileMappedBuffer || (len<_maxCachedFileSize && len<_maxCacheSize)));
     }
     
     /* ------------------------------------------------------------ */
-    private HttpContent load(String pathInContext, Resource resource)
+    private HttpContent load(String pathInContext, Resource resource, int maxBufferSize)
         throws IOException
     {
-        
         if (resource==null || !resource.exists())
             return null;
         
@@ -256,7 +258,6 @@
                     if (resourceGz.exists() && resourceGz.lastModified()>=resource.lastModified() && resourceGz.length()<resource.length())
                     {
                         contentGz = new CachedHttpContent(pathInContextGz,resourceGz,null);
-                        shrinkCache();
                         CachedHttpContent added = _cache.putIfAbsent(pathInContextGz,contentGz);
                         if (added!=null)
                         {
@@ -270,9 +271,6 @@
             else 
                 content = new CachedHttpContent(pathInContext,resource,null);
 
-            // reduce the cache to an acceptable size.
-            shrinkCache();
-
             // Add it to the cache.
             CachedHttpContent added = _cache.putIfAbsent(pathInContext,content);
             if (added!=null)
@@ -284,7 +282,7 @@
             return content;
         }
         
-        // Look for a gzip resource or content
+        // Look for non Cacheable gzip resource or content
         String mt = _mimeTypes.getMimeByExtension(pathInContext);
         if (_gzip)
         {
@@ -292,16 +290,16 @@
             String pathInContextGz=pathInContext+".gz";
             CachedHttpContent contentGz = _cache.get(pathInContextGz);
             if (contentGz!=null && contentGz.isValid() && contentGz.getResource().lastModified()>=resource.lastModified())
-                return new ResourceHttpContent(resource,mt,getMaxCachedFileSize(),contentGz);
+                return new ResourceHttpContent(resource,mt,maxBufferSize,contentGz);
             
             // Is there a gzip resource?
             Resource resourceGz=_factory.getResource(pathInContextGz);
             if (resourceGz.exists() && resourceGz.lastModified()>=resource.lastModified() && resourceGz.length()<resource.length())
-                return new ResourceHttpContent(resource,mt,getMaxCachedFileSize(),
-                       new ResourceHttpContent(resourceGz,_mimeTypes.getMimeByExtension(pathInContextGz),getMaxCachedFileSize()));
+                return new ResourceHttpContent(resource,mt,maxBufferSize,
+                       new ResourceHttpContent(resourceGz,_mimeTypes.getMimeByExtension(pathInContextGz),maxBufferSize));
         }
         
-        return new ResourceHttpContent(resource,mt,getMaxCachedFileSize());
+        return new ResourceHttpContent(resource,mt,maxBufferSize);
     }
     
     /* ------------------------------------------------------------ */
@@ -359,6 +357,8 @@
     /* ------------------------------------------------------------ */
     protected ByteBuffer getDirectBuffer(Resource resource)
     {
+        // Only use file mapped buffers for cached resources, otherwise too much virtual memory commitment for
+        // a non shared resource.  Also ignore max buffer size
         try
         {
             if (_useFileMappedBuffer && resource.getFile()!=null && resource.length()<Integer.MAX_VALUE) 
@@ -421,8 +421,9 @@
             _contentLengthValue=exists?(int)resource.length():0;
             _contentLength=new PreEncodedHttpField(HttpHeader.CONTENT_LENGTH,Long.toString(_contentLengthValue));
             
-            _cachedSize.addAndGet(_contentLengthValue);
-            _cachedFiles.incrementAndGet();
+            if (_cachedFiles.incrementAndGet()>_maxCachedFiles)
+                shrinkCache();
+            
             _lastAccessed=System.currentTimeMillis();
             
             _etag=ResourceCache.this._etags?new PreEncodedHttpField(HttpHeader.ETAG,resource.getWeakETag()):null;
@@ -487,8 +488,14 @@
         /* ------------------------------------------------------------ */
         protected void invalidate()
         {
-            // Invalidate it
-            _cachedSize.addAndGet(-_contentLengthValue);
+            ByteBuffer indirect=_indirectBuffer.get();
+            if (indirect!=null && _indirectBuffer.compareAndSet(indirect,null))
+                _cachedSize.addAndGet(-BufferUtil.length(indirect));
+            
+            ByteBuffer direct=_directBuffer.get();
+            if (direct!=null && !BufferUtil.isMappedBuffer(direct) && _directBuffer.compareAndSet(direct,null))
+                _cachedSize.addAndGet(-BufferUtil.length(direct));
+
             _cachedFiles.decrementAndGet();
             _resource.close();
         }
@@ -507,7 +514,6 @@
             return _lastModified==null?null:_lastModified.getValue();
         }
 
-
         /* ------------------------------------------------------------ */
         @Override
         public HttpField getContentType()
@@ -569,7 +575,11 @@
                 if (buffer2==null)
                     LOG.warn("Could not load "+this);
                 else if (_indirectBuffer.compareAndSet(null,buffer2))
+                {
                     buffer=buffer2;
+                    if (_cachedSize.addAndGet(BufferUtil.length(buffer))>_maxCacheSize)
+                        shrinkCache();
+                }
                 else
                     buffer=_indirectBuffer.get();
             }
@@ -578,7 +588,6 @@
             return buffer.slice();
         }
         
-
         /* ------------------------------------------------------------ */
         @Override
         public ByteBuffer getDirectBuffer()
@@ -591,7 +600,12 @@
                 if (buffer2==null)
                     LOG.warn("Could not load "+this);
                 else if (_directBuffer.compareAndSet(null,buffer2))
+                {
                     buffer=buffer2;
+
+                    if (!BufferUtil.isMappedBuffer(buffer) && _cachedSize.addAndGet(BufferUtil.length(buffer))>_maxCacheSize)
+                        shrinkCache(); 
+                }
                 else
                     buffer=_directBuffer.get();
             }
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceContentFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceContentFactory.java
index 2e0edde..11d8810 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceContentFactory.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceContentFactory.java
@@ -27,55 +27,47 @@
 import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.util.resource.ResourceFactory;
 
+
+/**
+ * A HttpContent.Factory for transient content.  The HttpContent's created by 
+ * this factory are not intended to be cached, so memory limits for individual
+ * HttpOutput streams are enforced.
+ */
 public class ResourceContentFactory implements Factory
 {
     private final ResourceFactory _factory;
     private final MimeTypes _mimeTypes;
-    private final int _maxBufferSize;
     private final boolean _gzip;
     
-
     /* ------------------------------------------------------------ */
-    public ResourceContentFactory(ResourceFactory factory, MimeTypes mimeTypes, int maxBufferSize, boolean gzip)
+    public ResourceContentFactory(ResourceFactory factory, MimeTypes mimeTypes, boolean gzip)
     {
         _factory=factory;
         _mimeTypes=mimeTypes;
-        _maxBufferSize=maxBufferSize;
         _gzip=gzip;
     }
 
     /* ------------------------------------------------------------ */
-    /** Get a Entry from the cache.
-     * Get either a valid entry object or create a new one if possible.
-     *
-     * @param pathInContext The key into the cache
-     * @return The entry matching <code>pathInContext</code>, or a new entry 
-     * if no matching entry was found. If the content exists but is not cachable, 
-     * then a {@link ResourceHttpContent} instance is return. If 
-     * the resource does not exist, then null is returned.
-     * @throws IOException Problem loading the resource
-     */
     @Override
-    public HttpContent getContent(String pathInContext)
+    public HttpContent getContent(String pathInContext,int maxBufferSize)
         throws IOException
     {
-       
         // try loading the content from our factory.
         Resource resource=_factory.getResource(pathInContext);
-        HttpContent loaded = load(pathInContext,resource);
+        HttpContent loaded = load(pathInContext,resource,maxBufferSize);
         return loaded;
     }
     
     
     /* ------------------------------------------------------------ */
-    private HttpContent load(String pathInContext, Resource resource)
+    private HttpContent load(String pathInContext, Resource resource, int maxBufferSize)
         throws IOException
     {   
         if (resource==null || !resource.exists())
             return null;
         
         if (resource.isDirectory())
-            return new ResourceHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),_maxBufferSize);
+            return new ResourceHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),maxBufferSize);
         
         // Look for a gzip resource or content
         String mt = _mimeTypes.getMimeByExtension(pathInContext);
@@ -85,11 +77,11 @@
             String pathInContextGz=pathInContext+".gz";
             Resource resourceGz=_factory.getResource(pathInContextGz);
             if (resourceGz.exists() && resourceGz.lastModified()>=resource.lastModified() && resourceGz.length()<resource.length())
-                return new ResourceHttpContent(resource,mt,_maxBufferSize,
-                       new ResourceHttpContent(resourceGz,_mimeTypes.getMimeByExtension(pathInContextGz),_maxBufferSize));
+                return new ResourceHttpContent(resource,mt,maxBufferSize,
+                       new ResourceHttpContent(resourceGz,_mimeTypes.getMimeByExtension(pathInContextGz),maxBufferSize));
         }
         
-        return new ResourceHttpContent(resource,mt,_maxBufferSize);
+        return new ResourceHttpContent(resource,mt,maxBufferSize);
     }
     
     
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java
index 67c6e24..fb465b8 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -30,6 +31,7 @@
 import org.eclipse.jetty.http.HttpContent;
 import org.eclipse.jetty.http.MimeTypes;
 import org.eclipse.jetty.http.ResourceHttpContent;
+import org.eclipse.jetty.toolchain.test.OS;
 import org.eclipse.jetty.util.BufferUtil;
 import org.eclipse.jetty.util.resource.Resource;
 import org.eclipse.jetty.util.resource.ResourceCollection;
@@ -136,50 +138,83 @@
         cache.setMaxCachedFileSize(85);
         cache.setMaxCachedFiles(4);
 
-        assertTrue(cache.lookup("does not exist")==null);
-        assertTrue(cache.lookup(names[9]) instanceof ResourceHttpContent);
+        assertTrue(cache.getContent("does not exist",4096)==null);
+        assertTrue(cache.getContent(names[9],4096) instanceof ResourceHttpContent);
+        assertTrue(cache.getContent(names[9],4096).getIndirectBuffer()!=null);
 
         HttpContent content;
-        content=cache.lookup(names[8]);
+        content=cache.getContent(names[8],4096);
         assertTrue(content!=null);
         assertEquals(80,content.getContentLengthValue());
+        assertEquals(0,cache.getCachedSize());
+        
+        if (OS.IS_LINUX)
+        {
+            // Initially not using memory mapped files
+            content.getDirectBuffer();
+            assertEquals(80,cache.getCachedSize());
 
+            // with both types of buffer loaded, this is too large for cache
+            content.getIndirectBuffer();
+            assertEquals(0,cache.getCachedSize());
+            assertEquals(0,cache.getCachedFiles());
+
+            cache=new ResourceCache(null,directory,new MimeTypes(),true,false,false);
+            cache.setMaxCacheSize(95);
+            cache.setMaxCachedFileSize(85);
+            cache.setMaxCachedFiles(4);
+            
+            content=cache.getContent(names[8],4096);
+            content.getDirectBuffer();
+            assertEquals(cache.isUseFileMappedBuffer()?0:80,cache.getCachedSize());
+
+            // with both types of buffer loaded, this is not too large for cache because
+            // mapped buffers don't count, so we can continue
+        }
+        content.getIndirectBuffer();
         assertEquals(80,cache.getCachedSize());
         assertEquals(1,cache.getCachedFiles());
 
         Thread.sleep(200);
 
-        content=cache.lookup(names[1]);
+        content=cache.getContent(names[1],4096);
+        assertEquals(80,cache.getCachedSize());
+        content.getIndirectBuffer();
         assertEquals(90,cache.getCachedSize());
         assertEquals(2,cache.getCachedFiles());
 
         Thread.sleep(200);
 
-        content=cache.lookup(names[2]);
+        content=cache.getContent(names[2],4096);
+        content.getIndirectBuffer();
         assertEquals(30,cache.getCachedSize());
         assertEquals(2,cache.getCachedFiles());
 
         Thread.sleep(200);
 
-        content=cache.lookup(names[3]);
+        content=cache.getContent(names[3],4096);
+        content.getIndirectBuffer();
         assertEquals(60,cache.getCachedSize());
         assertEquals(3,cache.getCachedFiles());
 
         Thread.sleep(200);
 
-        content=cache.lookup(names[4]);
+        content=cache.getContent(names[4],4096);
+        content.getIndirectBuffer();
         assertEquals(90,cache.getCachedSize());
         assertEquals(3,cache.getCachedFiles());
 
         Thread.sleep(200);
 
-        content=cache.lookup(names[5]);
+        content=cache.getContent(names[5],4096);
+        content.getIndirectBuffer();
         assertEquals(90,cache.getCachedSize());
         assertEquals(2,cache.getCachedFiles());
 
         Thread.sleep(200);
 
-        content=cache.lookup(names[6]);
+        content=cache.getContent(names[6],4096);
+        content.getIndirectBuffer();
         assertEquals(60,cache.getCachedSize());
         assertEquals(1,cache.getCachedFiles());
 
@@ -189,37 +224,43 @@
         {
             out.write(' ');
         }
-        content=cache.lookup(names[7]);
+        content=cache.getContent(names[7],4096);
+        content.getIndirectBuffer();
         assertEquals(70,cache.getCachedSize());
         assertEquals(1,cache.getCachedFiles());
 
         Thread.sleep(200);
 
-        content=cache.lookup(names[6]);
+        content=cache.getContent(names[6],4096);
+        content.getIndirectBuffer();
         assertEquals(71,cache.getCachedSize());
         assertEquals(2,cache.getCachedFiles());
 
         Thread.sleep(200);
 
-        content=cache.lookup(names[0]);
+        content=cache.getContent(names[0],4096);
+        content.getIndirectBuffer();
         assertEquals(72,cache.getCachedSize());
         assertEquals(3,cache.getCachedFiles());
 
         Thread.sleep(200);
 
-        content=cache.lookup(names[1]);
+        content=cache.getContent(names[1],4096);
+        content.getIndirectBuffer();
         assertEquals(82,cache.getCachedSize());
         assertEquals(4,cache.getCachedFiles());
 
         Thread.sleep(200);
 
-        content=cache.lookup(names[2]);
+        content=cache.getContent(names[2],4096);
+        content.getIndirectBuffer();
         assertEquals(32,cache.getCachedSize());
         assertEquals(4,cache.getCachedFiles());
 
         Thread.sleep(200);
 
-        content=cache.lookup(names[3]);
+        content=cache.getContent(names[3],4096);
+        content.getIndirectBuffer();
         assertEquals(61,cache.getCachedSize());
         assertEquals(4,cache.getCachedFiles());
 
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java
index 90b641c..a5182fe 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java
@@ -289,8 +289,15 @@
             throw new UnavailableException(e.toString());
         }
 
-        _contentFactory=_cache==null?new ResourceContentFactory(this,_mimeTypes,-1,_gzip):_cache; // TODO pass a buffer size
-
+        if (_cache!=null)
+            _contentFactory=_cache;
+        else
+        {
+            _contentFactory=new ResourceContentFactory(this,_mimeTypes,_gzip);
+            if (resourceCache!=null)
+                _servletContext.setAttribute(resourceCache,_contentFactory);
+        }
+        
         _gzipEquivalentFileExtensions = new ArrayList<String>();
         String otherGzipExtensions = getInitParameter("otherGzipFileExtensions");
         if (otherGzipExtensions != null)
@@ -461,7 +468,7 @@
         try
         {
             // Find the content
-            content=_contentFactory.getContent(pathInContext);
+            content=_contentFactory.getContent(pathInContext,response.getBufferSize());
             if (LOG.isDebugEnabled())
                 LOG.info("content={}",content);
             
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java
index 0f740d9..13a5483 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java
@@ -23,6 +23,7 @@
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -39,8 +40,10 @@
 import javax.servlet.ServletResponse;
 
 import org.eclipse.jetty.http.DateGenerator;
+import org.eclipse.jetty.http.HttpContent;
 import org.eclipse.jetty.server.HttpConfiguration;
 import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.ResourceContentFactory;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker;
 import org.eclipse.jetty.toolchain.test.FS;
@@ -48,6 +51,7 @@
 import org.eclipse.jetty.toolchain.test.OS;
 import org.eclipse.jetty.toolchain.test.TestingDir;
 import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.resource.Resource;
 import org.hamcrest.Matchers;
 import org.junit.After;
 import org.junit.Assert;
@@ -522,6 +526,45 @@
     }
 
     @Test
+    public void testDirectFromResourceHttpContent() throws Exception
+    {
+        if (!OS.IS_LINUX)
+            return;
+        
+        testdir.ensureEmpty();
+        File resBase = testdir.getPathFile("docroot").toFile();
+        FS.ensureDirExists(resBase);
+        context.setBaseResource(Resource.newResource(resBase));
+        
+        File index = new File(resBase, "index.html");
+        createFile(index, "<h1>Hello World</h1>");
+
+        ServletHolder defholder = context.addServlet(DefaultServlet.class, "/");
+        defholder.setInitParameter("dirAllowed", "true");
+        defholder.setInitParameter("redirectWelcome", "false");
+        defholder.setInitParameter("useFileMappedBuffer", "true");
+        defholder.setInitParameter("welcomeServlets", "exact");
+        defholder.setInitParameter("gzip", "false");
+        defholder.setInitParameter("resourceCache","resourceCache");
+
+        String response;
+
+        response = connector.getResponses("GET /context/index.html HTTP/1.0\r\n\r\n");
+        assertResponseContains("<h1>Hello World</h1>", response);
+        
+        ResourceContentFactory factory = (ResourceContentFactory)context.getServletContext().getAttribute("resourceCache");
+        
+        HttpContent content = factory.getContent("/index.html",200);
+        ByteBuffer buffer = content.getDirectBuffer();
+        Assert.assertTrue(buffer.isDirect());        
+        content = factory.getContent("/index.html",5);
+        buffer = content.getDirectBuffer();
+        Assert.assertTrue(buffer==null);        
+    }
+    
+    
+    
+    @Test
     public void testRangeRequests() throws Exception
     {
         testdir.ensureEmpty();
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java
index 8a13382..aad404d 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java
@@ -1107,10 +1107,10 @@
     {
         private static final long serialVersionUID = 3534663738034577872L;
 
-        protected transient final String _id;
-        protected transient final int _type;
-        protected transient final long[] _timestamps;
-        protected transient int _next;
+        protected final String _id;
+        protected final int _type;
+        protected final long[] _timestamps;
+        protected int _next;
 
         public RateTracker(String id, int type, int maxRequestsPerSecond)
         {
@@ -1164,16 +1164,14 @@
         public void sessionWillPassivate(HttpSessionEvent se)
         {
             //take the tracker of the list of trackers (if its still there)
-            //and ensure that we take ourselves out of the session so we are not saved
             _rateTrackers.remove(_id);
-            se.getSession().removeAttribute(__TRACKER);
-            if (LOG.isDebugEnabled()) 
-                LOG.debug("Value removed: {}", getId());
         }
 
         public void sessionDidActivate(HttpSessionEvent se)
         {
-            LOG.warn("Unexpected session activation");
+            RateTracker tracker = (RateTracker)se.getSession().getAttribute(__TRACKER);
+            if (tracker!=null)
+                _rateTrackers.put(tracker.getId(),tracker);
         }
 
         @Override
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java
index 4937714..fa1ef98 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java
@@ -19,17 +19,21 @@
 package org.eclipse.jetty.util;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.RandomAccessFile;
+import java.lang.reflect.Field;
 import java.nio.Buffer;
 import java.nio.BufferOverflowException;
 import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
 import java.nio.channels.FileChannel;
 import java.nio.channels.FileChannel.MapMode;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.StandardOpenOption;
 import java.util.Arrays;
 
 import org.eclipse.jetty.util.log.Log;
@@ -903,12 +907,48 @@
 
     public static ByteBuffer toMappedBuffer(File file) throws IOException
     {
-        try (RandomAccessFile raf = new RandomAccessFile(file, "r"))
+        try (FileChannel channel = FileChannel.open(file.toPath(),StandardOpenOption.READ))
         {
-            return raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length());
+            return channel.map(MapMode.READ_ONLY, 0, file.length());
         }
     }
 
+    static final Field fdMappedByteBuffer;
+    static
+    {
+        Field fd = null;
+        try
+        {
+            fd=MappedByteBuffer.class.getDeclaredField("fd");
+            fd.setAccessible(true);
+        }
+        catch(Exception e)
+        {   
+        }
+        fdMappedByteBuffer=fd;
+    }
+
+    public static boolean isMappedBuffer(ByteBuffer buffer)
+    {
+        if (!(buffer instanceof MappedByteBuffer))
+            return false;
+        MappedByteBuffer mapped = (MappedByteBuffer) buffer;
+
+        if (fdMappedByteBuffer!=null)
+        {
+            try
+            {
+                if (fdMappedByteBuffer.get(mapped) instanceof FileDescriptor)
+                    return true;
+            }
+            catch(Exception e)
+            {
+            }
+        }            
+        return false;
+    }
+    
+    
     public static ByteBuffer toBuffer(Resource resource,boolean direct) throws IOException
     {
         int len=(int)resource.length();
@@ -1156,4 +1196,5 @@
 
 
 
+
 }
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java
index d2f8b24..9e44393 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java
@@ -21,13 +21,20 @@
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
 import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileWriter;
 import java.io.IOException;
 import java.nio.BufferOverflowException;
 import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.OpenOption;
 import java.util.Arrays;
 import java.util.concurrent.ThreadLocalRandom;
 
@@ -335,4 +342,39 @@
         BufferUtil.writeTo(buffer.asReadOnlyBuffer(), out);
         assertThat("Bytes in out equal bytes in buffer", Arrays.equals(bytes, out.toByteArray()), is(true));
     }
+    
+    @Test
+    public void testMappedFile() throws Exception
+    {
+        String data="Now is the time for all good men to come to the aid of the party";
+        File file = File.createTempFile("test",".txt");
+        file.deleteOnExit();
+        try(FileWriter out = new FileWriter(file);)
+        {
+            out.write(data);
+        }
+        
+        ByteBuffer mapped = BufferUtil.toMappedBuffer(file);
+        assertEquals(data,BufferUtil.toString(mapped));
+        assertTrue(BufferUtil.isMappedBuffer(mapped));
+        
+        ByteBuffer direct = BufferUtil.allocateDirect(data.length());
+        direct.clear();
+        direct.put(data.getBytes(StandardCharsets.ISO_8859_1));
+        direct.flip();
+        assertEquals(data,BufferUtil.toString(direct));
+        assertFalse(BufferUtil.isMappedBuffer(direct));
+        
+        ByteBuffer slice = direct.slice();
+        assertEquals(data,BufferUtil.toString(slice));
+        assertFalse(BufferUtil.isMappedBuffer(slice));
+        
+        ByteBuffer duplicate = direct.duplicate();
+        assertEquals(data,BufferUtil.toString(duplicate));
+        assertFalse(BufferUtil.isMappedBuffer(duplicate));
+        
+        ByteBuffer readonly = direct.asReadOnlyBuffer();
+        assertEquals(data,BufferUtil.toString(readonly));
+        assertFalse(BufferUtil.isMappedBuffer(readonly));
+    }
 }