diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt index b288c89e2b..39eb057a56 100644 --- a/Documentation/config-gerrit.txt +++ b/Documentation/config-gerrit.txt @@ -485,6 +485,21 @@ configuration. + Default is true, enabled. +cache.projects.checkFrequency:: ++ +How often project configuration should be checked for update from Git. +Gerrit Code Review caches project access rules and configuration in +memory, checking the refs/meta/config branch every checkFrequency +minutes to see if a new revision should be loaded and used for future +access. Values can be specified using standard time unit abbreviations +('ms', 'sec', 'min', etc.). ++ +If set to 0, checks occur every time, which may slow down operations. +Administrators may force the cache to flush with +link:cmd-flush-caches.html[gerrit flush-caches]. ++ +Default is 5 minutes. + [[commentlink]]Section commentlink ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java index 6459e3c2ff..3990d06d07 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java @@ -19,6 +19,8 @@ import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.server.cache.Cache; import com.google.gerrit.server.cache.CacheModule; import com.google.gerrit.server.cache.EntryCreator; +import com.google.gerrit.server.config.ConfigUtil; +import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.ProjectConfig; import com.google.gwtorm.client.SchemaFactory; @@ -29,6 +31,7 @@ import com.google.inject.TypeLiteral; import com.google.inject.name.Named; import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Repository; import java.util.Collections; @@ -36,6 +39,8 @@ import java.util.Iterator; import java.util.NoSuchElementException; import java.util.SortedSet; import java.util.TreeSet; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -66,14 +71,37 @@ public class ProjectCacheImpl implements ProjectCache { private final Cache byName; private final Cache> list; private final Lock listLock; + private volatile long generation; @Inject ProjectCacheImpl( @Named(CACHE_NAME) final Cache byName, - @Named(CACHE_LIST) final Cache> list) { + @Named(CACHE_LIST) final Cache> list, + @GerritServerConfig final Config serverConfig) { this.byName = byName; this.list = list; this.listLock = new ReentrantLock(true /* fair */); + + long checkFrequencyMillis = TimeUnit.MILLISECONDS.convert( + ConfigUtil.getTimeUnit(serverConfig, + "cache", "projects", "checkFrequency", + 5, TimeUnit.MINUTES), TimeUnit.MINUTES); + if (10 < checkFrequencyMillis) { + // Start with generation 1 (to avoid magic 0 below). + generation = 1; + Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + // This is not exactly thread-safe, but is OK for our use. + // The only thread that writes the volatile is this task. + generation = generation + 1; + } + }, checkFrequencyMillis, checkFrequencyMillis, TimeUnit.MILLISECONDS); + } else { + // Magic generation 0 triggers ProjectState to always + // check on each needsRefresh() request we make to it. + generation = 0; + } } /** @@ -83,7 +111,12 @@ public class ProjectCacheImpl implements ProjectCache { * @return the cached data; null if no such project exists. */ public ProjectState get(final Project.NameKey projectName) { - return byName.get(projectName); + ProjectState state = byName.get(projectName); + if (state != null && state.needsRefresh(generation)) { + byName.remove(projectName); + state = byName.get(projectName); + } + return state; } /** Invalidate the cached information about the given project. */ diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java index 73765e8756..0d297cd71d 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java @@ -23,10 +23,15 @@ import com.google.gerrit.reviewdb.Project; import com.google.gerrit.server.AnonymousUser; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.config.WildProjectName; +import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.ProjectConfig; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; + +import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -44,21 +49,28 @@ public class ProjectState { private final Project.NameKey wildProject; private final ProjectCache projectCache; private final ProjectControl.AssistedFactory projectControlFactory; + private final GitRepositoryManager gitMgr; private final ProjectConfig config; private final Set localOwners; + /** Last system time the configuration's revision was examined. */ + private transient long lastCheckTime; + @Inject protected ProjectState(final AnonymousUser anonymousUser, final ProjectCache projectCache, @WildProjectName final Project.NameKey wildProject, final ProjectControl.AssistedFactory projectControlFactory, + final GitRepositoryManager gitMgr, @Assisted final ProjectConfig config) { this.anonymousUser = anonymousUser; this.projectCache = projectCache; this.wildProject = wildProject; this.projectControlFactory = projectControlFactory; + this.gitMgr = gitMgr; this.config = config; + this.lastCheckTime = System.currentTimeMillis(); HashSet groups = new HashSet(); AccessSection all = config.getAccessSection(AccessSection.ALL); @@ -76,6 +88,34 @@ public class ProjectState { localOwners = Collections.unmodifiableSet(groups); } + boolean needsRefresh(long generation) { + if (generation <= 0) { + return isRevisionOutOfDate(); + } + if (lastCheckTime != generation) { + lastCheckTime = generation; + return isRevisionOutOfDate(); + } + return false; + } + + private boolean isRevisionOutOfDate() { + try { + Repository git = gitMgr.openRepository(getProject().getNameKey()); + try { + Ref ref = git.getRef(GitRepositoryManager.REF_CONFIG); + if (ref == null || ref.getObjectId() == null) { + return true; + } + return !ref.getObjectId().equals(config.getRevision()); + } finally { + git.close(); + } + } catch (IOException gone) { + return true; + } + } + public Project getProject() { return getConfig().getProject(); } diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java index a037e30f7b..ab5d75f087 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java @@ -31,6 +31,7 @@ import com.google.gerrit.server.AnonymousUser; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.config.AuthConfig; import com.google.gerrit.server.config.GerritServerConfig; +import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.ProjectConfig; import com.google.inject.AbstractModule; import com.google.inject.Guice; @@ -316,12 +317,13 @@ public class RefControlTest extends TestCase { } }; + GitRepositoryManager mgr = null; Project.NameKey wildProject = new Project.NameKey("-- All Projects --"); ProjectControl.AssistedFactory projectControlFactory = null; all.put(local.getProject().getNameKey(), new ProjectState(anonymousUser, - projectCache, wildProject, projectControlFactory, local)); + projectCache, wildProject, projectControlFactory, mgr, local)); all.put(parent.getProject().getNameKey(), new ProjectState(anonymousUser, - projectCache, wildProject, projectControlFactory, parent)); + projectCache, wildProject, projectControlFactory, mgr, parent)); return all.get(local.getProject().getNameKey()); }