ALF-12273: Merge V4.0-BUG-FIX to HEAD

33119: Merge V3.4-BUG-FIX (3.4.8) to V4.0-BUG-FIX (4.0.1)
      33099: ALF-10412 Nonreducing 100% CPU Uploading Large Files to Share Site Document Library
      ALF-10976 Excel files bigger than 2mb cause soffice.exe to take 100% of one CPU for more than 2 minutes in previews.
         - Polish TransformerDebug
         - Better config for txt and xlsx to swf   
      33095: ALF-10412 Nonreducing 100% CPU Uploading Large Files to Share Site Document Library
      ALF-10976 Excel files bigger than 2mb cause soffice.exe to take 100% of one CPU for more than 2 minutes in previews.
         - Improvements to TransformerDebug so that calls to getTransformers use trace rather than debug level logging
           allowing one to see the wood for the trees  
      33016: ALF-10412 Nonreducing 100% CPU Uploading Large Files to Share Site Document Library
      ALF-10976 Excel files bigger than 2mb cause soffice.exe to take 100% of one CPU for more than 2 minutes in previews.
         - fix build errors - may not get all of them as not tested on Linux
      33005: ALF-10412 Nonreducing 100% CPU Uploading Large Files to Share Site Document Library
      ALF-10976 Excel files bigger than 2mb cause soffice.exe to take 100% of one CPU for more than 2 minutes in previews.
         - Disable transformers if the source txt or xlsx is too large - avoids transforms that don't finish
           txt limit is 5MB
      	 xlsx limit is 1MB
         - Added a general 2 minute timeout added (ignored by JOD transformers - which already have a 2 minute timeout 
      	 and OpenOffice transformers - would require more work)
         - Previous commit already limited txt -> pdf -> png so that only the 1st pdf page was created when creating a doclib icon
         - Earlier commit already reduced the priority of the background Thread used for transformations so that user interaction
           did not suffer.
      33004: ALF-10412 Nonreducing 100% CPU Uploading Large Files to Share Site Document Library
      ALF-10976 Excel files bigger than 2mb cause soffice.exe to take 100% of one CPU for more than 2 minutes in previews.
         - Added time, size and page limits to transformer configuration to allow one to avoid
           costly transformations and to stop them if they do occur. Limits avoid a transformer being
           selected if the source is too large, or make it throw and Exception or discard data after a given
           time, KBytes read or pages created.
         - Page limits currently only used by TextToPdfContentTransformer for thumbnail (icon) creation.
         - Time, Size and Page limits are currently ignored by JodContentTransformer and OpenOfficeContentTransformerWorker
           once running but the max source size limits may be used to avoid the selection of the transformer in the first
           place.
         - TransformerDebug added to be able to see what is going on. A real eye opener!
           log4j org.alfresco.repo.content.transform.TransformerDebug
      32136: ALF-10412 Nonreducing 100% CPU Uploading Large Files to Share Site Document Library
         Reducing the priority of the async thread pool that is used to perform the transformations so that normal activity (and even garbage collection) is not interrupted by transformations. 


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@33223 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Alan Davis
2012-01-13 17:25:32 +00:00
parent 0a5389a552
commit 819988c518
70 changed files with 4514 additions and 438 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -32,13 +32,20 @@ import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicBoolean;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.content.filestore.FileContentWriter;
import org.alfresco.repo.content.transform.TransformerDebug;
import org.alfresco.service.cmr.repository.ContentAccessor;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentStreamListener;
import org.alfresco.service.cmr.repository.TransformationOptionLimits;
import org.alfresco.service.cmr.repository.TransformationOptionPair;
import org.alfresco.service.cmr.repository.TransformationOptionPair.Action;
import org.alfresco.util.EqualsHelper;
import org.alfresco.util.TempFileProvider;
import org.apache.commons.logging.Log;
@@ -46,6 +53,9 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.util.FileCopyUtils;
import sun.nio.ch.ChannelInputStream;
/**
* Implements all the convenience methods of the interface. The only methods
* that need to be implemented, i.e. provide low-level content access are:
@@ -59,10 +69,20 @@ import org.springframework.util.FileCopyUtils;
public abstract class AbstractContentReader extends AbstractContentAccessor implements ContentReader
{
private static final Log logger = LogFactory.getLog(AbstractContentReader.class);
private static final Timer timer = new Timer();
private List<ContentStreamListener> listeners;
private ReadableByteChannel channel;
// Optional limits on reading
private TransformationOptionLimits limits;
// Only needed if limits are set
private TransformerDebug transformerDebug;
// For testing: Allows buffering to be turned off
private boolean useBufferedInputStream = true;
/**
* @param contentUrl the content URL - this should be relative to the root of the store
* and not absolute: to enable moving of the stores
@@ -73,7 +93,37 @@ public abstract class AbstractContentReader extends AbstractContentAccessor impl
listeners = new ArrayList<ContentStreamListener>(2);
}
public void setLimits(TransformationOptionLimits limits)
{
this.limits = limits;
}
public TransformationOptionLimits getLimits()
{
return limits;
}
public void setTransformerDebug(TransformerDebug transformerDebug)
{
this.transformerDebug = transformerDebug;
}
public TransformerDebug getTransformerDebug()
{
return transformerDebug;
}
public void setUseBufferedInputStream(boolean useBufferedInputStream)
{
this.useBufferedInputStream = useBufferedInputStream;
}
public boolean getUseBufferedInputStream()
{
return useBufferedInputStream;
}
/**
* Adds the listener after checking that the output stream isn't already in
* use.
@@ -323,7 +373,26 @@ public abstract class AbstractContentReader extends AbstractContentAccessor impl
try
{
ReadableByteChannel channel = getReadableChannel();
InputStream is = new BufferedInputStream(Channels.newInputStream(channel));
InputStream is = Channels.newInputStream(channel);
// If we have a timeout or read limit, intercept the calls.
if (limits != null)
{
TransformationOptionPair time = limits.getTimePair();
TransformationOptionPair kBytes = limits.getKBytesPair();
long timeoutMs = time.getValue();
long readLimitBytes = kBytes.getValue() * 1024;
if (timeoutMs > 0 || readLimitBytes > 0)
{
Action timeoutAction = time.getAction();
Action readLimitAction = kBytes.getAction();
is = new TimeSizeRestrictedInputStream(is, timeoutMs, timeoutAction,
readLimitBytes, readLimitAction, transformerDebug);
}
}
is = new BufferedInputStream(is);
// done
return is;
}
@@ -486,4 +555,105 @@ public abstract class AbstractContentReader extends AbstractContentAccessor impl
" right: " + right);
}
}
/**
* InputStream that wraps another InputStream to terminate early after a timeout
* or after reading a number of bytes. It terminates by either returning end of file
* (-1) or throwing an IOException.
* @author Alan Davis
*/
private class TimeSizeRestrictedInputStream extends InputStream
{
private final AtomicBoolean timeoutFlag = new AtomicBoolean(false);
private final InputStream is;
private final long timeoutMs;
private final long readLimitBytes;
private final Action timeoutAction;
private final Action readLimitAction;
private final TransformerDebug transformerDebug;
private long readCount = 0;
public TimeSizeRestrictedInputStream(InputStream is,
long timeoutMs, Action timeoutAction,
long readLimitBytes, Action readLimitAction,
TransformerDebug transformerDebug)
{
this.is = useBufferedInputStream ? new BufferedInputStream(is) : is;
this.timeoutMs = timeoutMs;
this.timeoutAction = timeoutAction;
this.readLimitBytes = readLimitBytes;
this.readLimitAction = readLimitAction;
this.transformerDebug = transformerDebug;
if (timeoutMs > 0)
{
timer.schedule(new TimerTask()
{
@Override
public void run()
{
timeoutFlag.set(true);
}
}, timeoutMs);
}
}
@Override
public int read() throws IOException
{
// Throws exception or return true to indicate EOF
if (hitTimeout() || hitReadLimit())
{
return -1;
}
int n = is.read();
if (n > 0)
{
readCount++;
}
return n;
}
private boolean hitTimeout() throws IOException
{
if (timeoutMs > 0 && timeoutFlag.get())
{
timeoutAction.throwIOExceptionIfRequired(
"Transformation has taken too long ("+
(timeoutMs/1000)+" seconds)", transformerDebug);
return true;
}
return false;
}
private boolean hitReadLimit() throws IOException
{
if (readLimitBytes > 0 && readCount >= readLimitBytes)
{
readLimitAction.throwIOExceptionIfRequired(
"Transformation has read too many bytes ("+
(readLimitBytes/1024)+"K)", transformerDebug);
return true;
}
return false;
}
@Override
public void close() throws IOException
{
try
{
is.close();
}
finally
{
super.close();
}
}
};
}