diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java index 59a46aef4e..7017ae63dc 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedLocalCache.java @@ -810,8 +810,36 @@ void afterWrite(@Nullable Node node, Runnable task, long now) { if (buffersWrites()) { writeQueue().add(task); } - lazySetDrainStatus(REQUIRED); - scheduleDrainBuffers(); + scheduleAfterWrite(); + } + + /** + * Conditionally schedules the asynchronous maintenance task after a write operation. If the + * task status was IDLE or REQUIRED then the maintenance task is scheduled immediately. If it + * is already processing then it is set to transition to REQUIRED upon completion so that a new + * execution triggered by the next operation. + */ + void scheduleAfterWrite() { + for (;;) { + switch (drainStatus()) { + case IDLE: + casDrainStatus(IDLE, REQUIRED); + scheduleDrainBuffers(); + return; + case REQUIRED: + scheduleDrainBuffers(); + return; + case PROCESSING_TO_IDLE: + if (casDrainStatus(PROCESSING_TO_IDLE, PROCESSING_TO_REQUIRED)) { + return; + } + continue; + case PROCESSING_TO_REQUIRED: + return; + default: + throw new IllegalStateException(); + } + } } /** @@ -819,15 +847,16 @@ void afterWrite(@Nullable Node node, Runnable task, long now) { * replacement policy. If the executor rejects the task then it is run directly. */ void scheduleDrainBuffers() { - if (drainStatus() == PROCESSING) { + if (drainStatus() >= PROCESSING_TO_IDLE) { return; } if (evictionLock.tryLock()) { try { - if (drainStatus() == PROCESSING) { + int drainStatus = drainStatus(); + if (drainStatus >= PROCESSING_TO_IDLE) { return; } - lazySetDrainStatus(PROCESSING); + lazySetDrainStatus(PROCESSING_TO_IDLE); executor().execute(drainBuffersTask); } catch (Throwable t) { logger.log(Level.WARNING, "Exception thrown when submitting maintenance task", t); @@ -855,10 +884,12 @@ public void cleanUp() { void performCleanUp() { evictionLock.lock(); try { - lazySetDrainStatus(PROCESSING); + lazySetDrainStatus(PROCESSING_TO_IDLE); maintenance(); } finally { - casDrainStatus(PROCESSING, IDLE); + if ((drainStatus() != PROCESSING_TO_IDLE) || !casDrainStatus(PROCESSING_TO_IDLE, IDLE)) { + lazySetDrainStatus(REQUIRED); + } evictionLock.unlock(); } } @@ -3001,8 +3032,10 @@ static abstract class DrainStatusRef extends PadDrainStatus { static final int IDLE = 0; /** A drain is required due to a pending write modification. */ static final int REQUIRED = 1; - /** A drain is in progress. */ - static final int PROCESSING = 2; + /** A drain is in progress and will transition to idle. */ + static final int PROCESSING_TO_IDLE = 2; + /** A drain is in progress and will transition to required. */ + static final int PROCESSING_TO_REQUIRED = 3; /** The draining status of the buffers. */ volatile int drainStatus = IDLE; @@ -3018,7 +3051,8 @@ boolean shouldDrainBuffers(boolean delayable) { return !delayable; case REQUIRED: return true; - case PROCESSING: + case PROCESSING_TO_IDLE: + case PROCESSING_TO_REQUIRED: return false; default: throw new IllegalStateException(); diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java index 30fdf8b3e6..a820ec542e 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java @@ -15,6 +15,9 @@ */ package com.github.benmanes.caffeine.cache; +import static com.github.benmanes.caffeine.cache.BLCHeader.DrainStatusRef.IDLE; +import static com.github.benmanes.caffeine.cache.BLCHeader.DrainStatusRef.PROCESSING_TO_IDLE; +import static com.github.benmanes.caffeine.cache.BLCHeader.DrainStatusRef.PROCESSING_TO_REQUIRED; import static com.github.benmanes.caffeine.cache.BLCHeader.DrainStatusRef.REQUIRED; import static com.github.benmanes.caffeine.cache.testing.HasRemovalNotifications.hasRemovalNotifications; import static com.github.benmanes.caffeine.cache.testing.HasStats.hasEvictionCount; @@ -28,6 +31,7 @@ import static org.hamcrest.Matchers.nullValue; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -35,6 +39,8 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import org.mockito.Matchers; +import org.mockito.Mockito; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @@ -58,6 +64,7 @@ import com.github.benmanes.caffeine.testing.Awaits; import com.github.benmanes.caffeine.testing.ConcurrentTestHarness; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.ThreadFactoryBuilder; @@ -76,6 +83,46 @@ static BoundedLocalCache asBoundedLocalCache(Cache) cache.asMap(); } + @Test + public void scheduleAfterWrite() { + BoundedLocalCache cache = new BoundedLocalCache( + Caffeine.newBuilder(), /* loader */ null, /* async */ false) { + @Override void scheduleDrainBuffers() {} + }; + Map transitions = ImmutableMap.of( + IDLE, REQUIRED, + REQUIRED, REQUIRED, + PROCESSING_TO_IDLE, PROCESSING_TO_REQUIRED, + PROCESSING_TO_REQUIRED, PROCESSING_TO_REQUIRED); + transitions.forEach((start, end) -> { + cache.drainStatus = start; + cache.scheduleAfterWrite(); + assertThat(cache.drainStatus, is(end)); + }); + } + + @Test + public void scheduleDrainBuffers() { + Executor executor = Mockito.mock(Executor.class); + BoundedLocalCache cache = new BoundedLocalCache( + Caffeine.newBuilder().executor(executor), /* loader */ null, /* async */ false) {}; + Map transitions = ImmutableMap.of( + IDLE, PROCESSING_TO_IDLE, + REQUIRED, PROCESSING_TO_IDLE, + PROCESSING_TO_IDLE, PROCESSING_TO_IDLE, + PROCESSING_TO_REQUIRED, PROCESSING_TO_REQUIRED); + transitions.forEach((start, end) -> { + cache.drainStatus = start; + cache.scheduleDrainBuffers(); + assertThat(cache.drainStatus, is(end)); + + if (start != end) { + Mockito.verify(executor).execute(Matchers.any()); + Mockito.reset(executor); + } + }); + } + @Test public void putWeighted_noOverflow() { Cache cache = Caffeine.newBuilder() diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/Stresser.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/Stresser.java index d7819ef6d5..4b04437754 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/Stresser.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/Stresser.java @@ -39,7 +39,8 @@ * @author ben.manes@gmail.com (Ben Manes) */ public final class Stresser { - private static final String[] STATUS = { "Idle", "Required", "Processing" }; + private static final String[] STATUS = + { "Idle", "Required", "Processing -> Idle", "Processing -> Required" }; private static final int THREADS = 2 * Runtime.getRuntime().availableProcessors(); private static final int WRITE_MAX_SIZE = (1 << 12); private static final int TOTAL_KEYS = (1 << 20); diff --git a/config/checkstyle/checkstyle.xsl b/config/checkstyle/checkstyle.xsl deleted file mode 100644 index 955b7bff59..0000000000 --- a/config/checkstyle/checkstyle.xsl +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -

CheckStyle Audit

Designed for use with CheckStyle and Ant.
-
- - - -
- - - -
- - - - -
- - - - -
- - - - -

Files

- - - - - - - - - - - - - - - -
NameErrors
-
- - - - -

File

- - - - - - - - - - - - - - -
Error DescriptionLine
- Back to top -
- - - -

Summary

- - - - - - - - - - - - -
FilesErrors
-
- - - - a - b - - -
- - diff --git a/config/pmd/rulesSets.xml b/config/pmd/rulesSets.xml index 3fb175c66b..e63f102581 100644 --- a/config/pmd/rulesSets.xml +++ b/config/pmd/rulesSets.xml @@ -13,6 +13,7 @@ + diff --git a/gradle/code_quality.gradle b/gradle/code_quality.gradle index be8bcaccdf..29d7d7c479 100644 --- a/gradle/code_quality.gradle +++ b/gradle/code_quality.gradle @@ -77,10 +77,9 @@ tasks.withType(Test) { tasks.withType(Checkstyle) { enabled = System.properties.containsKey('checkstyle') group = 'Checkstyle' - doLast { - ant.xslt(in: "${buildDir}/reports/checkstyle/main.xml", - style: "//${rootDir}/config/checkstyle/checkstyle.xsl", - out:"${buildDir}/reports/checkstyle/checkstyle.html") + reports { + xml.enabled = false + html.enabled = true } } diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 0353d25f96..390444b536 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -57,7 +57,7 @@ ext { ehcache2: '2.10.1-55', ehcache3: '3.0.0.m5', high_scale_lib: '1.0.6', - infinispan: '8.2.0.CR1', + infinispan: '8.2.0.Final', jackrabbit: '1.3.16', jamm: '0.3.1', java_object_layout: '0.4',