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)
{