Skip to content

Commit

Permalink
Finalize CacheWriter support
Browse files Browse the repository at this point in the history
  • Loading branch information
ben-manes committed Jul 12, 2015
1 parent dc6e4e8 commit cb936ae
Show file tree
Hide file tree
Showing 20 changed files with 166 additions and 197 deletions.
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()

#### Features at a Glance

Caffeine provide flexible construction to create a cache with any combination of the following
features:
Caffeine provide flexible construction to create a cache with a combination of the following features:

* [automatic loading of entries][population] into the cache, optionally asynchronously
* [least-recently-used eviction][size] when a maximum size is exceeded
Expand All @@ -44,7 +43,7 @@ In addition, Caffeine offers the following extensions:

### Download

Download [the latest .jar][jar] from [Maven Central][maven] or depend via Gradle:
Download from [Maven Central][maven] or depend via Gradle:

```gradle
compile 'com.github.ben-manes.caffeine:caffeine:1.2.0'
Expand All @@ -55,7 +54,7 @@ compile 'com.github.ben-manes.caffeine:jcache:1.2.0'
compile 'com.github.ben-manes.caffeine:tracing-async:1.2.0'
```

Snapshots of the development version are available in
Snapshots of the development version are available in
[Sonatype's snapshots repository](https://oss.sonatype.org/content/repositories/snapshots).

[benchmarks]: https://github.com/ben-manes/caffeine/wiki/Benchmarks
Expand All @@ -74,5 +73,4 @@ Snapshots of the development version are available in
[simulator]: https://github.com/ben-manes/caffeine/wiki/Simulator
[guava-adapter]: https://github.com/ben-manes/caffeine/wiki/Guava
[jsr107]: https://github.com/ben-manes/caffeine/wiki/JCache
[jar]: https://search.maven.org/remote_content?g=com.github.ben-manes.caffeine&a=caffeine&v=LATEST
[maven]: https://maven-badges.herokuapp.com/maven-central/com.github.ben-manes.caffeine/caffeine
Original file line number Diff line number Diff line change
Expand Up @@ -903,12 +903,17 @@ public Map<K, V> getAllPresent(Iterable<?> keys) {

@Override
public V put(K key, V value) {
return put(key, value, false);
return put(key, value, true, false);
}

@Override
public V put(K key, V value, boolean notifyWriter) {
return put(key, value, notifyWriter, false);
}

@Override
public V putIfAbsent(K key, V value) {
return put(key, value, true);
return put(key, value, true, true);
}

/**
Expand All @@ -917,10 +922,11 @@ public V putIfAbsent(K key, V value) {
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @param notifyWriter if the writer should be notified for an inserted or updated entry
* @param onlyIfAbsent a write is performed only if the key is not already associated with a value
* @return the prior value in the data store or null if no mapping was found
*/
V put(K key, V value, boolean onlyIfAbsent) {
V put(K key, V value, boolean notifyWriter, boolean onlyIfAbsent) {
requireNonNull(key);
requireNonNull(value);

Expand All @@ -938,7 +944,9 @@ V put(K key, V value, boolean onlyIfAbsent) {
}
Node<K, V> computed = node;
prior = data.computeIfAbsent(node.getKeyReference(), k -> {
writer.write(key, value);
if (notifyWriter) {
writer.write(key, value);
}
return computed;
});
if (prior == node) {
Expand Down Expand Up @@ -966,7 +974,7 @@ V put(K key, V value, boolean onlyIfAbsent) {
mayUpdate = false;
}

if (expired || (mayUpdate && (value != oldValue))) {
if (notifyWriter && (expired || (mayUpdate && (value != oldValue)))) {
writer.write(key, value);
}
if (mayUpdate) {
Expand Down Expand Up @@ -1369,7 +1377,6 @@ V remap(K key, Object keyRef, BiFunction<? super K, ? super V, ? extends V> rema
if (newValue[0] == null) {
return null;
}
writer.write(key, newValue[0]);
weight[1] = weigher.weigh(key, newValue[0]);
tracer().recordWrite(id, key, weight[1]);
return nodeFactory.newNode(keyRef, newValue[0],
Expand Down Expand Up @@ -1398,14 +1405,12 @@ V remap(K key, Object keyRef, BiFunction<? super K, ? super V, ? extends V> rema
if (newValue[0] == null) {
if (cause[0] == null) {
cause[0] = RemovalCause.EXPLICIT;
writer.delete(nodeKey[0], oldValue[0], cause[0]);
}
removed[0] = n;
n.retire();
return null;
}

writer.write(nodeKey[0], newValue[0]);
weight[0] = n.getWeight();
weight[1] = weigher.weigh(key, newValue[0]);
n.setValue(newValue[0], valueReferenceQueue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,9 @@
import javax.annotation.concurrent.ThreadSafe;

/**
* Communicates the write or deletion of a value, based on a key, to an external resource.
* <p>
* The operations may be performed in either a <tt>write-through</tt> or <tt>write-behind</tt>
* style, where the difference is whether the operation completes synchronously or asynchronously
* with the cache.
* <p>
* When combined with a {@link CacheLoader}, the writer simplifies the implementation of a tiered
* cache. The loader queries the secondary cache and computes the value if not found. The layered
* cache may be modeled as a victim cache by writing entries when the {@link RemovalCause} indicates
* an eviction.
* Communicates the write or deletion of a value, based on a key, to an external resource. A writer
* is notified by the cache each time an entry is explicitly created or modified, or removed for any
* {@linkplain RemovalCause reason}. The writer is not notified when an entry is loaded or computed.
*
* @author [email protected] (Ben Manes)
* @param <K> the most general type of keys this writer can write; for example {@code Object} if any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

/**
* A builder of {@link AsyncLoadingCache}, {@link LoadingCache}, and {@link Cache} instances
* having any combination of the following features:
* having a combination of the following features:
* <ul>
* <li>automatic loading of entries into the cache, optionally asynchronously
* <li>least-recently-used eviction when a maximum size is exceeded
Expand Down Expand Up @@ -216,9 +216,9 @@ int getInitialCapacity() {

/**
* Specifies the executor to use when running asynchronous tasks. The executor is delegated to
* when sending removal notifications and asynchronous computations requested through the
* {@link AsyncLoadingCache} and {@link LoadingCache#refresh}. By default,
* {@link ForkJoinPool#commonPool()} is used.
* when sending removal notifications, when asynchronous computations are performed by
* {@link AsyncLoadingCache} and {@link LoadingCache#refresh}, or when performing periodic
* maintenance. By default, {@link ForkJoinPool#commonPool()} is used.
* <p>
* The primary intent of this method is to facilitate testing of caches which have been
* configured with {@link #removalListener} or utilize asynchronous computations. A test may
Expand Down Expand Up @@ -652,8 +652,8 @@ <K1 extends K, V1 extends V> RemovalListener<K1, V1> getRemovalListener(boolean
/**
* Specifies a writer instance that caches should notify each time an entry is explicitly created
* or modified, or removed for any {@linkplain RemovalCause reason}. The writer is not notified
* when an entry is loaded. Each cache created by this builder will invoke this writer as part of
* the atomic operation that modifies the cache.
* when an entry is loaded or computed. Each cache created by this builder will invoke this writer
* as part of the atomic operation that modifies the cache.
* <p>
* <b>Warning:</b> after invoking this method, do not continue to use <i>this</i> cache builder
* reference; instead use the reference this method <i>returns</i>. At runtime, these point to the
Expand All @@ -665,6 +665,8 @@ <K1 extends K, V1 extends V> RemovalListener<K1, V1> getRemovalListener(boolean
* <p>
* <b>Warning:</b> any exception thrown by {@code writer} will be propagated to the {@code Cache}
* user.
* <p>
* This feature cannot be used in conjunction with {@link #buildAsync}.
*
* @param writer a writer instance that caches should notify each time an entry is explicitly
* created or modified, or removed for any reason
Expand Down Expand Up @@ -860,6 +862,7 @@ public <K1 extends K, V1 extends V> LoadingCache<K1, V1> build(
public <K1 extends K, V1 extends V> AsyncLoadingCache<K1, V1> buildAsync(
@Nonnull CacheLoader<? super K1, V1> loader) {
requireState(valueStrength == null);
requireState(writer == null);
requireWeightWithWeigher();
requireNonNull(loader);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,8 @@ private CompletableFuture<Map<K, V>> composeResult(Map<K, CompletableFuture<V>>

@Override
public void put(K key, CompletableFuture<V> valueFuture) {
if (valueFuture.isCompletedExceptionally()) {
if (valueFuture.isCompletedExceptionally()
|| (valueFuture.isDone() && (valueFuture.join() == null))) {
cache.statsCounter().recordLoadFailure(0L);
cache.remove(key);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ default Tracer tracer() {
@Nonnull
Map<K, V> getAllPresent(@Nonnull Iterable<?> keys);

/**
* See {@link Cache#put(Object, Object)}. This method differs by allowing the operation to not
* notify the writer when an entry was inserted or updated.
*/
V put(K key, V value, boolean notifyWriter);

@Override
default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
return compute(key, remappingFunction, false, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.BiFunction;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand Down Expand Up @@ -123,7 +124,9 @@ default void bulkLoad(List<K> keysToLoad, Map<K, V> result) {
try {
@SuppressWarnings("unchecked")
Map<K, V> loaded = (Map<K, V>) cacheLoader().loadAll(keysToLoad);
cache().putAll(loaded);
for (Entry<K, V> entry : loaded.entrySet()) {
cache().put(entry.getKey(), entry.getValue(), false);
}
for (K key : keysToLoad) {
V value = loaded.get(key);
if (value != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,15 +242,7 @@ public V computeIfPresent(K key,
V nv = data.computeIfPresent(key, (K k, V oldValue) -> {
V newValue = statsAware(remappingFunction, false, false).apply(k, oldValue);

RemovalCause cause;
if (newValue == null) {
cause = RemovalCause.EXPLICIT;
writer.delete(key, oldValue, cause);
} else {
cause = RemovalCause.REPLACED;
writer.write(key, newValue);
}

RemovalCause cause = (newValue == null) ? RemovalCause.EXPLICIT : RemovalCause.REPLACED;
if (hasRemovalListener() && (newValue != oldValue)) {
notification[0] = new RemovalNotification<>(key, oldValue, cause);
}
Expand Down Expand Up @@ -298,18 +290,7 @@ V remap(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
return null;
}

RemovalCause cause;
if (newValue == null) {
cause = RemovalCause.EXPLICIT;
writer.delete(key, oldValue, cause);
} else {
// Do not communicate to CacheWriter on a load
cause = RemovalCause.REPLACED;
if (oldValue != null) {
writer.write(key, newValue);
}
}

RemovalCause cause = (newValue == null) ? RemovalCause.EXPLICIT : RemovalCause.REPLACED;
if (hasRemovalListener() && (oldValue != null) && (newValue != oldValue)) {
notification[0] = new RemovalNotification<>(key, oldValue, cause);
}
Expand Down Expand Up @@ -362,13 +343,18 @@ public V get(Object key) {

@Override
public V put(K key, V value) {
return put(key, value, true);
}

@Override
public V put(K key, V value, boolean notifyWriter) {
requireNonNull(value);

// ensures that the removal notification is processed after the removal has completed
@SuppressWarnings({"unchecked", "rawtypes"})
V oldValue[] = (V[]) new Object[1];
data.compute(key, (k, v) -> {
if (value != v) {
if (notifyWriter && (value != v)) {
writer.write(key, value);
}
oldValue[0] = v;
Expand Down
Loading

0 comments on commit cb936ae

Please sign in to comment.