Skip to content

Commit

Permalink
Upgrade to Guava 22
Browse files Browse the repository at this point in the history
Uses the Java8 compute methods within the adapter, correcting for
differences in statistics.
  • Loading branch information
ben-manes committed May 24, 2017
1 parent abf9add commit 764df3a
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1029,7 +1029,8 @@ public void computeIfPresent_nullValue(Map<Integer, Integer> map, CacheContext c
}

@CacheSpec(population = { Population.SINGLETON, Population.PARTIAL, Population.FULL },
removalListener = { Listener.DEFAULT, Listener.REJECTING })
removalListener = { Listener.DEFAULT, Listener.REJECTING },
implementation = Implementation.Caffeine)
@Test(dataProvider = "caches", expectedExceptions = StackOverflowError.class)
public void computeIfPresent_recursive(Map<Integer, Integer> map, CacheContext context) {
// As we cannot provide immediate checking without an expensive solution, e.g. ThreadLocal,
Expand All @@ -1051,7 +1052,8 @@ public void computeIfPresent_recursive(Map<Integer, Integer> map, CacheContext c
}

@CacheSpec(population = { Population.SINGLETON, Population.PARTIAL, Population.FULL },
removalListener = { Listener.DEFAULT, Listener.REJECTING })
removalListener = { Listener.DEFAULT, Listener.REJECTING },
implementation = Implementation.Caffeine)
@Test(dataProvider = "caches", expectedExceptions = StackOverflowError.class)
public void computeIfPresent_pingpong(Map<Integer, Integer> map, CacheContext context) {
// As we cannot provide immediate checking without an expensive solution, e.g. ThreadLocal,
Expand Down Expand Up @@ -1289,7 +1291,8 @@ public void merge_remove(Map<Integer, Integer> map, CacheContext context) {
}

@CacheSpec(population = { Population.SINGLETON, Population.PARTIAL, Population.FULL },
removalListener = { Listener.DEFAULT, Listener.REJECTING })
removalListener = { Listener.DEFAULT, Listener.REJECTING },
implementation = Implementation.Caffeine)
@Test(dataProvider = "caches")
public void merge_recursive(Map<Integer, Integer> map, CacheContext context) {
BiFunction<Integer, Integer, Integer> mappingFunction =
Expand All @@ -1304,7 +1307,8 @@ public void merge_recursive(Map<Integer, Integer> map, CacheContext context) {
}

@CacheSpec(population = { Population.SINGLETON, Population.PARTIAL, Population.FULL },
removalListener = { Listener.DEFAULT, Listener.REJECTING })
removalListener = { Listener.DEFAULT, Listener.REJECTING },
implementation = Implementation.Caffeine)
@Test(dataProvider = "caches", expectedExceptions = StackOverflowError.class)
public void merge_pingpong(Map<Integer, Integer> map, CacheContext context) {
// As we cannot provide immediate checking without an expensive solution, e.g. ThreadLocal,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiFunction;
import java.util.function.Function;

Expand Down Expand Up @@ -135,13 +136,15 @@ static class GuavaCache<K, V> implements Cache<K, V>, Serializable {
private static final long serialVersionUID = 1L;

private final com.google.common.cache.Cache<K, V> cache;
private final AtomicLong loadSuccessCount;
private final boolean isRecordingStats;
private final Ticker ticker;

transient StatsCounter statsCounter;

GuavaCache(com.google.common.cache.Cache<K, V> cache, Ticker ticker, boolean isRecordingStats) {
this.statsCounter = new SimpleStatsCounter();
this.loadSuccessCount = new AtomicLong();
this.isRecordingStats = isRecordingStats;
this.cache = requireNonNull(cache);
this.ticker = ticker;
Expand Down Expand Up @@ -217,7 +220,8 @@ public long estimatedSize() {
@Override
public CacheStats stats() {
com.google.common.cache.CacheStats stats = statsCounter.snapshot().plus(cache.stats());
return new CacheStats(stats.hitCount(), stats.missCount(), stats.loadSuccessCount(),
long loadSuccess = Math.max(0L, stats.loadSuccessCount() + loadSuccessCount.get());
return new CacheStats(stats.hitCount(), stats.missCount(), loadSuccess,
stats.loadExceptionCount(), stats.totalLoadTime(), stats.evictionCount(), 0L);
}

Expand Down Expand Up @@ -257,112 +261,93 @@ public boolean replace(K key, V oldValue, V newValue) {
@Override
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
requireNonNull(mappingFunction);
V value = getIfPresent(key);
if (value != null) {
return value;
}
long now = ticker.read();
try {
value = mappingFunction.apply(key);
long loadTime = (ticker.read() - now);
if (value == null) {
statsCounter.recordLoadException(loadTime);
return null;
} else {
statsCounter.recordLoadSuccess(loadTime);
V v = delegate().putIfAbsent(key, value);
return (v == null) ? value : v;
boolean[] computed = { false };
V result = delegate().computeIfAbsent(key, k -> {
long now = ticker.read();
computed[0] = true;
try {
statsCounter.recordMisses(1);
V value = mappingFunction.apply(key);
if (value == null) {
statsCounter.recordLoadException(ticker.read() - now);
}
return value;
} catch (Throwable t) {
statsCounter.recordLoadException(ticker.read() - now);
throw t;
}
} catch (RuntimeException | Error e) {
statsCounter.recordLoadException((ticker.read() - now));
throw e;
});
if (!computed[0]) {
statsCounter.recordHits(1);
loadSuccessCount.decrementAndGet();
}
return result;
}
@Override
public V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
requireNonNull(remappingFunction);
V oldValue;
long now = ticker.read();
if ((oldValue = get(key)) != null) {
return delegate().computeIfPresent(key, (k, oldValue) -> {
long now = ticker.read();
try {
V newValue = remappingFunction.apply(key, oldValue);
long loadTime = ticker.read() - now;
if (newValue == null) {
statsCounter.recordLoadException(loadTime);
remove(key);
statsCounter.recordLoadException(ticker.read() - now);
return null;
} else {
statsCounter.recordLoadSuccess(loadTime);
put(key, newValue);
return newValue;
}
return newValue;
} catch (RuntimeException | Error e) {
statsCounter.recordLoadException(ticker.read() - now);
throw e;
}
} else {
return null;
}
});
}
@Override
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
requireNonNull(remappingFunction);
V oldValue = get(key);

long now = ticker.read();
try {
V newValue = remappingFunction.apply(key, oldValue);
if (newValue == null) {
if (oldValue != null || containsKey(key)) {
remove(key);
statsCounter.recordLoadException(ticker.read() - now);
return null;
} else {
boolean[] computed = { false };
V result = delegate().compute(key, (k, oldValue) -> {
long now = ticker.read();
computed[0] = true;
try {
V newValue = remappingFunction.apply(key, oldValue);
if (newValue == null) {
statsCounter.recordLoadException(ticker.read() - now);
return null;
}
} else {
statsCounter.recordLoadSuccess(ticker.read() - now);
put(key, newValue);
return newValue;
} catch (RuntimeException | Error e) {
statsCounter.recordLoadException(ticker.read() - now);
throw e;
}
} catch (RuntimeException | Error e) {
statsCounter.recordLoadException(ticker.read() - now);
throw e;
});
if (!computed[0]) {
loadSuccessCount.decrementAndGet();
}
return result;
}
@Override
public V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
requireNonNull(remappingFunction);
requireNonNull(value);
V oldValue = get(key);
for (;;) {
if (oldValue != null) {
long now = ticker.read();
try {
V newValue = remappingFunction.apply(oldValue, value);
if (newValue != null) {
if (replace(key, oldValue, newValue)) {
statsCounter.recordLoadSuccess(ticker.read() - now);
return newValue;
}
} else if (remove(key, oldValue)) {
statsCounter.recordLoadException(ticker.read() - now);
return null;
}
} catch (RuntimeException | Error e) {
boolean[] computed = { false };
V result = delegate().merge(key, value, (oldValue, val) -> {
long now = ticker.read();
computed[0] = true;
try {
V newValue = remappingFunction.apply(oldValue, val);
if (newValue == null) {
statsCounter.recordLoadException(ticker.read() - now);
throw e;
}
oldValue = get(key);
} else {
if ((oldValue = putIfAbsent(key, value)) == null) {
return value;
}
return newValue;
} catch (RuntimeException | Error e) {
statsCounter.recordLoadException(ticker.read() - now);
throw e;
}
});
if (!computed[0]) {
loadSuccessCount.decrementAndGet();
}
return result;
}
@Override
protected ConcurrentMap<K, V> delegate() {
Expand All @@ -371,6 +356,7 @@ protected ConcurrentMap<K, V> delegate() {

private void readObject(ObjectInputStream stream) throws InvalidObjectException {
statsCounter = new SimpleStatsCounter();
loadSuccessCount.set(0L);
}
};
}
Expand Down
6 changes: 3 additions & 3 deletions gradle/dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@
*/
ext {
versions = [
akka: '2.5.1',
akka: '2.5.2',
commons_compress: '1.14',
commons_lang3: '3.5',
config: '1.3.1',
error_prone_annotations: '2.0.19',
fastutil: '7.2.0',
flip_tables: '1.0.2',
guava: '21.0',
guava: '22.0',
javapoet: '1.9.0',
jcache: '1.0.0',
jsr305: '3.0.2',
Expand Down Expand Up @@ -69,7 +69,7 @@ ext {
ohc: '0.6.1',
rapidoid: '5.3.4',
slf4j: '1.7.25',
tcache: '1.0.1',
tcache: '1.0.3',
]
plugin_versions = [
buildscan: '1.7.1',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright (C) 2017 The Guava Authors
*
* 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.google.common.cache;

import static com.google.common.truth.Truth.assertThat;

import java.util.concurrent.TimeUnit;
import java.util.function.IntConsumer;
import java.util.stream.IntStream;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.guava.CaffeinatedGuava;

import junit.framework.TestCase;

/**
* Test Java8 map.compute in concurrent cache context.
*/
public class LocalCacheMapComputeTest extends TestCase {
final int count = 10000;
final String delimiter = "-";
final String key = "key";
Cache<String, String> cache;

// helper
private static void doParallelCacheOp(int count, IntConsumer consumer) {
IntStream.range(0, count).parallel().forEach(consumer);
}

@Override
public void setUp() throws Exception {
super.setUp();
this.cache = CaffeinatedGuava.build(Caffeine.newBuilder()
.expireAfterAccess(500000, TimeUnit.MILLISECONDS)
.maximumSize(count));
}

public void testComputeIfAbsent() {
// simultaneous insertion for same key, expect 1 winner
doParallelCacheOp(count, n -> {
cache.asMap().computeIfAbsent(key, k -> "value" + n);
});
assertEquals(1, cache.size());
}

public void testComputeIfPresent() {
cache.put(key, "1");
// simultaneous update for same key, expect count successful updates
doParallelCacheOp(count, n -> {
cache.asMap().computeIfPresent(key, (k, v) -> v + delimiter + n);
});
assertEquals(1, cache.size());
assertThat(cache.getIfPresent(key).split(delimiter)).hasLength(count + 1);
}

public void testUpdates() {
cache.put(key, "1");
// simultaneous update for same key, some null, some non-null
doParallelCacheOp(count, n -> {
cache.asMap().compute(key, (k, v) -> n % 2 == 0 ? v + delimiter + n : null);
});
assertTrue(1 >= cache.size());
}

public void testCompute() {
cache.put(key, "1");
// simultaneous deletion
doParallelCacheOp(count, n -> {
cache.asMap().compute(key, (k, v) -> null);
});
assertEquals(0, cache.size());
}
}

0 comments on commit 764df3a

Please sign in to comment.