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