LCOV - code coverage report
Current view: top level - server/git - SearchingChangeCacheImpl.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 53 55 96.4 %
Date: 2022-11-19 15:00:39 Functions: 12 12 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2012 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.git;
      16             : 
      17             : import com.google.auto.value.AutoValue;
      18             : import com.google.common.cache.CacheLoader;
      19             : import com.google.common.cache.LoadingCache;
      20             : import com.google.common.flogger.FluentLogger;
      21             : import com.google.gerrit.common.Nullable;
      22             : import com.google.gerrit.common.UsedAt;
      23             : import com.google.gerrit.entities.Change;
      24             : import com.google.gerrit.entities.Project;
      25             : import com.google.gerrit.entities.RefNames;
      26             : import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
      27             : import com.google.gerrit.extensions.registration.DynamicSet;
      28             : import com.google.gerrit.server.ReviewerSet;
      29             : import com.google.gerrit.server.cache.CacheModule;
      30             : import com.google.gerrit.server.index.change.ChangeField;
      31             : import com.google.gerrit.server.logging.Metadata;
      32             : import com.google.gerrit.server.logging.TraceContext;
      33             : import com.google.gerrit.server.logging.TraceContext.TraceTimer;
      34             : import com.google.gerrit.server.query.change.ChangeData;
      35             : import com.google.gerrit.server.query.change.InternalChangeQuery;
      36             : import com.google.gerrit.server.util.ManualRequestContext;
      37             : import com.google.gerrit.server.util.OneOffRequestContext;
      38             : import com.google.inject.Inject;
      39             : import com.google.inject.Provider;
      40             : import com.google.inject.Singleton;
      41             : import com.google.inject.TypeLiteral;
      42             : import com.google.inject.name.Named;
      43             : import com.google.inject.util.Providers;
      44             : import java.util.HashMap;
      45             : import java.util.List;
      46             : import java.util.Map;
      47             : import java.util.concurrent.ExecutionException;
      48             : import java.util.stream.Stream;
      49             : 
      50             : /**
      51             :  * Cache based on an index query of the most recent changes. The number of cached items depends on
      52             :  * the index implementation and configuration.
      53             :  *
      54             :  * <p>This cache is intended to be used when filtering references. By design it returns only a
      55             :  * fraction of all changes. These are the changes that were modified last.
      56             :  */
      57             : @Singleton
      58             : public class SearchingChangeCacheImpl implements GitReferenceUpdatedListener {
      59         151 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      60             : 
      61             :   static final String ID_CACHE = "changes";
      62             : 
      63             :   public static class SearchingChangeCacheImplModule extends CacheModule {
      64             :     private final boolean slave;
      65             : 
      66             :     public SearchingChangeCacheImplModule() {
      67          16 :       this(false);
      68          16 :     }
      69             : 
      70         152 :     public SearchingChangeCacheImplModule(boolean slave) {
      71         152 :       this.slave = slave;
      72         152 :     }
      73             : 
      74             :     @Override
      75             :     protected void configure() {
      76         152 :       if (slave) {
      77           4 :         bind(SearchingChangeCacheImpl.class).toProvider(Providers.of(null));
      78             :       } else {
      79         152 :         cache(ID_CACHE, Project.NameKey.class, new TypeLiteral<List<CachedChange>>() {})
      80         152 :             .maximumWeight(0)
      81         152 :             .loader(Loader.class);
      82             : 
      83         152 :         bind(SearchingChangeCacheImpl.class);
      84         152 :         DynamicSet.bind(binder(), GitReferenceUpdatedListener.class)
      85         152 :             .to(SearchingChangeCacheImpl.class);
      86             :       }
      87         152 :     }
      88             :   }
      89             : 
      90             :   @AutoValue
      91             :   @UsedAt(UsedAt.Project.GOOGLE)
      92           8 :   public abstract static class CachedChange {
      93             :     // Subset of fields in ChangeData, specifically fields needed to serve
      94             :     // VisibleRefFilter without touching the database. More can be added as
      95             :     // necessary.
      96             :     abstract Change change();
      97             : 
      98             :     @Nullable
      99             :     abstract ReviewerSet reviewers();
     100             :   }
     101             : 
     102             :   private final LoadingCache<Project.NameKey, List<CachedChange>> cache;
     103             :   private final ChangeData.Factory changeDataFactory;
     104             : 
     105             :   @Inject
     106             :   SearchingChangeCacheImpl(
     107             :       @Named(ID_CACHE) LoadingCache<Project.NameKey, List<CachedChange>> cache,
     108         151 :       ChangeData.Factory changeDataFactory) {
     109         151 :     this.cache = cache;
     110         151 :     this.changeDataFactory = changeDataFactory;
     111         151 :   }
     112             : 
     113             :   /**
     114             :    * Read changes for the project from the secondary index.
     115             :    *
     116             :    * <p>Returned changes only include the {@code Change} object (with id, branch) and the reviewers.
     117             :    * Additional stored fields are not loaded from the index.
     118             :    *
     119             :    * @param project project to read.
     120             :    * @return stream of known changes; empty if no changes.
     121             :    */
     122             :   public Stream<ChangeData> getChangeData(Project.NameKey project) {
     123             :     List<CachedChange> cached;
     124             :     try {
     125           8 :       cached = cache.get(project);
     126           1 :     } catch (ExecutionException e) {
     127           1 :       logger.atWarning().withCause(e).log("Cannot fetch changes for %s", project);
     128           1 :       return Stream.empty();
     129           8 :     }
     130           8 :     return cached.stream()
     131           8 :         .map(
     132             :             cc -> {
     133           8 :               ChangeData cd = changeDataFactory.create(cc.change());
     134           8 :               cd.setReviewers(cc.reviewers());
     135           8 :               return cd;
     136             :             });
     137             :   }
     138             : 
     139             :   @Override
     140             :   public void onGitReferenceUpdated(GitReferenceUpdatedListener.Event event) {
     141         151 :     if (event.getRefName().startsWith(RefNames.REFS_CHANGES)) {
     142         103 :       cache.invalidate(Project.nameKey(event.getProjectName()));
     143             :     }
     144         151 :   }
     145             : 
     146             :   static class Loader extends CacheLoader<Project.NameKey, List<CachedChange>> {
     147             :     private final OneOffRequestContext requestContext;
     148             :     private final Provider<InternalChangeQuery> queryProvider;
     149             : 
     150             :     @Inject
     151         152 :     Loader(OneOffRequestContext requestContext, Provider<InternalChangeQuery> queryProvider) {
     152         152 :       this.requestContext = requestContext;
     153         152 :       this.queryProvider = queryProvider;
     154         152 :     }
     155             : 
     156             :     @Override
     157             :     public List<CachedChange> load(Project.NameKey key) throws Exception {
     158           8 :       try (TraceTimer timer =
     159           8 :               TraceContext.newTimer(
     160           8 :                   "Loading changes of project", Metadata.builder().projectName(key.get()).build());
     161           8 :           ManualRequestContext ctx = requestContext.open()) {
     162           8 :         List<ChangeData> cds =
     163             :             queryProvider
     164           8 :                 .get()
     165           8 :                 .setRequestedFields(ChangeField.CHANGE, ChangeField.REVIEWER_SPEC)
     166           8 :                 .byProject(key);
     167           8 :         Map<Change.Id, CachedChange> result = new HashMap<>(cds.size());
     168           8 :         for (ChangeData cd : cds) {
     169           8 :           if (result.containsKey(cd.getId())) {
     170           0 :             logger.atWarning().log(
     171             :                 "Duplicate changes returned from change query by project %s: %s, %s",
     172           0 :                 key, cd.change(), result.get(cd.getId()).change());
     173             :           }
     174           8 :           result.put(
     175           8 :               cd.getId(),
     176           8 :               new AutoValue_SearchingChangeCacheImpl_CachedChange(cd.change(), cd.reviewers()));
     177           8 :         }
     178           8 :         return List.copyOf(result.values());
     179             :       }
     180             :     }
     181             :   }
     182             : }

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