• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ben-manes / caffeine / #3896

pending completion
#3896

push

github-actions

ben-manes
upgrade jamm library (memory meter)

7542 of 7616 relevant lines covered (99.03%)

0.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.12
/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalLoadingCache.java
1
/*
2
 * Copyright 2015 Ben Manes. All Rights Reserved.
3
 *
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
 */
16
package com.github.benmanes.caffeine.cache;
17

18
import static com.github.benmanes.caffeine.cache.Caffeine.calculateHashMapCapacity;
19
import static com.github.benmanes.caffeine.cache.LocalAsyncCache.composeResult;
20
import static java.util.Objects.requireNonNull;
21

22
import java.lang.System.Logger;
23
import java.lang.System.Logger.Level;
24
import java.lang.reflect.Method;
25
import java.util.Collections;
26
import java.util.LinkedHashMap;
27
import java.util.Map;
28
import java.util.Set;
29
import java.util.concurrent.CancellationException;
30
import java.util.concurrent.CompletableFuture;
31
import java.util.concurrent.CompletionException;
32
import java.util.concurrent.TimeoutException;
33
import java.util.function.Function;
34

35
import org.checkerframework.checker.nullness.qual.Nullable;
36

37
/**
38
 * This class provides a skeletal implementation of the {@link LoadingCache} interface to minimize
39
 * the effort required to implement a {@link LocalCache}.
40
 *
41
 * @author ben.manes@gmail.com (Ben Manes)
42
 */
