mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-31 17:39:05 +00:00
Merged BRANCHES/DEV/THOR1 to HEAD:
32573: THOR-659: Caching Content Store blocked readers git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@32628 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -54,7 +54,7 @@ public class CachingContentStore implements ContentStore, ApplicationEventPublis
|
|||||||
{
|
{
|
||||||
private final static Log log = LogFactory.getLog(CachingContentStore.class);
|
private final static Log log = LogFactory.getLog(CachingContentStore.class);
|
||||||
// NUM_LOCKS absolutely must be a power of 2 for the use of locks to be evenly balanced
|
// NUM_LOCKS absolutely must be a power of 2 for the use of locks to be evenly balanced
|
||||||
private final static int numLocks = 32;
|
private final static int numLocks = 256;
|
||||||
private final static ReentrantReadWriteLock[] locks;
|
private final static ReentrantReadWriteLock[] locks;
|
||||||
private ContentStore backingStore;
|
private ContentStore backingStore;
|
||||||
private ContentCache cache;
|
private ContentCache cache;
|
||||||
|
@@ -40,7 +40,7 @@ import org.springframework.context.ApplicationEventPublisherAware;
|
|||||||
*
|
*
|
||||||
* @author Matt Ward
|
* @author Matt Ward
|
||||||
*/
|
*/
|
||||||
public class CachedContentCleaner implements FileHandler, ApplicationEventPublisherAware
|
public class CachedContentCleaner extends Thread implements FileHandler, ApplicationEventPublisherAware
|
||||||
{
|
{
|
||||||
private static final Log log = LogFactory.getLog(CachedContentCleaner.class);
|
private static final Log log = LogFactory.getLog(CachedContentCleaner.class);
|
||||||
private ContentCacheImpl cache; // impl specific functionality required
|
private ContentCacheImpl cache; // impl specific functionality required
|
||||||
@@ -58,53 +58,66 @@ public class CachedContentCleaner implements FileHandler, ApplicationEventPublis
|
|||||||
private Date timeFinished;
|
private Date timeFinished;
|
||||||
private ApplicationEventPublisher eventPublisher;
|
private ApplicationEventPublisher eventPublisher;
|
||||||
private long targetReductionBytes;
|
private long targetReductionBytes;
|
||||||
|
private boolean cleanRequested;
|
||||||
|
private String reasonMessage;
|
||||||
|
|
||||||
|
|
||||||
|
public CachedContentCleaner()
|
||||||
|
{
|
||||||
|
setName(getClass().getSimpleName());
|
||||||
|
setDaemon(true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method should be called after the cleaner has been fully constructed
|
* This method MUST be called after the cleaner has been fully constructed
|
||||||
* to notify interested parties that the cleaner exists.
|
* to notify interested parties that the cleaner exists and to start the actual cleaner thread.
|
||||||
*/
|
*/
|
||||||
public void init()
|
public void init()
|
||||||
{
|
{
|
||||||
eventPublisher.publishEvent(new CachedContentCleanerCreatedEvent(this));
|
eventPublisher.publishEvent(new CachedContentCleanerCreatedEvent(this));
|
||||||
|
start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void execute()
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
doClean();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void execute()
|
||||||
{
|
{
|
||||||
execute("none specified");
|
execute("none specified");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void executeAggressive(String reason, long targetReductionBytes)
|
public synchronized void executeAggressive(String reason, long targetReductionBytes)
|
||||||
{
|
{
|
||||||
this.targetReductionBytes = targetReductionBytes;
|
this.targetReductionBytes = targetReductionBytes;
|
||||||
execute(reason);
|
execute(reason);
|
||||||
this.targetReductionBytes = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void execute(String reason)
|
private synchronized void doClean()
|
||||||
|
{
|
||||||
|
while (running || (!cleanRequested))
|
||||||
{
|
{
|
||||||
lock.readLock().lock();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (running)
|
wait();
|
||||||
|
}
|
||||||
|
catch (InterruptedException error)
|
||||||
{
|
{
|
||||||
// Do nothing - we only want one cleaner running at a time.
|
// Nothing to do.
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
running = true;
|
||||||
lock.readLock().unlock();
|
|
||||||
}
|
|
||||||
lock.writeLock().lock();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!running)
|
|
||||||
{
|
|
||||||
if (log.isInfoEnabled())
|
if (log.isInfoEnabled())
|
||||||
{
|
{
|
||||||
log.info("Starting cleaner, reason: " + reason);
|
log.info("Starting cleaner, reason: " + reasonMessage);
|
||||||
}
|
}
|
||||||
running = true;
|
|
||||||
resetStats();
|
resetStats();
|
||||||
timeStarted = new Date();
|
timeStarted = new Date();
|
||||||
cache.processFiles(this);
|
cache.processFiles(this);
|
||||||
@@ -115,7 +128,6 @@ public class CachedContentCleaner implements FileHandler, ApplicationEventPublis
|
|||||||
usageTracker.setCurrentUsageBytes(newDiskUsage);
|
usageTracker.setCurrentUsageBytes(newDiskUsage);
|
||||||
}
|
}
|
||||||
|
|
||||||
running = false;
|
|
||||||
if (log.isInfoEnabled())
|
if (log.isInfoEnabled())
|
||||||
{
|
{
|
||||||
log.info("Finished, duration: " + getDurationSeconds() + "s, seen: " + numFilesSeen +
|
log.info("Finished, duration: " + getDurationSeconds() + "s, seen: " + numFilesSeen +
|
||||||
@@ -125,18 +137,23 @@ public class CachedContentCleaner implements FileHandler, ApplicationEventPublis
|
|||||||
sizeFilesDeleted + " bytes)" +
|
sizeFilesDeleted + " bytes)" +
|
||||||
", target: " + targetReductionBytes + " bytes");
|
", target: " + targetReductionBytes + " bytes");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cleanRequested = false;
|
||||||
|
this.targetReductionBytes = 0;
|
||||||
|
running = false;
|
||||||
|
|
||||||
|
notifyAll();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
finally
|
|
||||||
|
public synchronized void execute(String reasonMessage)
|
||||||
{
|
{
|
||||||
lock.writeLock().unlock();
|
this.reasonMessage = reasonMessage;
|
||||||
}
|
cleanRequested = true;
|
||||||
|
notifyAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private void resetStats()
|
private void resetStats()
|
||||||
{
|
{
|
||||||
newDiskUsage = 0;
|
newDiskUsage = 0;
|
||||||
@@ -152,7 +169,7 @@ public class CachedContentCleaner implements FileHandler, ApplicationEventPublis
|
|||||||
{
|
{
|
||||||
if (log.isDebugEnabled())
|
if (log.isDebugEnabled())
|
||||||
{
|
{
|
||||||
log.debug("handle file: " + cachedContentFile);
|
log.debug("handle file: " + cachedContentFile + " (target reduction: " + targetReductionBytes + " bytes)");
|
||||||
}
|
}
|
||||||
numFilesSeen++;
|
numFilesSeen++;
|
||||||
CacheFileProps props = null;
|
CacheFileProps props = null;
|
||||||
@@ -160,6 +177,11 @@ public class CachedContentCleaner implements FileHandler, ApplicationEventPublis
|
|||||||
|
|
||||||
if (targetReductionBytes > 0 && sizeFilesDeleted < targetReductionBytes)
|
if (targetReductionBytes > 0 && sizeFilesDeleted < targetReductionBytes)
|
||||||
{
|
{
|
||||||
|
if (log.isDebugEnabled())
|
||||||
|
{
|
||||||
|
log.debug("Target reduction " + targetReductionBytes +
|
||||||
|
" bytes not yet reached. Deleted so far: " + sizeFilesDeleted);
|
||||||
|
}
|
||||||
// Aggressive clean mode, delete file straight away.
|
// Aggressive clean mode, delete file straight away.
|
||||||
deleted = deleteFilesNow(cachedContentFile);
|
deleted = deleteFilesNow(cachedContentFile);
|
||||||
}
|
}
|
||||||
@@ -299,10 +321,21 @@ public class CachedContentCleaner implements FileHandler, ApplicationEventPublis
|
|||||||
boolean deleted = cacheFile.delete();
|
boolean deleted = cacheFile.delete();
|
||||||
if (deleted)
|
if (deleted)
|
||||||
{
|
{
|
||||||
|
if (log.isTraceEnabled())
|
||||||
|
{
|
||||||
|
log.trace("Deleted cache file: " + cacheFile);
|
||||||
|
}
|
||||||
numFilesDeleted++;
|
numFilesDeleted++;
|
||||||
sizeFilesDeleted += fileSize;
|
sizeFilesDeleted += fileSize;
|
||||||
Deleter.deleteEmptyParents(cacheFile, cache.getCacheRoot());
|
Deleter.deleteEmptyParents(cacheFile, cache.getCacheRoot());
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (log.isWarnEnabled())
|
||||||
|
{
|
||||||
|
log.warn("Failed to delete cache file: " + cacheFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return deleted;
|
return deleted;
|
||||||
}
|
}
|
||||||
|
@@ -87,7 +87,7 @@ public class CachedContentCleanupJobTest
|
|||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void filesNotInCacheAreDeleted()
|
public void filesNotInCacheAreDeleted() throws InterruptedException
|
||||||
{
|
{
|
||||||
cleaner.setMaxDeleteWatchCount(0);
|
cleaner.setMaxDeleteWatchCount(0);
|
||||||
int numFiles = 300; // Must be a multiple of number of UrlSource types being tested
|
int numFiles = 300; // Must be a multiple of number of UrlSource types being tested
|
||||||
@@ -108,6 +108,12 @@ public class CachedContentCleanupJobTest
|
|||||||
// Run cleaner
|
// Run cleaner
|
||||||
cleaner.execute();
|
cleaner.execute();
|
||||||
|
|
||||||
|
Thread.sleep(400);
|
||||||
|
while (cleaner.isRunning())
|
||||||
|
{
|
||||||
|
Thread.sleep(200);
|
||||||
|
}
|
||||||
|
|
||||||
// check all files deleted
|
// check all files deleted
|
||||||
for (File file : files)
|
for (File file : files)
|
||||||
{
|
{
|
||||||
@@ -149,6 +155,12 @@ public class CachedContentCleanupJobTest
|
|||||||
// old the test will fail and it will be necessary to rethink how to test this.
|
// old the test will fail and it will be necessary to rethink how to test this.
|
||||||
cleaner.execute();
|
cleaner.execute();
|
||||||
|
|
||||||
|
Thread.sleep(400);
|
||||||
|
while (cleaner.isRunning())
|
||||||
|
{
|
||||||
|
Thread.sleep(200);
|
||||||
|
}
|
||||||
|
|
||||||
// check all 'old' files deleted
|
// check all 'old' files deleted
|
||||||
for (File file : oldFiles)
|
for (File file : oldFiles)
|
||||||
{
|
{
|
||||||
@@ -186,6 +198,12 @@ public class CachedContentCleanupJobTest
|
|||||||
// Since some of the newer files are not in the cache, it will delete those.
|
// Since some of the newer files are not in the cache, it will delete those.
|
||||||
cleaner.executeAggressive("aggressiveCleanReclaimsTargetSpace()", sevenFilesSize);
|
cleaner.executeAggressive("aggressiveCleanReclaimsTargetSpace()", sevenFilesSize);
|
||||||
|
|
||||||
|
Thread.sleep(400);
|
||||||
|
while (cleaner.isRunning())
|
||||||
|
{
|
||||||
|
Thread.sleep(200);
|
||||||
|
}
|
||||||
|
|
||||||
int numDeleted = 0;
|
int numDeleted = 0;
|
||||||
|
|
||||||
for (File f : files)
|
for (File f : files)
|
||||||
@@ -239,6 +257,12 @@ public class CachedContentCleanupJobTest
|
|||||||
// Since some of the newer files are not in the cache, it will delete those too.
|
// Since some of the newer files are not in the cache, it will delete those too.
|
||||||
cleaner.executeAggressive("standardCleanAfterAggressiveFinished()", sevenFilesSize);
|
cleaner.executeAggressive("standardCleanAfterAggressiveFinished()", sevenFilesSize);
|
||||||
|
|
||||||
|
Thread.sleep(400);
|
||||||
|
while (cleaner.isRunning())
|
||||||
|
{
|
||||||
|
Thread.sleep(200);
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < numFiles; i++)
|
for (int i = 0; i < numFiles; i++)
|
||||||
{
|
{
|
||||||
if (i < 7)
|
if (i < 7)
|
||||||
@@ -328,7 +352,7 @@ public class CachedContentCleanupJobTest
|
|||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void filesInCacheAreNotDeleted()
|
public void filesInCacheAreNotDeleted() throws InterruptedException
|
||||||
{
|
{
|
||||||
cleaner.setMaxDeleteWatchCount(0);
|
cleaner.setMaxDeleteWatchCount(0);
|
||||||
|
|
||||||
@@ -344,6 +368,12 @@ public class CachedContentCleanupJobTest
|
|||||||
|
|
||||||
cleaner.execute();
|
cleaner.execute();
|
||||||
|
|
||||||
|
Thread.sleep(400);
|
||||||
|
while (cleaner.isRunning())
|
||||||
|
{
|
||||||
|
Thread.sleep(200);
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < numFiles; i++)
|
for (int i = 0; i < numFiles; i++)
|
||||||
{
|
{
|
||||||
File cacheFile = new File(cache.getCacheFilePath(url));
|
File cacheFile = new File(cache.getCacheFilePath(url));
|
||||||
|
@@ -88,7 +88,7 @@ public class StandardQuotaStrategy implements QuotaManagerStrategy, UsageTracker
|
|||||||
|
|
||||||
loadDiskUsage();
|
loadDiskUsage();
|
||||||
// Run the cleaner thread so that it can update the disk usage more accurately.
|
// Run the cleaner thread so that it can update the disk usage more accurately.
|
||||||
runCleanerThread("quota (init)");
|
signalCleanerStart("quota (init)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ public class StandardQuotaStrategy implements QuotaManagerStrategy, UsageTracker
|
|||||||
log.debug("Panic threshold reached (" + panicThresholdPct +
|
log.debug("Panic threshold reached (" + panicThresholdPct +
|
||||||
"%) - vetoing disk write and starting cached content cleaner.");
|
"%) - vetoing disk write and starting cached content cleaner.");
|
||||||
}
|
}
|
||||||
runCleanerThread("quota (panic threshold)");
|
signalCleanerStart("quota (panic threshold)");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ public class StandardQuotaStrategy implements QuotaManagerStrategy, UsageTracker
|
|||||||
log.debug("Usage has reached or exceeded quota limit, limit: " + maxUsageBytes +
|
log.debug("Usage has reached or exceeded quota limit, limit: " + maxUsageBytes +
|
||||||
" bytes, current usage: " + getCurrentUsageBytes() + " bytes.");
|
" bytes, current usage: " + getCurrentUsageBytes() + " bytes.");
|
||||||
}
|
}
|
||||||
runAggressiveCleanerThread("quota (limit reached)");
|
signalAggressiveCleanerStart("quota (limit reached)");
|
||||||
}
|
}
|
||||||
else if (usageHasReached(cleanThresholdPct))
|
else if (usageHasReached(cleanThresholdPct))
|
||||||
{
|
{
|
||||||
@@ -213,7 +213,7 @@ public class StandardQuotaStrategy implements QuotaManagerStrategy, UsageTracker
|
|||||||
log.debug("Usage has reached " + cleanThresholdPct + "% - starting cached content cleaner.");
|
log.debug("Usage has reached " + cleanThresholdPct + "% - starting cached content cleaner.");
|
||||||
}
|
}
|
||||||
|
|
||||||
runCleanerThread("quota (clean threshold)");
|
signalCleanerStart("quota (clean threshold)");
|
||||||
}
|
}
|
||||||
|
|
||||||
return keepNewFile;
|
return keepNewFile;
|
||||||
@@ -223,12 +223,7 @@ public class StandardQuotaStrategy implements QuotaManagerStrategy, UsageTracker
|
|||||||
/**
|
/**
|
||||||
* Run the cleaner in a new thread.
|
* Run the cleaner in a new thread.
|
||||||
*/
|
*/
|
||||||
private void runCleanerThread(final String reason, final boolean aggressive)
|
private void signalCleanerStart(final String reason, final boolean aggressive)
|
||||||
{
|
|
||||||
Runnable cleanerRunner = new Runnable()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
{
|
||||||
if (aggressive)
|
if (aggressive)
|
||||||
{
|
{
|
||||||
@@ -240,20 +235,15 @@ public class StandardQuotaStrategy implements QuotaManagerStrategy, UsageTracker
|
|||||||
cleaner.execute(reason);
|
cleaner.execute(reason);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
Thread cleanerThread = new Thread(cleanerRunner, getClass().getSimpleName() + " cleaner");
|
|
||||||
cleanerThread.setDaemon(true);
|
|
||||||
cleanerThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run a non-aggressive clean up job in a new thread.
|
* Run a non-aggressive clean up job in a new thread.
|
||||||
*
|
*
|
||||||
* @param reason
|
* @param reason
|
||||||
*/
|
*/
|
||||||
private void runCleanerThread(final String reason)
|
private void signalCleanerStart(final String reason)
|
||||||
{
|
{
|
||||||
runCleanerThread(reason, false);
|
signalCleanerStart(reason, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -261,9 +251,9 @@ public class StandardQuotaStrategy implements QuotaManagerStrategy, UsageTracker
|
|||||||
*
|
*
|
||||||
* @param reason
|
* @param reason
|
||||||
*/
|
*/
|
||||||
private void runAggressiveCleanerThread(final String reason)
|
private void signalAggressiveCleanerStart(final String reason)
|
||||||
{
|
{
|
||||||
runCleanerThread(reason, true);
|
signalCleanerStart(reason, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -9,7 +9,9 @@
|
|||||||
<property name="pauseMillis" value="0"/>
|
<property name="pauseMillis" value="0"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="cachedContentCleaner" class="org.alfresco.repo.content.caching.cleanup.CachedContentCleaner">
|
<bean id="cachedContentCleaner"
|
||||||
|
class="org.alfresco.repo.content.caching.cleanup.CachedContentCleaner"
|
||||||
|
init-method="init">
|
||||||
<property name="maxDeleteWatchCount" value="1"/>
|
<property name="maxDeleteWatchCount" value="1"/>
|
||||||
<property name="cache" ref="contentCache"/>
|
<property name="cache" ref="contentCache"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
@@ -56,7 +56,9 @@
|
|||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
|
|
||||||
<bean id="cachedContentCleaner" class="org.alfresco.repo.content.caching.cleanup.CachedContentCleaner">
|
<bean id="cachedContentCleaner"
|
||||||
|
class="org.alfresco.repo.content.caching.cleanup.CachedContentCleaner"
|
||||||
|
init-method="init">
|
||||||
<property name="maxDeleteWatchCount" value="0"/><!-- zero is NOT recommmend in production -->
|
<property name="maxDeleteWatchCount" value="0"/><!-- zero is NOT recommmend in production -->
|
||||||
<property name="cache" ref="contentCache"/>
|
<property name="cache" ref="contentCache"/>
|
||||||
<property name="usageTracker" ref="quotaManager"/>
|
<property name="usageTracker" ref="quotaManager"/>
|
||||||
|
Reference in New Issue
Block a user