Line data Source code
1 : // Copyright (C) 2018 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.cache; 16 : 17 : import static com.google.common.base.Preconditions.checkState; 18 : 19 : import com.google.common.base.Objects; 20 : import com.google.common.collect.ImmutableList; 21 : import com.google.common.collect.Maps; 22 : import com.google.gerrit.common.Nullable; 23 : import java.util.Map; 24 : import java.util.function.Supplier; 25 : 26 : /** 27 : * Caches object instances for a request as {@link ThreadLocal} in the serving thread. 28 : * 29 : * <p>This class is intended to cache objects that have a high instantiation cost, are specific to 30 : * the current request and potentially need to be instantiated multiple times while serving a 31 : * request. 32 : * 33 : * <p>This is different from the key-value storage in {@code CurrentUser}: {@code CurrentUser} 34 : * offers a key-value storage by providing thread-safe {@code get} and {@code put} methods. Once the 35 : * value is retrieved through {@code get} there is not thread-safety anymore - apart from the 36 : * retrieved object guarantees. Depending on the implementation of {@code CurrentUser}, it might be 37 : * shared between the request serving thread as well as sub- or background treads. 38 : * 39 : * <p>In comparison to that, this class guarantees thread safety even on non-thread-safe objects as 40 : * its cache is tied to the serving thread only. While allowing to cache non-thread-safe objects, it 41 : * has the downside of not sharing any objects with background threads or executors. 42 : * 43 : * <p>Lastly, this class offers a cache, that requires callers to also provide a {@code Supplier} in 44 : * case the object is not present in the cache, while {@code CurrentUser} provides a storage where 45 : * just retrieving stored values is a valid operation. 46 : * 47 : * <p>To prevent OOM errors on requests that would cache a lot of objects, this class enforces an 48 : * internal limit after which no new elements are cached. All {@code get} calls are served by 49 : * invoking the {@code Supplier} after that. 50 : */ 51 : public class PerThreadCache implements AutoCloseable { 52 146 : private static final ThreadLocal<PerThreadCache> CACHE = new ThreadLocal<>(); 53 : /** 54 : * Cache at maximum 25 values per thread. This value was chosen arbitrarily. Some endpoints (like 55 : * ListProjects) break the assumption that the data cached in a request is limited. To prevent 56 : * this class from accumulating an unbound number of objects, we enforce this limit. 57 : */ 58 : private static final int PER_THREAD_CACHE_SIZE = 25; 59 : 60 : /** 61 : * Unique key for key-value mappings stored in PerThreadCache. The key is based on the value's 62 : * class and a list of identifiers that in combination uniquely set the object apart form others 63 : * of the same class. 64 : */ 65 : public static final class Key<T> { 66 : private final Class<T> clazz; 67 : private final ImmutableList<Object> identifiers; 68 : 69 : /** 70 : * Returns a key based on the value's class and an identifier that uniquely identify the value. 71 : * The identifier needs to implement {@code equals()} and {@code hashCode()}. 72 : */ 73 : public static <T> Key<T> create(Class<T> clazz, Object identifier) { 74 1 : return new Key<>(clazz, ImmutableList.of(identifier)); 75 : } 76 : 77 : /** 78 : * Returns a key based on the value's class and a set of identifiers that uniquely identify the 79 : * value. Identifiers need to implement {@code equals()} and {@code hashCode()}. 80 : */ 81 : public static <T> Key<T> create(Class<T> clazz, Object... identifiers) { 82 146 : return new Key<>(clazz, ImmutableList.copyOf(identifiers)); 83 : } 84 : 85 146 : private Key(Class<T> clazz, ImmutableList<Object> identifiers) { 86 146 : this.clazz = clazz; 87 146 : this.identifiers = identifiers; 88 146 : } 89 : 90 : @Override 91 : public int hashCode() { 92 103 : return Objects.hashCode(clazz, identifiers); 93 : } 94 : 95 : @Override 96 : public boolean equals(Object o) { 97 101 : if (!(o instanceof Key)) { 98 0 : return false; 99 : } 100 101 : Key<?> other = (Key<?>) o; 101 101 : return this.clazz == other.clazz && this.identifiers.equals(other.identifiers); 102 : } 103 : } 104 : 105 : public static PerThreadCache create() { 106 104 : checkState(CACHE.get() == null, "called create() twice on the same request"); 107 104 : PerThreadCache cache = new PerThreadCache(); 108 104 : CACHE.set(cache); 109 104 : return cache; 110 : } 111 : 112 : @Nullable 113 : public static PerThreadCache get() { 114 146 : return CACHE.get(); 115 : } 116 : 117 : public static <T> T getOrCompute(Key<T> key, Supplier<T> loader) { 118 145 : PerThreadCache cache = get(); 119 145 : return cache != null ? cache.get(key, loader) : loader.get(); 120 : } 121 : 122 104 : private final Map<Key<?>, Object> cache = Maps.newHashMapWithExpectedSize(PER_THREAD_CACHE_SIZE); 123 : 124 104 : private PerThreadCache() {} 125 : 126 : /** 127 : * Returns an instance of {@code T} that was either loaded from the cache or obtained from the 128 : * provided {@link Supplier}. 129 : */ 130 : public <T> T get(Key<T> key, Supplier<T> loader) { 131 : @SuppressWarnings("unchecked") 132 103 : T value = (T) cache.get(key); 133 103 : if (value == null) { 134 103 : value = loader.get(); 135 103 : if (cache.size() < PER_THREAD_CACHE_SIZE) { 136 103 : cache.put(key, value); 137 : } 138 : } 139 103 : return value; 140 : } 141 : 142 : @Override 143 : public void close() { 144 104 : CACHE.remove(); 145 104 : } 146 : }