From a411eb6fe021e4a18affe7923c7433b41342e0d6 Mon Sep 17 00:00:00 2001 From: Matt Ward Date: Thu, 8 Dec 2011 09:57:57 +0000 Subject: [PATCH] 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 --- .../content/caching/CachingContentStore.java | 2 +- .../caching/cleanup/CachedContentCleaner.java | 135 +++++++++++------- .../cleanup/CachedContentCleanupJobTest.java | 34 ++++- .../caching/quota/StandardQuotaStrategy.java | 46 +++--- .../cachingstore/test-cleaner-context.xml | 4 +- .../cachingstore/test-std-quota-context.xml | 4 +- 6 files changed, 141 insertions(+), 84 deletions(-) diff --git a/source/java/org/alfresco/repo/content/caching/CachingContentStore.java b/source/java/org/alfresco/repo/content/caching/CachingContentStore.java index f4b771ea7c..03d6b1de34 100644 --- a/source/java/org/alfresco/repo/content/caching/CachingContentStore.java +++ b/source/java/org/alfresco/repo/content/caching/CachingContentStore.java @@ -54,7 +54,7 @@ public class CachingContentStore implements ContentStore, ApplicationEventPublis { 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 - private final static int numLocks = 32; + private final static int numLocks = 256; private final static ReentrantReadWriteLock[] locks; private ContentStore backingStore; private ContentCache cache; diff --git a/source/java/org/alfresco/repo/content/caching/cleanup/CachedContentCleaner.java b/source/java/org/alfresco/repo/content/caching/cleanup/CachedContentCleaner.java index db315c9f39..f1bd735161 100644 --- a/source/java/org/alfresco/repo/content/caching/cleanup/CachedContentCleaner.java +++ b/source/java/org/alfresco/repo/content/caching/cleanup/CachedContentCleaner.java @@ -40,7 +40,7 @@ import org.springframework.context.ApplicationEventPublisherAware; * * @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 ContentCacheImpl cache; // impl specific functionality required @@ -58,85 +58,102 @@ public class CachedContentCleaner implements FileHandler, ApplicationEventPublis private Date timeFinished; private ApplicationEventPublisher eventPublisher; 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 - * to notify interested parties that the cleaner exists. + * This method MUST be called after the cleaner has been fully constructed + * to notify interested parties that the cleaner exists and to start the actual cleaner thread. */ 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"); } - public void executeAggressive(String reason, long targetReductionBytes) + public synchronized void executeAggressive(String reason, long targetReductionBytes) { this.targetReductionBytes = targetReductionBytes; execute(reason); - this.targetReductionBytes = 0; } - public void execute(String reason) + private synchronized void doClean() { - lock.readLock().lock(); - try + while (running || (!cleanRequested)) { - if (running) + try { - // Do nothing - we only want one cleaner running at a time. - return; + wait(); + } + catch (InterruptedException error) + { + // Nothing to do. } } - finally + + running = true; + if (log.isInfoEnabled()) { - lock.readLock().unlock(); + log.info("Starting cleaner, reason: " + reasonMessage); } - lock.writeLock().lock(); - try + resetStats(); + timeStarted = new Date(); + cache.processFiles(this); + timeFinished = new Date(); + + if (usageTracker != null) { - if (!running) - { - if (log.isInfoEnabled()) - { - log.info("Starting cleaner, reason: " + reason); - } - running = true; - resetStats(); - timeStarted = new Date(); - cache.processFiles(this); - timeFinished = new Date(); - - if (usageTracker != null) - { - usageTracker.setCurrentUsageBytes(newDiskUsage); - } - - running = false; - if (log.isInfoEnabled()) - { - log.info("Finished, duration: " + getDurationSeconds() + "s, seen: " + numFilesSeen + - ", marked: " + numFilesMarked + - ", deleted: " + numFilesDeleted + - " (" + String.format("%.2f", getSizeFilesDeletedMB()) + "MB, " + - sizeFilesDeleted + " bytes)" + - ", target: " + targetReductionBytes + " bytes"); - } - } + usageTracker.setCurrentUsageBytes(newDiskUsage); } - finally + + if (log.isInfoEnabled()) { - lock.writeLock().unlock(); + log.info("Finished, duration: " + getDurationSeconds() + "s, seen: " + numFilesSeen + + ", marked: " + numFilesMarked + + ", deleted: " + numFilesDeleted + + " (" + String.format("%.2f", getSizeFilesDeletedMB()) + "MB, " + + sizeFilesDeleted + " bytes)" + + ", target: " + targetReductionBytes + " bytes"); } + + cleanRequested = false; + this.targetReductionBytes = 0; + running = false; + + notifyAll(); + } + + + public synchronized void execute(String reasonMessage) + { + this.reasonMessage = reasonMessage; + cleanRequested = true; + notifyAll(); } - /** - * - */ private void resetStats() { newDiskUsage = 0; @@ -152,7 +169,7 @@ public class CachedContentCleaner implements FileHandler, ApplicationEventPublis { if (log.isDebugEnabled()) { - log.debug("handle file: " + cachedContentFile); + log.debug("handle file: " + cachedContentFile + " (target reduction: " + targetReductionBytes + " bytes)"); } numFilesSeen++; CacheFileProps props = null; @@ -160,6 +177,11 @@ public class CachedContentCleaner implements FileHandler, ApplicationEventPublis 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. deleted = deleteFilesNow(cachedContentFile); } @@ -299,10 +321,21 @@ public class CachedContentCleaner implements FileHandler, ApplicationEventPublis boolean deleted = cacheFile.delete(); if (deleted) { + if (log.isTraceEnabled()) + { + log.trace("Deleted cache file: " + cacheFile); + } numFilesDeleted++; sizeFilesDeleted += fileSize; Deleter.deleteEmptyParents(cacheFile, cache.getCacheRoot()); } + else + { + if (log.isWarnEnabled()) + { + log.warn("Failed to delete cache file: " + cacheFile); + } + } return deleted; } diff --git a/source/java/org/alfresco/repo/content/caching/cleanup/CachedContentCleanupJobTest.java b/source/java/org/alfresco/repo/content/caching/cleanup/CachedContentCleanupJobTest.java index 4db6fe2d44..12b0d229a9 100644 --- a/source/java/org/alfresco/repo/content/caching/cleanup/CachedContentCleanupJobTest.java +++ b/source/java/org/alfresco/repo/content/caching/cleanup/CachedContentCleanupJobTest.java @@ -87,7 +87,7 @@ public class CachedContentCleanupJobTest @Test - public void filesNotInCacheAreDeleted() + public void filesNotInCacheAreDeleted() throws InterruptedException { cleaner.setMaxDeleteWatchCount(0); int numFiles = 300; // Must be a multiple of number of UrlSource types being tested @@ -108,6 +108,12 @@ public class CachedContentCleanupJobTest // Run cleaner cleaner.execute(); + Thread.sleep(400); + while (cleaner.isRunning()) + { + Thread.sleep(200); + } + // check all files deleted 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. cleaner.execute(); + Thread.sleep(400); + while (cleaner.isRunning()) + { + Thread.sleep(200); + } + // check all 'old' files deleted 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. cleaner.executeAggressive("aggressiveCleanReclaimsTargetSpace()", sevenFilesSize); + Thread.sleep(400); + while (cleaner.isRunning()) + { + Thread.sleep(200); + } + int numDeleted = 0; 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. cleaner.executeAggressive("standardCleanAfterAggressiveFinished()", sevenFilesSize); + Thread.sleep(400); + while (cleaner.isRunning()) + { + Thread.sleep(200); + } + for (int i = 0; i < numFiles; i++) { if (i < 7) @@ -328,7 +352,7 @@ public class CachedContentCleanupJobTest @Test - public void filesInCacheAreNotDeleted() + public void filesInCacheAreNotDeleted() throws InterruptedException { cleaner.setMaxDeleteWatchCount(0); @@ -344,6 +368,12 @@ public class CachedContentCleanupJobTest cleaner.execute(); + Thread.sleep(400); + while (cleaner.isRunning()) + { + Thread.sleep(200); + } + for (int i = 0; i < numFiles; i++) { File cacheFile = new File(cache.getCacheFilePath(url)); diff --git a/source/java/org/alfresco/repo/content/caching/quota/StandardQuotaStrategy.java b/source/java/org/alfresco/repo/content/caching/quota/StandardQuotaStrategy.java index 46e0587b12..084aab4946 100644 --- a/source/java/org/alfresco/repo/content/caching/quota/StandardQuotaStrategy.java +++ b/source/java/org/alfresco/repo/content/caching/quota/StandardQuotaStrategy.java @@ -88,7 +88,7 @@ public class StandardQuotaStrategy implements QuotaManagerStrategy, UsageTracker loadDiskUsage(); // 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 + "%) - vetoing disk write and starting cached content cleaner."); } - runCleanerThread("quota (panic threshold)"); + signalCleanerStart("quota (panic threshold)"); return false; } @@ -203,7 +203,7 @@ public class StandardQuotaStrategy implements QuotaManagerStrategy, UsageTracker log.debug("Usage has reached or exceeded quota limit, limit: " + maxUsageBytes + " bytes, current usage: " + getCurrentUsageBytes() + " bytes."); } - runAggressiveCleanerThread("quota (limit reached)"); + signalAggressiveCleanerStart("quota (limit reached)"); } else if (usageHasReached(cleanThresholdPct)) { @@ -213,7 +213,7 @@ public class StandardQuotaStrategy implements QuotaManagerStrategy, UsageTracker log.debug("Usage has reached " + cleanThresholdPct + "% - starting cached content cleaner."); } - runCleanerThread("quota (clean threshold)"); + signalCleanerStart("quota (clean threshold)"); } return keepNewFile; @@ -223,27 +223,17 @@ public class StandardQuotaStrategy implements QuotaManagerStrategy, UsageTracker /** * 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) - { - long targetReductionBytes = (long) (((double) targetUsagePct / 100) * maxUsageBytes); - cleaner.executeAggressive(reason, targetReductionBytes); - } - else - { - cleaner.execute(reason); - } - } - }; - Thread cleanerThread = new Thread(cleanerRunner, getClass().getSimpleName() + " cleaner"); - cleanerThread.setDaemon(true); - cleanerThread.start(); + if (aggressive) + { + long targetReductionBytes = (long) (((double) targetUsagePct / 100) * maxUsageBytes); + cleaner.executeAggressive(reason, targetReductionBytes); + } + else + { + cleaner.execute(reason); + } } /** @@ -251,9 +241,9 @@ public class StandardQuotaStrategy implements QuotaManagerStrategy, UsageTracker * * @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 */ - private void runAggressiveCleanerThread(final String reason) + private void signalAggressiveCleanerStart(final String reason) { - runCleanerThread(reason, true); + signalCleanerStart(reason, true); } diff --git a/source/test-resources/cachingstore/test-cleaner-context.xml b/source/test-resources/cachingstore/test-cleaner-context.xml index 9525920cfc..f0a88d4c7e 100644 --- a/source/test-resources/cachingstore/test-cleaner-context.xml +++ b/source/test-resources/cachingstore/test-cleaner-context.xml @@ -9,7 +9,9 @@ - + diff --git a/source/test-resources/cachingstore/test-std-quota-context.xml b/source/test-resources/cachingstore/test-std-quota-context.xml index 3ca40fdbd1..da9ee10404 100644 --- a/source/test-resources/cachingstore/test-std-quota-context.xml +++ b/source/test-resources/cachingstore/test-std-quota-context.xml @@ -56,7 +56,9 @@ - +