mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
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
345 lines
12 KiB
Java
345 lines
12 KiB
Java
/*
|
|
* Copyright (C) 2005-2011 Alfresco Software Limited.
|
|
*
|
|
* This file is part of Alfresco
|
|
*
|
|
* Alfresco is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Alfresco is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package org.alfresco.repo.content;
|
|
|
|
|
|
import static org.junit.Assert.assertTrue;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.nio.channels.Channels;
|
|
import java.nio.channels.ReadableByteChannel;
|
|
|
|
import org.alfresco.repo.content.transform.TransformerDebug;
|
|
import org.alfresco.service.cmr.repository.ContentIOException;
|
|
import org.alfresco.service.cmr.repository.ContentReader;
|
|
import org.alfresco.service.cmr.repository.TransformationOptionLimits;
|
|
import org.junit.Before;
|
|
import org.junit.Test;
|
|
import org.springframework.context.ApplicationContext;
|
|
|
|
/**
|
|
* Test class for time and byte limits on an AbstractContentReader
|
|
*
|
|
* @author Alan Davis
|
|
*/
|
|
public class AbstractContentReaderLimitTest
|
|
{
|
|
private static final int K = 1024;
|
|
private static final int M = K*K;
|
|
|
|
// Normal test file size 5K
|
|
private static final long SIZE = 5*K;
|
|
|
|
// Normal test delay between returning bytes, giving a run time of about 5 seconds if not interrupted.
|
|
private static final long MS_PER_BYTE = 1;
|
|
|
|
// Large test file size 100 MB with no delay takes about a second to read.
|
|
private static final long LARGE_SIZE = 100*M;
|
|
|
|
// Top speed to read 1 MB via the DummyAbstractContentReader
|
|
// Assume about 10ms normally per MB, so use half that and a high
|
|
// MARGIN_OF_ERROR_PERCENTAGE_FAST.
|
|
private static final long MS_PER_MB = 5;
|
|
|
|
// Margins of error when using a DummyAbstractContentReader
|
|
// with or without a delay. Used to make sure different runs
|
|
// don't result in failing tests but at the same time that
|
|
// they will if there is a real problem.
|
|
private static final int MARGIN_OF_ERROR_PERCENTAGE_SLOW = 50;
|
|
private static final int MARGIN_OF_ERROR_PERCENTAGE_FAST = 900;
|
|
|
|
private DummyAbstractContentReader reader;
|
|
private TransformationOptionLimits limits;
|
|
private TransformerDebug transformerDebug;
|
|
private long minTime;
|
|
private long maxTime;
|
|
private long minLength;
|
|
private long maxLength;
|
|
|
|
@Before
|
|
public void setUp() throws Exception
|
|
{
|
|
ApplicationContext ctx = ContentMinimalContextTestSuite.getContext();
|
|
transformerDebug = (TransformerDebug) ctx.getBean("transformerDebug");
|
|
|
|
limits = new TransformationOptionLimits();
|
|
reader = new DummyAbstractContentReader(SIZE, MS_PER_BYTE);
|
|
reader.setLimits(limits);
|
|
reader.setTransformerDebug(transformerDebug);
|
|
|
|
// Without the following, the bytes from the DummyAbstractContentReader are read in 4K blocks
|
|
// so the test to do with timeouts and read limits will not work, as they expect to read 1K per
|
|
// second. Not an issue in the real world as read rates are much higher, so a buffer makes no
|
|
// difference to limit checking. It does make a vast difference to performance when the InputStream
|
|
// is wrapped in a InputStreamReader as is done by a number of transformers.
|
|
reader.setUseBufferedInputStream(false);
|
|
}
|
|
|
|
@Test
|
|
public void noLimitTest() throws Exception
|
|
{
|
|
readAndCheck();
|
|
}
|
|
|
|
@Test(expected=ContentIOException.class)
|
|
public void maxKBytesTest() throws Exception
|
|
{
|
|
limits.setMaxSourceSizeKBytes(1);
|
|
readAndCheck();
|
|
}
|
|
|
|
@Test(expected=ContentIOException.class)
|
|
public void maxTimeTest() throws Exception
|
|
{
|
|
limits.setTimeoutMs(1000);
|
|
readAndCheck();
|
|
}
|
|
|
|
@Test(expected=ContentIOException.class)
|
|
public void maxTimeAndKBytesTest() throws Exception
|
|
{
|
|
limits.setTimeoutMs(1000);
|
|
limits.setMaxSourceSizeKBytes(1);
|
|
readAndCheck();
|
|
}
|
|
|
|
@Test
|
|
public void limitKBytesTest() throws Exception
|
|
{
|
|
limits.setReadLimitKBytes(1);
|
|
readAndCheck();
|
|
}
|
|
|
|
@Test
|
|
public void limitTimeTest() throws Exception
|
|
{
|
|
limits.setReadLimitTimeMs(1000);
|
|
readAndCheck();
|
|
}
|
|
|
|
@Test
|
|
public void limitTimeAndKBytesTest() throws Exception
|
|
{
|
|
limits.setReadLimitTimeMs(1000);
|
|
limits.setReadLimitKBytes(1);
|
|
readAndCheck();
|
|
}
|
|
|
|
@Test
|
|
public void fullSpeedReader() throws Exception
|
|
{
|
|
// Check that we have not slowed down reading of large files.
|
|
reader = new DummyAbstractContentReader(LARGE_SIZE, 0);
|
|
reader.setLimits(limits);
|
|
reader.setTransformerDebug(transformerDebug);
|
|
reader.setUseBufferedInputStream(true);
|
|
|
|
readAndCheck();
|
|
}
|
|
|
|
private void readAndCheck() throws Exception
|
|
{
|
|
Exception exception = null;
|
|
|
|
long length = 0;
|
|
long time = System.currentTimeMillis();
|
|
try
|
|
{
|
|
String content = reader.getContentString();
|
|
length = content.length();
|
|
}
|
|
catch(ContentIOException e)
|
|
{
|
|
exception = e;
|
|
}
|
|
time = System.currentTimeMillis() - time;
|
|
|
|
calcMaxMinValues();
|
|
|
|
System.out.printf("Time %04d %04d..%04d length %04d %04d..%04d %s\n",
|
|
time, minTime, maxTime,
|
|
length, minLength, maxLength,
|
|
(exception == null ? "" : exception.getClass().getSimpleName()));
|
|
|
|
assertTrue("Reader is too fast ("+time+"ms range is "+minTime+"..."+maxTime+"ms)", time >= minTime);
|
|
assertTrue("Reader is too slow ("+time+"ms range is "+minTime+"..."+maxTime+"ms)", time <= maxTime);
|
|
|
|
if (exception != null)
|
|
throw exception;
|
|
|
|
assertTrue("Content is too short ("+length+" bytes range is "+minLength+"..."+maxLength+")", length >= minLength);
|
|
assertTrue("Content is too long ("+length+" bytes range is "+minLength+"..."+maxLength+")", length <= maxLength);
|
|
}
|
|
|
|
private void calcMaxMinValues()
|
|
{
|
|
long size = reader.size;
|
|
|
|
long timeout = limits.getTimePair().getValue();
|
|
assertTrue("The test time value ("+timeout+
|
|
"ms) should be lowered given the file size ("+size+
|
|
") and the margin of error (of "+marginOfError(timeout)+"ms)",
|
|
timeout <= 0 || msToBytes(timeout+marginOfError(timeout)) <= size);
|
|
|
|
long readKBytes = limits.getKBytesPair().getValue();
|
|
long readBytes = readKBytes * K;
|
|
assertTrue("The test KByte value ("+readKBytes+
|
|
"K) should be lowered given the file size ("+size+
|
|
") and the margin of error (of "+marginOfError(readBytes)+"bytes)",
|
|
readBytes <= 0 || readBytes+marginOfError(readBytes) <= size);
|
|
|
|
long bytes = (readBytes > 0) ? readBytes : size;
|
|
long readTime = bytesToMs(bytes);
|
|
|
|
minTime = (timeout > 0) ? Math.min(timeout, readTime) : readTime;
|
|
maxTime = minTime + marginOfError(minTime);
|
|
minLength = (timeout > 0) ? msToBytes(minTime-marginOfError(minTime)) : bytes;
|
|
maxLength = (timeout > 0) ? Math.min(msToBytes(maxTime), size) : bytes;
|
|
}
|
|
|
|
private long msToBytes(long ms)
|
|
{
|
|
return (reader.msPerByte > 0)
|
|
? ms / reader.msPerByte
|
|
: ms / MS_PER_MB * M;
|
|
}
|
|
|
|
private long bytesToMs(long bytes)
|
|
{
|
|
return (reader.msPerByte > 0)
|
|
? bytes * reader.msPerByte
|
|
: bytes * MS_PER_MB / M;
|
|
}
|
|
|
|
private long marginOfError(long value)
|
|
{
|
|
return
|
|
value *
|
|
((reader.msPerByte > 0)
|
|
? MARGIN_OF_ERROR_PERCENTAGE_SLOW
|
|
: MARGIN_OF_ERROR_PERCENTAGE_FAST) /
|
|
100;
|
|
}
|
|
|
|
/**
|
|
* A dummy AbstractContentReader that returns a given number of bytes
|
|
* (all 'a') very slowly. There is a configurable delay returning each byte.
|
|
* Used to test timeouts and read limits.
|
|
*/
|
|
public static class DummyAbstractContentReader extends AbstractContentReader
|
|
{
|
|
final long size;
|
|
final long msPerByte;
|
|
|
|
/**
|
|
* @param size of the dummy data
|
|
* @param msPerByte milliseconds between byte reads
|
|
*/
|
|
public DummyAbstractContentReader(long size, long msPerByte)
|
|
{
|
|
super("a");
|
|
this.size = size;
|
|
this.msPerByte = msPerByte;
|
|
}
|
|
|
|
/**
|
|
* @return Returns an instance of the this class
|
|
*/
|
|
@Override
|
|
protected ContentReader createReader() throws ContentIOException
|
|
{
|
|
return new DummyAbstractContentReader(size, msPerByte);
|
|
}
|
|
|
|
@Override
|
|
protected ReadableByteChannel getDirectReadableChannel() throws ContentIOException
|
|
{
|
|
InputStream is = new InputStream()
|
|
{
|
|
long read = 0;
|
|
long start = 0;
|
|
|
|
@Override
|
|
public int read() throws IOException
|
|
{
|
|
if (read >= size)
|
|
return -1;
|
|
|
|
read++;
|
|
|
|
if (msPerByte > 0)
|
|
{
|
|
long elapse = System.currentTimeMillis() - start;
|
|
if (read == 1)
|
|
{
|
|
start = elapse;
|
|
}
|
|
else
|
|
{
|
|
// On Windows it is possible to just wait 1 ms per byte but this
|
|
// does not work on linux hence (end up with a full read taking
|
|
// 40 seconds rather than 5) the need to wait if elapse time
|
|
// is too fast.
|
|
long delay = (read * msPerByte) - elapse;
|
|
if (delay > 0)
|
|
{
|
|
try
|
|
{
|
|
Thread.sleep(delay);
|
|
}
|
|
catch (InterruptedException e)
|
|
{
|
|
// ignore
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 'a';
|
|
}
|
|
|
|
// Just a way to tell AbstractContentReader not to wrap the ChannelInputStream
|
|
// in a BufferedInputStream
|
|
@Override
|
|
public boolean markSupported()
|
|
{
|
|
return true;
|
|
}
|
|
};
|
|
return Channels.newChannel(is);
|
|
}
|
|
|
|
public boolean exists()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public long getLastModified()
|
|
{
|
|
return 0L;
|
|
}
|
|
|
|
public long getSize()
|
|
{
|
|
return size;
|
|
}
|
|
};
|
|
}
|