From 8121c1d2f24adce2f768ae2303b2cce398426070 Mon Sep 17 00:00:00 2001 From: Ben Manes Date: Sun, 26 Apr 2020 22:11:50 -0700 Subject: [PATCH] Fixed race causing an incorrect removal cause (fixes #412) This bug manifests due to an incorrectly optimized `Map.remove(key)` implementation when there is no CacheWriter specified. If there is, an alternative implementation is used which does not suffer from this mistake. This change now uses that version in all cases. The explicit removal and an eviction of the same entry can occur concurrently. If the removal wins the race, it is responsible for retiring the entry. However even after that entry is discarded, an further evictions may be necessary to meet the bounding criteria (e.g. weighted entries). Therefore the eviction thread eagerly discards the entry from the policy's data structures so that it can continue its evaluation. While the entry is synchronized in both cases, the eviction thread could lose the removal but retire the entry first. This lead to the removal seeing a dead entry and miscommunicating the cause to the listener. There is a modest performance difference in a microbenchmark because of a slightly coarser locking. However most of the differences between libraries is whether they decide to be linearizable or perform an optimistic check to no-op if the entry is absent. The optimistic check avoids locking but also can lead to a removal to not block waiting for an in-flight computation to complete. While a valid choice, some users have expressed a desire for removal to be pessimistic and is rare enough that we follow their wishes. --- .../benmanes/caffeine/cache/BasicCache.java | 6 +- .../caffeine/cache/PutRemoveBenchmark.java | 108 ++++++++++++++++++ .../benmanes/caffeine/cache/impl/Cache2k.java | 7 +- .../caffeine/cache/impl/CaffeineCache.java | 5 + .../caffeine/cache/impl/Collision.java | 5 + .../cache/impl/ConcurrentMapCache.java | 5 + .../caffeine/cache/impl/Ehcache3.java | 5 + .../cache/impl/ElasticSearchCache.java | 5 + .../caffeine/cache/impl/ExpiringMapCache.java | 5 + .../caffeine/cache/impl/GuavaCache.java | 5 + .../cache/impl/LinkedHashMapCache.java | 7 ++ .../benmanes/caffeine/cache/impl/TCache.java | 5 + .../caffeine/cache/BoundedLocalCache.java | 50 -------- .../github/benmanes/caffeine/cache/Cache.java | 2 +- .../caffeine/cache/issues/Issue412Test.java | 103 +++++++++++++++++ .../cache/testing/RemovalListeners.java | 7 +- .../testing/ConcurrentTestHarness.java | 2 +- checksum.xml | 7 ++ config/pmd/rulesSets.xml | 1 + gradle/codeQuality.gradle | 2 - gradle/dependencies.gradle | 27 ++--- gradle/wrapper/gradle-wrapper.properties | 2 +- 22 files changed, 295 insertions(+), 76 deletions(-) create mode 100644 caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/PutRemoveBenchmark.java create mode 100644 caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue412Test.java diff --git a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/BasicCache.java b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/BasicCache.java index b7b3c0a8c0..22eaf1448b 100644 --- a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/BasicCache.java +++ b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/BasicCache.java @@ -26,12 +26,14 @@ public interface BasicCache { /** Returns the value stored in the cache, or null if not present. */ - @Nullable - V get(@NonNull K key); + @Nullable V get(@NonNull K key); /** Stores the value into the cache, replacing an existing mapping if present. */ void put(@NonNull K key, @NonNull V value); + /** Removes the entry from the cache, if present. */ + void remove(@NonNull K key); + /** Invalidates all entries from the cache. */ void clear(); diff --git a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/PutRemoveBenchmark.java b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/PutRemoveBenchmark.java new file mode 100644 index 0000000000..40f5971d52 --- /dev/null +++ b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/PutRemoveBenchmark.java @@ -0,0 +1,108 @@ +/* + * Copyright 2014 Ben Manes. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.benmanes.caffeine.cache; + +import java.util.Random; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Group; +import org.openjdk.jmh.annotations.GroupThreads; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; + +import site.ycsb.generator.NumberGenerator; +import site.ycsb.generator.ScrambledZipfianGenerator; + +/** + * A benchmark that loosely evaluates the put and removal performance of the cache. The cache is + * written using a Zipf distribution of keys is used to incur lock contention on popular entries. + *

+ * The performance of this benchmark is expected to be unrealistic due to removing entries. A cache + * that optimistically skips locking when the entry is absent receives a benefit, but sacrifices + * linearizability with a computation. In real-world usages the rate of explicit writes is + * relatively rare compared to reads. Thus, this benchmark is only for diagnosing performance + * concerns and should not be used to compare implementations. + *

+ *

{@code
+ *   ./gradlew jmh -PincludePattern=PutRemoveBenchmark
+ * }
+ * + * @author ben.manes@gmail.com (Ben Manes) + */ +@State(Scope.Group) +public class PutRemoveBenchmark { + private static final int SIZE = (2 << 14); + private static final int MASK = SIZE - 1; + private static final int ITEMS = SIZE / 3; + + @Param({ + "Caffeine", + "LinkedHashMap_Lru", + "ConcurrentHashMap", + "ConcurrentLinkedHashMap", + "Guava", + "Cache2k", + "Ehcache3", + }) + CacheType cacheType; + + BasicCache cache; + Integer[] ints; + + @State(Scope.Thread) + public static class ThreadState { + static final Random random = new Random(); + int index = random.nextInt(); + } + + @Setup + public void setup() { + ints = new Integer[SIZE]; + cache = cacheType.create(2 * SIZE); + + // Enforce full initialization of internal structures + for (int i = 0; i < 2 * SIZE; i++) { + cache.put(i, Boolean.TRUE); + } + cache.clear(); + + // Populate with a realistic access distribution + NumberGenerator generator = new ScrambledZipfianGenerator(ITEMS); + for (int i = 0; i < SIZE; i++) { + ints[i] = generator.nextValue().intValue(); + cache.put(ints[i], Boolean.TRUE); + } + } + + @TearDown(Level.Iteration) + public void tearDown() { + cache.cleanUp(); + } + + @Benchmark @Group @GroupThreads(4) + public void put(ThreadState threadState) { + cache.put(ints[threadState.index++ & MASK], Boolean.TRUE); + } + + @Benchmark @Group @GroupThreads(4) + public void remove(ThreadState threadState) { + cache.remove(ints[threadState.index++ & MASK]); + } +} diff --git a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/Cache2k.java b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/Cache2k.java index 05838108f5..1a291b8c98 100644 --- a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/Cache2k.java +++ b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/Cache2k.java @@ -28,7 +28,7 @@ public final class Cache2k implements BasicCache { @SuppressWarnings("unchecked") public Cache2k(int maximumSize) { - cache = (Cache) Cache2kBuilder.forUnknownTypes() + cache = Cache2kBuilder.forUnknownTypes() .entryCapacity(maximumSize) .eternal(true) .build(); @@ -44,6 +44,11 @@ public void put(K key, V value) { cache.put(key, value); } + @Override + public void remove(K key) { + cache.remove(key); + } + @Override public void clear() { cache.clear(); diff --git a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/CaffeineCache.java b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/CaffeineCache.java index aab8ab9c3d..b8e8fe60ec 100644 --- a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/CaffeineCache.java +++ b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/CaffeineCache.java @@ -46,6 +46,11 @@ public void put(K key, V value) { map.put(key, value); } + @Override + public void remove(K key) { + map.remove(key); + } + @Override public void clear() { cache.invalidateAll(); diff --git a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/Collision.java b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/Collision.java index 36205fa36f..75ccaf11dc 100644 --- a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/Collision.java +++ b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/Collision.java @@ -43,6 +43,11 @@ public void put(K key, V value) { cache.putReplace(key, value); } + @Override + public void remove(K key) { + cache.remove(key); + } + @Override public void clear() { cache.clear(); diff --git a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/ConcurrentMapCache.java b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/ConcurrentMapCache.java index 11395ac064..0004592a4d 100644 --- a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/ConcurrentMapCache.java +++ b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/ConcurrentMapCache.java @@ -41,6 +41,11 @@ public void put(K key, V value) { map.put(key, value); } + @Override + public void remove(K key) { + map.remove(key); + } + @Override public void clear() { map.clear(); diff --git a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/Ehcache3.java b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/Ehcache3.java index d7aa1535b1..2ca3b7d400 100644 --- a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/Ehcache3.java +++ b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/Ehcache3.java @@ -50,6 +50,11 @@ public void put(K key, V value) { cache.put(key, value); } + @Override + public void remove(K key) { + cache.remove(key); + } + @Override public void clear() { cache.clear(); diff --git a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/ElasticSearchCache.java b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/ElasticSearchCache.java index 67e57df3fa..9d4df3f13d 100644 --- a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/ElasticSearchCache.java +++ b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/ElasticSearchCache.java @@ -42,6 +42,11 @@ public void put(K key, V value) { cache.put(key, value); } + @Override + public void remove(K key) { + cache.invalidate(key); + } + @Override public void clear() { cache.invalidateAll(); diff --git a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/ExpiringMapCache.java b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/ExpiringMapCache.java index 962536f42f..90149ad95e 100644 --- a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/ExpiringMapCache.java +++ b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/ExpiringMapCache.java @@ -43,6 +43,11 @@ public void put(K key, V value) { cache.put(key, value); } + @Override + public void remove(K key) { + cache.remove(key); + } + @Override public void clear() { cache.clear(); diff --git a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/GuavaCache.java b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/GuavaCache.java index fab8c176ca..25055e29b1 100644 --- a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/GuavaCache.java +++ b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/GuavaCache.java @@ -50,6 +50,11 @@ public void put(K key, V value) { cache.put(key, value); } + @Override + public void remove(K key) { + cache.invalidate(key); + } + @Override public void clear() { cache.invalidateAll(); diff --git a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/LinkedHashMapCache.java b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/LinkedHashMapCache.java index 0e022a298a..245b79ad60 100644 --- a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/LinkedHashMapCache.java +++ b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/LinkedHashMapCache.java @@ -44,6 +44,13 @@ public void put(K key, V value) { } } + @Override + public void remove(K key) { + synchronized (map) { + map.remove(key); + } + } + @Override public void clear() { synchronized (map) { diff --git a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/TCache.java b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/TCache.java index d6ba1358d8..2500cbab86 100644 --- a/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/TCache.java +++ b/caffeine/src/jmh/java/com/github/benmanes/caffeine/cache/impl/TCache.java @@ -47,6 +47,11 @@ public void put(K key, V value) { cache.put(key, value); } + @Override + public void remove(K key) { + cache.remove(key); + } + @Override public void clear() { cache.clear(); 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 c5d03e1ce1..de3d2d9aa2 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 @@ -2088,56 +2088,6 @@ public Map getAllPresent(Iterable keys) { @Override public @Nullable V remove(Object key) { - return hasWriter() - ? removeWithWriter(key) - : removeNoWriter(key); - } - - /** - * Removes the mapping for a key without notifying the writer. - * - * @param key key whose mapping is to be removed - * @return the removed value or null if no mapping was found - */ - @Nullable V removeNoWriter(Object key) { - Node node = data.remove(nodeFactory.newLookupKey(key)); - if (node == null) { - return null; - } - - V oldValue; - synchronized (node) { - oldValue = node.getValue(); - if (node.isAlive()) { - node.retire(); - } - } - - RemovalCause cause; - if (oldValue == null) { - cause = RemovalCause.COLLECTED; - } else if (hasExpired(node, expirationTicker().read())) { - cause = RemovalCause.EXPIRED; - } else { - cause = RemovalCause.EXPLICIT; - } - - if (hasRemovalListener()) { - @SuppressWarnings("unchecked") - K castKey = (K) key; - notifyRemoval(castKey, oldValue, cause); - } - afterWrite(new RemovalTask(node)); - return (cause == RemovalCause.EXPLICIT) ? oldValue : null; - } - - /** - * Removes the mapping for a key after notifying the writer. - * - * @param key key whose mapping is to be removed - * @return the removed value or null if no mapping was found - */ - @Nullable V removeWithWriter(Object key) { @SuppressWarnings("unchecked") K castKey = (K) key; @SuppressWarnings({"unchecked", "rawtypes"}) diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Cache.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Cache.java index dfd6355716..c6da398095 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Cache.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/Cache.java @@ -167,7 +167,7 @@ default Map getAll(@NonNull Iterable keys, * @param keys the keys whose associated values are to be removed * @throws NullPointerException if the specified collection is null or contains a null element */ - void invalidateAll(@NonNull Iterable keys); + void invalidateAll(@NonNull Iterable<@NonNull ?> keys); /** * Discards all entries in the cache. The behavior of this operation is undefined for an entry diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue412Test.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue412Test.java new file mode 100644 index 0000000000..ae850a6c30 --- /dev/null +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue412Test.java @@ -0,0 +1,103 @@ +/* + * Copyright 2020 Ben Manes. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.benmanes.caffeine.cache.issues; + +import static com.github.benmanes.caffeine.testing.ConcurrentTestHarness.DAEMON_FACTORY; +import static com.github.benmanes.caffeine.testing.ConcurrentTestHarness.timeTasks; +import static com.google.common.collect.ImmutableMultiset.toImmutableMultiset; +import static com.google.common.util.concurrent.MoreExecutors.shutdownAndAwaitTermination; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.not; + +import java.time.Duration; +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.RemovalCause; +import com.github.benmanes.caffeine.cache.testing.RemovalListeners; +import com.github.benmanes.caffeine.cache.testing.RemovalListeners.ConsumingRemovalListener; +import com.github.benmanes.caffeine.cache.testing.RemovalNotification; +import com.google.common.collect.Multiset; + +import site.ycsb.generator.NumberGenerator; +import site.ycsb.generator.ScrambledZipfianGenerator; + +/** + * Issue #412: Incorrect removal cause when expiration races with removal + * + * @author ben.manes@gmail.com (Ben Manes) + */ +@Test(groups = "isolated") +public final class Issue412Test { + private static final int NUM_THREADS = 5; + + private ConsumingRemovalListener listener; + private Cache cache; + private ExecutorService executor; + private Integer[] ints; + private Random random; + + @BeforeMethod + public void before() { + executor = Executors.newCachedThreadPool(DAEMON_FACTORY); + listener = RemovalListeners.consuming(); + cache = Caffeine.newBuilder() + .expireAfterWrite(Duration.ofNanos(10)) + .removalListener(listener) + .executor(executor) + .build(); + ints = generateSequence(); + random = new Random(); + } + + @Test + public void expire_remove() { + timeTasks(NUM_THREADS, this::addRemoveAndExpire); + shutdownAndAwaitTermination(executor, 1, TimeUnit.MINUTES); + + Multiset causes = listener.evicted().stream() + .map(RemovalNotification::getCause) + .collect(toImmutableMultiset()); + assertThat(causes, not(hasItem(RemovalCause.COLLECTED))); + } + + private void addRemoveAndExpire() { + int mask = (ints.length - 1); + int index = random.nextInt(); + for (int i = 0; i < (10 * ints.length); i++) { + Integer key = ints[index++ & mask]; + cache.put(key, Boolean.TRUE); + cache.invalidate(key); + } + } + + private static Integer[] generateSequence() { + Integer[] ints = new Integer[2 << 14]; + NumberGenerator generator = new ScrambledZipfianGenerator(ints.length / 3); + for (int i = 0; i < ints.length; i++) { + ints[i] = generator.nextValue().intValue(); + } + return ints; + } +} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/RemovalListeners.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/RemovalListeners.java index 8792104be4..c95b9e4c40 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/RemovalListeners.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/RemovalListeners.java @@ -19,6 +19,7 @@ import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.RejectedExecutionException; @@ -35,7 +36,7 @@ public final class RemovalListeners { private RemovalListeners() {} /** A removal listener that stores the notifications for inspection. */ - public static RemovalListener consuming() { + public static ConsumingRemovalListener consuming() { return new ConsumingRemovalListener<>(); } @@ -78,11 +79,11 @@ public static final class ConsumingRemovalListener private final List> evicted; public ConsumingRemovalListener() { - this.evicted = new ArrayList<>(); + this.evicted = Collections.synchronizedList(new ArrayList<>()); } @Override - public synchronized void onRemoval(K key, V value, RemovalCause cause) { + public void onRemoval(K key, V value, RemovalCause cause) { validate(key, value, cause); evicted.add(new RemovalNotification<>(key, value, cause)); } diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/ConcurrentTestHarness.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/ConcurrentTestHarness.java index bd390e4b41..e10d801076 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/ConcurrentTestHarness.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/ConcurrentTestHarness.java @@ -43,7 +43,7 @@ * @author ben.manes@gmail.com (Ben Manes) */ public final class ConcurrentTestHarness { - private static final ThreadFactory DAEMON_FACTORY = new ThreadFactoryBuilder() + public static final ThreadFactory DAEMON_FACTORY = new ThreadFactoryBuilder() .setPriority(Thread.MIN_PRIORITY).setDaemon(true).build(); public static final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(DAEMON_FACTORY); diff --git a/checksum.xml b/checksum.xml index 080278fa0a..08b15e14d2 100644 --- a/checksum.xml +++ b/checksum.xml @@ -26,6 +26,7 @@ + @@ -101,6 +102,7 @@ + @@ -140,9 +142,11 @@ + + @@ -241,6 +245,9 @@ D046D70ABBD137039F917CCD3786F5B3FCABA0FBAC3AE1C7597A77FA84BC75466F48A40DA53ADA6C45F05F77AF952D5EA82A9B0F873C53DF894DE0E15DB6D8AA + + 559ECA3B9FC21BE76ECAAA9E0073DC2014BA50DF2A14AD8919F358C1D7761E2C492EB09DAE1099A308C2F8C0A14635AD4583FF26B7868A782DE6A16D26F7EC6C + B3BFAD07E6A3D4D73CBCE802D8614CF4AC84E589166D243D41028DC077F84C027DF4D514F145360405F37DA73A8F2E7B65D90877A9EE1151174D2440530F9051 diff --git a/config/pmd/rulesSets.xml b/config/pmd/rulesSets.xml index 5651914da2..4f10af3af5 100644 --- a/config/pmd/rulesSets.xml +++ b/config/pmd/rulesSets.xml @@ -62,6 +62,7 @@ + diff --git a/gradle/codeQuality.gradle b/gradle/codeQuality.gradle index 3130b181e1..2cf09b8700 100644 --- a/gradle/codeQuality.gradle +++ b/gradle/codeQuality.gradle @@ -180,8 +180,6 @@ afterEvaluate { tasks.findAll { it.name.startsWith('spotbugs') }.each { it.enabled = System.properties.containsKey('spotbugs') it.group = 'SpotBugs' - it.reports.xml.enabled = false - it.reports.html.enabled = true } } diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index bf88130658..7a73e1a8ca 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -27,23 +27,23 @@ ext { versions = [ akka: '2.6.4', cache2k: '1.3.1.Alpha', - checkerFramework: '3.2.0', + checkerFramework: '3.3.0', collision: '0.3.3', commonsCompress: '1.20', - commonsLang3: '3.9', + commonsLang3: '3.10', commonsMath3: '3.6.1', concurrentlinkedhashmap: '1.4.2', config: '1.4.0', ehcache3: '3.8.1', errorprone: '2.3.4', errorproneJavac: '9+181-r4173-1', - elasticSearch: '7.6.1', + elasticSearch: '7.6.2', expiringMap: '0.5.9', fastfilter: 'bf0b02297f', fastutil: '8.3.1', flipTables: '1.1.0', - guava: '28.2-jre', - jackrabbit: '1.24.0', + guava: '29.0-jre', + jackrabbit: '1.26.0', jamm: '0.3.3', javaObjectLayout: '0.10', javapoet: '1.12.1', @@ -69,14 +69,14 @@ ext { jctools: '3.0.0', junit: '4.13', mockito: '3.3.3', - paxExam: '4.13.1', + paxExam: '4.13.3', testng: '7.2.0', truth: '0.24', ] pluginVersions = [ apt: '0.21', - bnd: '5.0.0', - checkstyle: '8.30', + bnd: '5.0.1', + checkstyle: '8.31', coveralls: '2.8.4', coverity: '1.0.10', errorprone: '1.1.1', @@ -84,12 +84,12 @@ ext { jmh: '0.5.0', jmhReport: '0.9.0', nullaway: '1.0.1', - pmd: '6.22.0', + pmd: '6.23.0', semanticVersioning: '1.1.0', shadow: '5.2.0', sonarqube: '2.8.0.1969', - spotbugs: '4.0.0-RC1', - spotbugsPlugin: '3.0.0', + spotbugs: '4.0.2', + spotbugsPlugin: '4.0.5', stats: '0.2.2', versions: '0.28.0', ] @@ -188,10 +188,7 @@ ext { semanticVersioning: "io.ehdev:gradle-semantic-versioning:${pluginVersions.semanticVersioning}", shadow: "com.github.jengelman.gradle.plugins:shadow:${pluginVersions.shadow}", sonarqube: "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:${pluginVersions.sonarqube}", - spotbugs: [ - "com.github.spotbugs:spotbugs-gradle-plugin:${pluginVersions.spotbugsPlugin}", - libraries.guava // https://github.com/spotbugs/spotbugs-gradle-plugin/issues/119 - ], + spotbugs: "gradle.plugin.com.github.spotbugs.snom:spotbugs-gradle-plugin:${pluginVersions.spotbugsPlugin}", stats: "org.kordamp.gradle:stats-gradle-plugin:${pluginVersions.stats}", versions: "com.github.ben-manes:gradle-versions-plugin:${pluginVersions.versions}", ] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 17bc87bdbb..29b89d4399 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-rc-4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME