Merge "Automatically check for refs/meta/config changes"

This commit is contained in:
Shawn O. Pearce 2011-05-20 17:50:49 -07:00 committed by Android Code Review
commit b7ebfe1d90
4 changed files with 94 additions and 4 deletions

View File

@ -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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -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<Project.NameKey, ProjectState> byName;
private final Cache<ListKey,SortedSet<Project.NameKey>> list;
private final Lock listLock;
private volatile long generation;
@Inject
ProjectCacheImpl(
@Named(CACHE_NAME) final Cache<Project.NameKey, ProjectState> byName,
@Named(CACHE_LIST) final Cache<ListKey, SortedSet<Project.NameKey>> list) {
@Named(CACHE_LIST) final Cache<ListKey, SortedSet<Project.NameKey>> 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. */

View File

@ -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<AccountGroup.UUID> 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<AccountGroup.UUID> groups = new HashSet<AccountGroup.UUID>();
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();
}

View File

@ -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());
}