In a high concurrency load test with Apache MyFaces + RichFaces component
library we found that under peak load majority of our web container threads
were stuck in this call stack

at java/util/zip/Inflater.inflateBytes(Native Method)
at java/util/zip/Inflater.inflate(Inflater.java:249(Compiled Code))
at
java/util/zip/InflaterInputStream.read(InflaterInputStream.java:146(Compiled
Code))
at
java/util/zip/InflaterInputStream.read(InflaterInputStream.java:116(Compiled
Code))
at java/io/FilterInputStream.read(FilterInputStream.java:77(Compiled Code))
at java/io/FilterInputStream.read(FilterInputStream.java:77(Compiled Code))
at java/io/PushbackInputStream.read(PushbackInputStream.java:133(Compiled
Code))
at
org/apache/myfaces/shared_impl/resource/ResourceImpl$ValueExpressionFilterInputStream.read(ResourceImpl.java:117(Compiled
Code))
at java/io/InputStream.read(InputStream.java:175(Compiled Code))
at java/io/InputStream.read(InputStream.java:97(Compiled Code))
at
org/apache/myfaces/application/ResourceHandlerImpl.pipeBytes(ResourceHandlerImpl.java:402(Compiled
Code))
at
org/apache/myfaces/application/ResourceHandlerImpl.handleResourceRequest(ResourceHandlerImpl.java:357(Compiled
Code))
at
org/richfaces/resource/ResourceHandlerImpl.handleResourceRequest(ResourceHandlerImpl.java:257(Compiled
Code))
at javax/faces/webapp/FacesServlet.service(FacesServlet.java:183(Compiled
Code))
at
org/richfaces/webapp/ResourceServlet.httpService(ResourceServlet.java:110(Compiled
Code))
at
org/richfaces/webapp/ResourceServlet.service(ResourceServlet.java:105(Compiled
Code))

After looking at the src code of
 
org.apache.myfaces.application.ResourceHandlerImpl.handleResourceRequest(FacesContext)
 I can see that the ResourceHandlerCache caches the Resource metadata ;
however the actual output of the resource which is written to the
outputstream in ResourceHandlerImpl.pipeBytes is NEVER cached.

This causes a scalability problem in our case because each access to the
resource involves cracking open a jar, inflating the bytes and parsing a
stream of bytes. This is done multiple times for the same resource(say a
css file) inside the richfaces jar inspite of the resource not changing.

I propose a much needed performance optimization wherein we cache the
output of the Resource handled and stash the output byte[] it as
softReference in the ResourceHandlerCache.ResourceValue.

I have attached a patch that does the same and would like your feedback on
my proposal.
These patches are from src taken from Apache MyFaces 2.0.5

-cheers,
Rohit Kelapure,
Apache Open WebBeans committer
*** C:\temp\old\ResourceHandlerCache.java 2011-03-08 08:40:12.000000000 -0400
--- C:\temp\new\ResourceHandlerCache.java 2012-07-17 20:52:23.000000000 -0400
***************
*** 15,26 ****
--- 15,27 ----
   *  KIND, either express or implied.  See the License for the
   *  specific language governing permissions and limitations
   *  under the License.
   */
  package org.apache.myfaces.shared_impl.resource;
  
+ import java.lang.ref.SoftReference;
  import java.util.Collections;
  import java.util.LinkedHashMap;
  import java.util.Map;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  
***************
*** 36,46 ****
--- 37,51 ----
      private static final Logger log = Logger
              .getLogger(ResourceHandlerCache.class.getName());
  
      private Boolean _resourceCacheEnabled = null;
      private Map<ResourceKey, ResourceValue> _resourceCacheMap = null;
  
+     public Map<ResourceKey, ResourceValue> getResourceCacheMap() {
+               return _resourceCacheMap;
+       }
  
        @JSFWebConfigParam(defaultValue = "500", since = "2.0.2")
      private static final String RESOURCE_HANDLER_CACHE_SIZE_ATTRIBUTE = 
"org.apache.myfaces.RESOURCE_HANDLER_CACHE_SIZE";
      private static final int RESOURCE_HANDLER_CACHE_DEFAULT_SIZE = 500;
  
      @JSFWebConfigParam(defaultValue = "true", since = "2.0.2")
