diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheClock.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheClock.java new file mode 100644 index 0000000000..f86562cf50 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheClock.java @@ -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; + } +} 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 ff687203ec..7a7f56e9da 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,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 byName; private final Cache> list; private final Lock listLock; - private volatile long generation; + private final ProjectCacheClock clock; @Inject ProjectCacheImpl( final AllProjectsName allProjectsName, @Named(CACHE_NAME) final Cache byName, @Named(CACHE_LIST) final Cache> 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); }