Bind the project cache clock thread in its own object

This allows me to better control when this thread gets created in a
server. Right now its created 1:1 with the ProjectCacheImpl, which
works fine for servers that have only one site loaded in the JVM.
Longer term we should be supporting virtual hosting, where different
Gerrit Code Review sites are able to be managed from a single JVM,
and a single unified cache footprint (that still shards data uniquely
for each site).

Change-Id: I3e9b269aac7071a2f26a4289b4b524cb468592c7
This commit is contained in:
Shawn O. Pearce
2012-01-23 11:38:21 -08:00
parent 8dba895ad6
commit a595eacf30
2 changed files with 82 additions and 29 deletions

View File

@@ -0,0 +1,78 @@
// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.project;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.jgit.lib.Config;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/** Ticks periodically to force refresh events for {@link ProjectCacheImpl}. */
@Singleton
public class ProjectCacheClock {
private volatile long generation;
@Inject
public ProjectCacheClock(@GerritServerConfig Config serverConfig) {
this(TimeUnit.MILLISECONDS.convert(
ConfigUtil.getTimeUnit(serverConfig,
"cache", "projects", "checkFrequency",
5, TimeUnit.MINUTES), TimeUnit.MINUTES));
}
public ProjectCacheClock(long checkFrequencyMillis) {
if (10 < checkFrequencyMillis) {
// Start with generation 1 (to avoid magic 0 below).
generation = 1;
ThreadFactory factory = new ThreadFactory() {
private final AtomicInteger id = new AtomicInteger();
@Override
public Thread newThread(Runnable runnable) {
Thread thread = Executors.defaultThreadFactory().newThread(runnable);
thread.setName(String.format("ProjectCacheClock-%d", id.incrementAndGet()));
thread.setDaemon(true);
thread.setPriority(Thread.MIN_PRIORITY);
return thread;
}
};
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, factory);
executor.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;
}
}
long read() {
return generation;
}
}

View File

@@ -19,8 +19,6 @@ 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.AllProjectsName;
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.inject.Inject;
@@ -30,7 +28,6 @@ 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;
@@ -38,8 +35,6 @@ 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;
@@ -71,39 +66,19 @@ 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;
private final ProjectCacheClock clock;
@Inject
ProjectCacheImpl(
final AllProjectsName allProjectsName,
@Named(CACHE_NAME) final Cache<Project.NameKey, ProjectState> byName,
@Named(CACHE_LIST) final Cache<ListKey, SortedSet<Project.NameKey>> list,
@GerritServerConfig final Config serverConfig) {
ProjectCacheClock clock) {
this.allProjectsName = allProjectsName;
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;
}
this.clock = clock;
}
@Override
@@ -125,7 +100,7 @@ public class ProjectCacheImpl implements ProjectCache {
*/
public ProjectState get(final Project.NameKey projectName) {
ProjectState state = byName.get(projectName);
if (state != null && state.needsRefresh(generation)) {
if (state != null && state.needsRefresh(clock.read())) {
byName.remove(projectName);
state = byName.get(projectName);
}