***************
*** 134,144 ****
--- 139,155 ----
          return WebConfigParamUtils.getIntegerInitParameter(externalContext,
                  RESOURCE_HANDLER_CACHE_SIZE_ATTRIBUTE, 
RESOURCE_HANDLER_CACHE_DEFAULT_SIZE);
      }
  
      public static class ResourceKey
      {
+         @Override
+               public String toString() {
+                       return "ResourceKey [contentType=" + contentType + ", 
libraryName="
+                                       + libraryName + ", resourceName=" + 
resourceName + "]";
+               }
  
                private String resourceName;
          private String libraryName;
          private String contentType;
  
          public ResourceKey(String resourceName, String libraryName,
***************
*** 201,211 ****
--- 212,234 ----
      public static class ResourceValue
      {
          private ResourceMeta resourceMeta;
  
          private ResourceLoader resourceLoader;
  
+         private SoftReference<byte[]> outputReference;
  
+         public byte[] getOutput()
+         {
+                       return outputReference != null ? outputReference.get() 
: null;
+               }
  
+               public void setOutput(byte[] o)
+               {
+                       this.outputReference = new SoftReference<byte[]>(o);
+               }
  
                public ResourceValue(ResourceMeta resourceMeta,
                  ResourceLoader resourceLoader)
          {
              super();
              this.resourceMeta = resourceMeta;
*** C:\temp\old\ResourceHandlerImpl.java 2012-01-25 19:03:58.000000000 -0400
--- C:\temp\new\ResourceHandlerImpl.java 2012-07-17 23:53:38.000000000 -0400
***************
*** 15,53 ****
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   */
  package org.apache.myfaces.application;
  
! import org.apache.myfaces.renderkit.ErrorPageWriter;
! import org.apache.myfaces.shared_impl.resource.ResourceHandlerCache;
! import 
org.apache.myfaces.shared_impl.resource.ResourceHandlerCache.ResourceValue;
! import org.apache.myfaces.shared_impl.resource.ResourceHandlerSupport;
! import org.apache.myfaces.shared_impl.resource.ResourceImpl;
! import org.apache.myfaces.shared_impl.resource.ResourceLoader;
! import org.apache.myfaces.shared_impl.resource.ResourceMeta;
! import org.apache.myfaces.shared_impl.util.ClassUtils;
! import org.apache.myfaces.shared_impl.util.ExternalContextUtils;
! import org.apache.myfaces.shared_impl.util.StringUtils;
  
- import javax.faces.application.Resource;
- import javax.faces.application.ResourceHandler;
- import javax.faces.application.ResourceWrapper;
- import javax.faces.context.ExternalContext;
- import javax.faces.context.FacesContext;
- import javax.servlet.http.HttpServletResponse;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.net.URL;
  import java.util.Locale;
  import java.util.Map;
  import java.util.MissingResourceException;
  import java.util.ResourceBundle;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  
  /**
   * DOCUMENT ME!
   *
   * @author Simon Lessard (latest modification by $Author: slessard $)
   * 
--- 15,55 ----
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   */
  package org.apache.myfaces.application;
  
! import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.net.URL;
  import java.util.Locale;
  import java.util.Map;
  import java.util.MissingResourceException;
  import java.util.ResourceBundle;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  
+ import javax.faces.application.Resource;
+ import javax.faces.application.ResourceHandler;
+ import javax.faces.application.ResourceWrapper;
+ import javax.faces.context.ExternalContext;
+ import javax.faces.context.FacesContext;
+ import javax.servlet.http.HttpServletResponse;
  
+ import org.apache.myfaces.renderkit.ErrorPageWriter;
+ import org.apache.myfaces.shared_impl.resource.ResourceHandlerCache;
+ import org.apache.myfaces.shared_impl.resource.ResourceHandlerSupport;
+ import org.apache.myfaces.shared_impl.resource.ResourceImpl;
+ import org.apache.myfaces.shared_impl.resource.ResourceLoader;
+ import org.apache.myfaces.shared_impl.resource.ResourceMeta;
+ import 
org.apache.myfaces.shared_impl.resource.ResourceHandlerCache.ResourceValue;
+ import org.apache.myfaces.shared_impl.util.ClassUtils;
+ import org.apache.myfaces.shared_impl.util.ExternalContextUtils;
+ import org.apache.myfaces.shared_impl.util.StringUtils;
  
  /**
   * DOCUMENT ME!
   *
   * @author Simon Lessard (latest modification by $Author: slessard $)
   *
***************
*** 262,272 ****
--- 264,276 ----
       *  This method implements an algorithm semantically identical to
       *  the one described on the javadoc of 
ResourceHandler.handleResourceRequest
       */
      @Override
      public void handleResourceRequest(FacesContext facesContext) throws 
IOException
      {
+       log.entering(this.getClass().getName(), "handleResourceRequest");
  
          try
          {
              String resourceBasePath = getResourceHandlerSupport()
                      .calculateResourceBasePath(facesContext);
  
***************
*** 327,341 ****
--- 332,353 ----
              if (resource == null)
              {
                  
httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
                  return;
              }
  
+             log.finer("\t NAME  "+ resource.getResourceName());
+             log.finer("\t ContentType "+ resource.getContentType());
+             log.finer("\t Library  "+ resource.getLibraryName());
+             log.finer("\t Request Path  "+ resource.getRequestPath());
+             log.finer("\t URL  "+ resource.getURL());
  
              if (!resource.userAgentNeedsUpdate(facesContext))
              {
                  
httpServletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                 log.exiting(this.getClass().getName(), 
"handleResourceRequest", "SC_NOT_MODIFIED");
                  return;
              }
  
              httpServletResponse.setContentType(_getContentType(resource, 
facesContext.getExternalContext()));
  
              Map<String, String> headers = resource.getResponseHeaders();
***************
*** 347,363 ****
      
              //serve up the bytes (taken from trinidad ResourceServlet)
              try
              {
                  InputStream in = resource.getInputStream();
                  OutputStream out = httpServletResponse.getOutputStream();
                  byte[] buffer = new byte[_BUFFER_SIZE];
!     
                  try
                  {
!                     int count = pipeBytes(in, out, buffer);
!                     //set the content lenght
                      httpServletResponse.setContentLength(count);
                  }
                  finally
                  {
                      try
--- 359,396 ----
  
              //serve up the bytes (taken from trinidad ResourceServlet)
              try
              {
                InputStream in = resource.getInputStream();
                OutputStream out = httpServletResponse.getOutputStream();
+               ResourceValue resourceValue = 
getResourceLoaderCache().getResource(resource.getResourceName(), 
resource.getLibraryName(), resource.getContentType());
+               log.finer("ResourceValue "+ resourceValue);
                byte[] buffer = new byte[_BUFFER_SIZE];
!               int count = -1;
!               
log.finer(getResourceLoaderCache().getResourceCacheMap().toString());
                try
                  {
+                       if (null != resourceValue &&
+                                       
resourceValue.getResourceMeta().couldResourceContainValueExpressions())
+                       {
+                               byte[] b = resourceValue.getOutput();
+                               if (null != b)
+                               {
+                                       out.write(b, 0, b.length);
+                                       count = b.length;
+                                       log.finer("CACHE HIT- SERVING UP BYTES 
"+ count);
  
!                               } else {
!                                       count = pipeAndCacheBytes(in, out, 
buffer);
!                                       resourceValue.setOutput(buffer);
!                                       log.finer("CACHE MISS - SERVING UP 
BYTES "+ count);
!                               }
!                       } else {
!                               count = pipeBytes(in, out, buffer);
!                               log.finer("NONE SERVING UP BYTES "+ count);
!                       }
  
                        httpServletResponse.setContentLength(count);
                  }
                  finally
                  {
                      try
***************
*** 386,403 ****
              // FIXME we are creating a html error page for a non html request 
here
              // shouln't we do something better? -=Jakob Korherr=-
              ErrorPageWriter.handleThrowable(facesContext, ex);
          }
      }
  
      /**
       * Reads the specified input stream into the provided byte array storage 
and
       * writes it to the output stream.
       */
!     private static int pipeBytes(InputStream in, OutputStream out, byte[] 
buffer)
!             throws IOException
      {
          int count = 0;
          int length;
  
          while ((length = (in.read(buffer))) >= 0)
          {
--- 419,459 ----
              // FIXME we are creating a html error page for a non html request 
here
              // shouln't we do something better? -=Jakob Korherr=-
              ErrorPageWriter.handleThrowable(facesContext, ex);
          }
  
  
+         log.exiting(this.getClass().getName(), "handleResourceRequest");
      }
  
      /**
       * Reads the specified input stream into the provided byte array storage 
and
       * writes it to the output stream.
       */
!       private static int pipeAndCacheBytes(InputStream is, OutputStream out, 
byte[] data) throws IOException {
! 
!               ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  
+               int nRead;
  
+               while ((nRead = is.read(data, 0, data.length)) != -1) {
+                       buffer.write(data, 0, nRead);
+               }
+               buffer.flush();
  
+               data = buffer.toByteArray();
+               out.write(data, 0, data.length);
  
+               return data.length;
+       }
  
      /**
       * Reads the specified input stream into the provided byte array storage 
and
       * writes it to the output stream.
       */
+     private static int pipeBytes(InputStream in, OutputStream out, byte[] 
buffer)   throws IOException
      {
          int count = 0;
          int length;
  
          while ((length = (in.read(buffer))) >= 0)
          {

Reply via email to