blob: 24b6931a74405b969f41e1edb614c2eb999e1706 [file] [log] [blame]
Madan Jampani10073672016-01-21 19:13:59 -08001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2016-present Open Networking Laboratory
Madan Jampani10073672016-01-21 19:13:59 -08003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Madan Jampani10073672016-01-21 19:13:59 -080016package org.onosproject.store.primitives.impl;
17
Madan Jampani1d3b6172016-04-28 13:22:57 -070018import static org.slf4j.LoggerFactory.getLogger;
19
Jordan Halterman5ecdb342017-07-22 12:33:14 -070020import java.util.Objects;
Madan Jampani10073672016-01-21 19:13:59 -080021import java.util.concurrent.CompletableFuture;
22import java.util.function.BiFunction;
Madan Jampani1d3b6172016-04-28 13:22:57 -070023import java.util.function.Consumer;
Madan Jampani10073672016-01-21 19:13:59 -080024import java.util.function.Predicate;
25
26import org.onosproject.store.service.AsyncConsistentMap;
Madan Jampani62f15332016-02-01 13:18:39 -080027import org.onosproject.store.service.MapEventListener;
Madan Jampani10073672016-01-21 19:13:59 -080028import org.onosproject.store.service.Versioned;
Madan Jampani1d3b6172016-04-28 13:22:57 -070029import org.slf4j.Logger;
Madan Jampani10073672016-01-21 19:13:59 -080030
31import com.google.common.cache.CacheBuilder;
32import com.google.common.cache.CacheLoader;
33import com.google.common.cache.LoadingCache;
34
Madan Jampani1d3b6172016-04-28 13:22:57 -070035import static org.onosproject.store.service.DistributedPrimitive.Status.INACTIVE;
36import static org.onosproject.store.service.DistributedPrimitive.Status.SUSPENDED;
37
Madan Jampani10073672016-01-21 19:13:59 -080038/**
39 * {@code AsyncConsistentMap} that caches entries on read.
40 * <p>
41 * The cache entries are automatically invalidated when updates are detected either locally or
42 * remotely.
43 * <p> This implementation only attempts to serve cached entries for {@link AsyncConsistentMap#get get}
Jordan Halterman5ecdb342017-07-22 12:33:14 -070044 * {@link AsyncConsistentMap#getOrDefault(Object, Object) getOrDefault}, and
45 * {@link AsyncConsistentMap#containsKey(Object) containsKey} calls. All other calls skip the cache
46 * and directly go the backing map.
Madan Jampani10073672016-01-21 19:13:59 -080047 *
48 * @param <K> key type
49 * @param <V> value type
50 */
51public class CachingAsyncConsistentMap<K, V> extends DelegatingAsyncConsistentMap<K, V> {
Madan Jampani1d3b6172016-04-28 13:22:57 -070052 private static final int DEFAULT_CACHE_SIZE = 10000;
53 private final Logger log = getLogger(getClass());
Madan Jampani10073672016-01-21 19:13:59 -080054
Madan Jampani1d3b6172016-04-28 13:22:57 -070055 private final LoadingCache<K, CompletableFuture<Versioned<V>>> cache;
Jordan Haltermanb2243072017-05-09 12:04:04 -070056 private final AsyncConsistentMap<K, V> backingMap;
Madan Jampanie88086f2016-06-16 16:48:14 -070057 private final MapEventListener<K, V> cacheUpdater;
Madan Jampani1d3b6172016-04-28 13:22:57 -070058 private final Consumer<Status> statusListener;
Madan Jampani62f15332016-02-01 13:18:39 -080059
sangyun-hancce07c52016-04-06 11:07:59 +090060 /**
61 * Default constructor.
62 *
63 * @param backingMap a distributed, strongly consistent map for backing
64 */
Madan Jampani10073672016-01-21 19:13:59 -080065 public CachingAsyncConsistentMap(AsyncConsistentMap<K, V> backingMap) {
Madan Jampani1d3b6172016-04-28 13:22:57 -070066 this(backingMap, DEFAULT_CACHE_SIZE);
Madan Jampani62f15332016-02-01 13:18:39 -080067 }
68
sangyun-hancce07c52016-04-06 11:07:59 +090069 /**
Madan Jampani1d3b6172016-04-28 13:22:57 -070070 * Constructor to configure cache size.
sangyun-hancce07c52016-04-06 11:07:59 +090071 *
72 * @param backingMap a distributed, strongly consistent map for backing
73 * @param cacheSize the maximum size of the cache
74 */
75 public CachingAsyncConsistentMap(AsyncConsistentMap<K, V> backingMap, int cacheSize) {
76 super(backingMap);
Jordan Haltermanb2243072017-05-09 12:04:04 -070077 this.backingMap = backingMap;
Madan Jampani1d3b6172016-04-28 13:22:57 -070078 cache = CacheBuilder.newBuilder()
79 .maximumSize(cacheSize)
80 .build(CacheLoader.from(CachingAsyncConsistentMap.super::get));
Madan Jampanie88086f2016-06-16 16:48:14 -070081 cacheUpdater = event -> {
82 Versioned<V> newValue = event.newValue();
83 if (newValue == null) {
84 cache.invalidate(event.key());
85 } else {
86 cache.put(event.key(), CompletableFuture.completedFuture(newValue));
87 }
88 };
Madan Jampani1d3b6172016-04-28 13:22:57 -070089 statusListener = status -> {
90 log.debug("{} status changed to {}", this.name(), status);
91 // If the status of the underlying map is SUSPENDED or INACTIVE
92 // we can no longer guarantee that the cache will be in sync.
93 if (status == SUSPENDED || status == INACTIVE) {
94 cache.invalidateAll();
95 }
96 };
Madan Jampanie88086f2016-06-16 16:48:14 -070097 super.addListener(cacheUpdater);
Madan Jampani1d3b6172016-04-28 13:22:57 -070098 super.addStatusChangeListener(statusListener);
sangyun-hancce07c52016-04-06 11:07:59 +090099 }
100
Madan Jampani62f15332016-02-01 13:18:39 -0800101 @Override
102 public CompletableFuture<Void> destroy() {
Madan Jampani1d3b6172016-04-28 13:22:57 -0700103 super.removeStatusChangeListener(statusListener);
Madan Jampanie88086f2016-06-16 16:48:14 -0700104 return super.destroy().thenCompose(v -> removeListener(cacheUpdater));
Madan Jampani10073672016-01-21 19:13:59 -0800105 }
106
107 @Override
108 public CompletableFuture<Versioned<V>> get(K key) {
Madan Jampani77012442016-06-02 07:47:42 -0700109 return cache.getUnchecked(key)
110 .whenComplete((r, e) -> {
111 if (e != null) {
112 cache.invalidate(key);
113 }
114 });
Madan Jampani10073672016-01-21 19:13:59 -0800115 }
116
117 @Override
Jordan Haltermanb2243072017-05-09 12:04:04 -0700118 public CompletableFuture<Versioned<V>> getOrDefault(K key, V defaultValue) {
119 return cache.getUnchecked(key).thenCompose(r -> {
120 if (r == null) {
121 CompletableFuture<Versioned<V>> versioned = backingMap.getOrDefault(key, defaultValue);
122 cache.put(key, versioned);
123 return versioned;
124 } else {
125 return CompletableFuture.completedFuture(r);
126 }
127 }).whenComplete((r, e) -> {
128 if (e != null) {
129 cache.invalidate(key);
130 }
131 });
132 }
133
134 @Override
Madan Jampani10073672016-01-21 19:13:59 -0800135 public CompletableFuture<Versioned<V>> computeIf(K key,
136 Predicate<? super V> condition,
137 BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
138 return super.computeIf(key, condition, remappingFunction)
sangyun-hancce07c52016-04-06 11:07:59 +0900139 .whenComplete((r, e) -> cache.invalidate(key));
Madan Jampani10073672016-01-21 19:13:59 -0800140 }
141
142 @Override
143 public CompletableFuture<Versioned<V>> put(K key, V value) {
144 return super.put(key, value)
sangyun-hancce07c52016-04-06 11:07:59 +0900145 .whenComplete((r, e) -> cache.invalidate(key));
Madan Jampani10073672016-01-21 19:13:59 -0800146 }
147
148 @Override
149 public CompletableFuture<Versioned<V>> putAndGet(K key, V value) {
Simon Hunt5829c342016-03-07 17:01:43 -0800150 return super.putAndGet(key, value)
sangyun-hancce07c52016-04-06 11:07:59 +0900151 .whenComplete((r, e) -> cache.invalidate(key));
Madan Jampani10073672016-01-21 19:13:59 -0800152 }
153
154 @Override
Jordan Halterman5ecdb342017-07-22 12:33:14 -0700155 public CompletableFuture<Versioned<V>> putIfAbsent(K key, V value) {
156 return super.putIfAbsent(key, value)
157 .whenComplete((r, e) -> cache.invalidate(key));
158 }
159
160 @Override
Madan Jampani10073672016-01-21 19:13:59 -0800161 public CompletableFuture<Versioned<V>> remove(K key) {
162 return super.remove(key)
sangyun-hancce07c52016-04-06 11:07:59 +0900163 .whenComplete((r, e) -> cache.invalidate(key));
Madan Jampani10073672016-01-21 19:13:59 -0800164 }
165
166 @Override
Jordan Halterman5ecdb342017-07-22 12:33:14 -0700167 public CompletableFuture<Boolean> containsKey(K key) {
168 return cache.getUnchecked(key).thenApply(Objects::nonNull)
169 .whenComplete((r, e) -> {
170 if (e != null) {
171 cache.invalidate(key);
172 }
173 });
174 }
175
176 @Override
Madan Jampani10073672016-01-21 19:13:59 -0800177 public CompletableFuture<Void> clear() {
178 return super.clear()
sangyun-hancce07c52016-04-06 11:07:59 +0900179 .whenComplete((r, e) -> cache.invalidateAll());
Madan Jampani10073672016-01-21 19:13:59 -0800180 }
181
182 @Override
183 public CompletableFuture<Boolean> remove(K key, V value) {
184 return super.remove(key, value)
Madan Jampani77012442016-06-02 07:47:42 -0700185 .whenComplete((r, e) -> {
186 if (r) {
187 cache.invalidate(key);
188 }
189 });
Madan Jampani10073672016-01-21 19:13:59 -0800190 }
191
192 @Override
193 public CompletableFuture<Boolean> remove(K key, long version) {
194 return super.remove(key, version)
195 .whenComplete((r, e) -> {
196 if (r) {
197 cache.invalidate(key);
198 }
199 });
200 }
201
202 @Override
203 public CompletableFuture<Versioned<V>> replace(K key, V value) {
204 return super.replace(key, value)
Madan Jampani77012442016-06-02 07:47:42 -0700205 .whenComplete((r, e) -> cache.invalidate(key));
Madan Jampani10073672016-01-21 19:13:59 -0800206 }
207
208 @Override
209 public CompletableFuture<Boolean> replace(K key, V oldValue, V newValue) {
210 return super.replace(key, oldValue, newValue)
211 .whenComplete((r, e) -> {
212 if (r) {
213 cache.invalidate(key);
214 }
215 });
216 }
217
218 @Override
219 public CompletableFuture<Boolean> replace(K key, long oldVersion, V newValue) {
220 return super.replace(key, oldVersion, newValue)
221 .whenComplete((r, e) -> {
222 if (r) {
223 cache.invalidate(key);
224 }
225 });
226 }
227}