43
interface LocalLoadingCache<K, V> extends LocalManualCache<K, V>, LoadingCache<K, V> {
44
  Logger logger = System.getLogger(LocalLoadingCache.class.getName());
1✔
45

46
  /** Returns the {@link AsyncCacheLoader} used by this cache. */
47
  AsyncCacheLoader<? super K, V> cacheLoader();
48

49
  /** Returns the {@link CacheLoader#load} as a mapping function. */
50
  Function<K, V> mappingFunction();
51

52
  /** Returns the {@link CacheLoader#loadAll} as a mapping function, if implemented. */
53
  @Nullable Function<Set<? extends K>, Map<K, V>> bulkMappingFunction();
54

55
  @Override
56
  @SuppressWarnings("NullAway")
57
  default V get(K key) {
58
    return cache().computeIfAbsent(key, mappingFunction());
1✔
59
  }
60

61
  @Override
62
  default Map<K, V> getAll(Iterable<? extends K> keys) {
63
    Function<Set<? extends K>, Map<K, V>> mappingFunction = bulkMappingFunction();
1✔
64
    return (mappingFunction == null)
1✔
65
        ? loadSequentially(keys)
1✔
66
        : getAll(keys, mappingFunction);
1✔
67
  }
68

69
  /** Sequentially loads each missing entry. */
70
  default Map<K, V> loadSequentially(Iterable<? extends K> keys) {
71
    var result = new LinkedHashMap<K, V>(calculateHashMapCapacity(keys));
1✔
72
    for (K key : keys) {
1✔
73
      result.put(key, null);
1✔
74
    }
1✔
75

76
    int count = 0;
1✔
77
    try {
78
      for (var iter = result.entrySet().iterator(); iter.hasNext();) {
1✔
79
        Map.Entry<K, V> entry = iter.next();
1✔
80
        count++;
1✔
81

82
        V value = get(entry.getKey());
1✔
83
        if (value == null) {
1✔
84
          iter.remove();
1✔
85
        } else {
86
          entry.setValue(value);
1✔
87
        }
88
      }
1✔
89
    } catch (Throwable t) {
1✔
90
      cache().statsCounter().recordMisses(result.size() - count);
1✔
91
      throw t;
1✔
92
    }
1✔
93
    return Collections.unmodifiableMap(result);
1✔
94
  }
95

96
  @Override
97
  @SuppressWarnings("FutureReturnValueIgnored")
98
  default CompletableFuture<V> refresh(K key) {
99
    requireNonNull(key);
1✔
100

101
    long[] startTime = new long[1];
1✔
102
    @SuppressWarnings("unchecked")
103
    V[] oldValue = (V[]) new Object[1];
1✔
104
    @SuppressWarnings({"rawtypes", "unchecked"})
105
    CompletableFuture<? extends V>[] reloading = new CompletableFuture[1];
1✔
106
    Object keyReference = cache().referenceKey(key);
1✔
107

108
    var future = cache().refreshes().compute(keyReference, (k, existing) -> {
1✔
109
      if ((existing != null) && !Async.isReady(existing) && !cache().isPendingEviction(key)) {
1✔
110
        return existing;
1✔
111
      }
112

113
      try {
114
        startTime[0] = cache().statsTicker().read();
1✔
115
        oldValue[0] = cache().getIfPresentQuietly(key);
1✔
116
        var refreshFuture = (oldValue[0] == null)
1✔
117
            ? cacheLoader().asyncLoad(key, cache().executor())
1✔
118
            : cacheLoader().asyncReload(key, oldValue[0], cache().executor());
1✔
119
        reloading[0] = requireNonNull(refreshFuture, "Null future");
1✔
120
        return refreshFuture;
1✔
121
      } catch (RuntimeException e) {
1✔
122
        throw e;
1✔
123
      } catch (InterruptedException e) {
1✔
124
        Thread.currentThread().interrupt();
1✔
125
        throw new CompletionException(e);
1✔
126
      } catch (Exception e) {
1✔
127
        throw new CompletionException(e);
1✔
128
      }
129
    });
130

131
    if (reloading[0] != null) {
1✔
132
      reloading[0].whenComplete((newValue, error) -> {
1✔
133
        long loadTime = cache().statsTicker().read() - startTime[0];
1✔
134
        if (error != null) {
1✔
135
          if (!(error instanceof CancellationException) && !(error instanceof TimeoutException)) {
1✔
136
            logger.log(Level.WARNING, "Exception thrown during refresh", error);
1✔
137
          }
138
          cache().refreshes().remove(keyReference, reloading[0]);
1✔
139
          cache().statsCounter().recordLoadFailure(loadTime);
1✔
140
          return;
1✔
141
        }
142

143
        boolean[] discard = new boolean[1];
1✔
144
        var value = cache().compute(key, (k, currentValue) -> {
1✔
145
          boolean removed = cache().refreshes().remove(keyReference, reloading[0]);
1✔
146
          if (removed && (currentValue == oldValue[0])) {
1✔
147
            return (currentValue == null) && (newValue == null) ? null : newValue;
1✔
148
          }
149
          discard[0] = (currentValue != newValue);
1✔
150
          return currentValue;
1✔
151
        }, cache().expiry(), /* recordLoad */ false, /* recordLoadFailure */ true);
1✔
152

153
        if (discard[0] && (newValue != null)) {
1✔
154
          var cause = (value == null) ? RemovalCause.EXPLICIT : RemovalCause.REPLACED;
1✔
155
          cache().notifyRemoval(key, newValue, cause);
1✔
156
        }
157
        if (newValue == null) {
1✔
158
          cache().statsCounter().recordLoadFailure(loadTime);
1✔
159
        } else {
160
          cache().statsCounter().recordLoadSuccess(loadTime);
1✔
161
        }
162
      });
1✔
163
    }
164

165
    @SuppressWarnings("unchecked")
166
    CompletableFuture<V> castedFuture = (CompletableFuture<V>) future;
1✔
167
    return castedFuture;
1✔
168
  }
169

170
  @Override
171
  default CompletableFuture<Map<K, V>> refreshAll(Iterable<? extends K> keys) {
172
    var result = new LinkedHashMap<K, CompletableFuture<V>>(calculateHashMapCapacity(keys));
1✔
173
    for (K key : keys) {
1✔
174
      result.computeIfAbsent(key, this::refresh);
1✔
175
    }
1✔
176
    return composeResult(result);
1✔
177
  }
178

179
  /** Returns a mapping function that adapts to {@link CacheLoader#load}. */
180
  static <K, V> Function<K, V> newMappingFunction(CacheLoader<? super K, V> cacheLoader) {
181
    return key -> {
1✔
182
      try {
183
        return cacheLoader.load(key);
1✔
184
      } catch (RuntimeException e) {
1✔
185
        throw e;
1✔
186
      } catch (InterruptedException e) {
1✔
187
        Thread.currentThread().interrupt();
1✔
188
        throw new CompletionException(e);
1✔
189
      } catch (Exception e) {
1✔
190
        throw new CompletionException(e);
1✔
191
      }
192
    };
193
  }
194

195
  /** Returns a mapping function that adapts to {@link CacheLoader#loadAll}, if implemented. */
196
  static <K, V> @Nullable Function<Set<? extends K>, Map<K, V>> newBulkMappingFunction(
197
      CacheLoader<? super K, V> cacheLoader) {
198
    if (!hasLoadAll(cacheLoader)) {
1✔
199
      return null;
1✔
200
    }
201
    return keysToLoad -> {
1✔
202
      try {
203
        @SuppressWarnings("unchecked")
204
        Map<K, V> loaded = (Map<K, V>) cacheLoader.loadAll(keysToLoad);
1✔
205
        return loaded;
1✔
206
      } catch (RuntimeException e) {
1✔
207
        throw e;
1✔
208
      } catch (InterruptedException e) {
1✔
209
        Thread.currentThread().interrupt();
1✔
210
        throw new CompletionException(e);
1✔
211
      } catch (Exception e) {
1✔
212
        throw new CompletionException(e);
1✔
213
      }
214
    };
215
  }
216

217
  /** Returns whether the supplied cache loader has bulk load functionality. */
218
  static boolean hasLoadAll(CacheLoader<?, ?> loader) {
219
    try {
220
      Method classLoadAll = loader.getClass().getMethod("loadAll", Set.class);
1✔
221
      Method defaultLoadAll = CacheLoader.class.getMethod("loadAll", Set.class);
1✔
222
      return !classLoadAll.equals(defaultLoadAll);
1✔
223
    } catch (NoSuchMethodException | SecurityException e) {
×
224
      logger.log(Level.WARNING, "Cannot determine if CacheLoader can bulk load", e);
×
225
      return false;
×
226
    }
227
  }
228
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc