LCOV - code coverage report
Current view: top level - server/cache - PerThreadCache.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 28 29 96.6 %
Date: 2022-11-19 15:00:39 Functions: 12 12 100.0 %

          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             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750