Back in-memory caches with Guava, disk caches with H2

Instead of using Ehcache for in-memory caches, use Guava. The Guava
cache code has been more completely tested by Google in high load
production environments, and it tends to have fewer bugs. It enables
caches to be built at any time, rather than only at server startup.

By creating a Guava cache as soon as it is declared, rather than
during the LifecycleListener.start() for the CachePool, we can promise
any downstream consumer of the cache that the cache is ready to
execute requests the moment it is supplied by Guice. This fixes a
startup ordering problem in the GroupCache and the ProjectCache, where
code wants to use one of these caches during startup to resolve a
group or project by name.

Tracking the Gauva backend caches with a DynamicMap makes it possible
for plugins to define their own in-memory caches using CacheModule's
cache() function to declare the cache. It allows the core server to
make the cache available to administrators over SSH with the gerrit
show-caches and gerrit flush-caches commands.

Persistent caches store in a private H2 database per cache, with a
simple one-table schema that stores each entry in a table row as a
pair of serialized objects (key and value). Database reads are gated
by a BloomFilter, to reduce the number of calls made to H2 during
cache misses. In theory less than 3% of cache misses will reach H2 and
find nothing. Stores happen on a background thread quickly after the
put is made to the cache, reducing the risk that a diff or web_session
record is lost during an ungraceful shutdown.

Cache databases are capped around 128M worth of stored data by running
a prune cycle each day at 1 AM local server time. Records are removed
from the database by ordering on the last access time, where last
accessed is the last time the record was moved from disk to memory.

Change-Id: Ia82d056796b5af9bcb1f219fe06d905c9c0fbc84
This commit is contained in:
Shawn O. Pearce
2012-05-24 14:28:40 -07:00
parent 34d4d1929a
commit 2e1cb2b849
76 changed files with 2432 additions and 1674 deletions

View File

@@ -14,12 +14,14 @@
package com.google.gerrit.server.account;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.server.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.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Module;
@@ -27,45 +29,58 @@ import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutionException;
/** Translates an email address to a set of matching accounts. */
@Singleton
public class AccountByEmailCacheImpl implements AccountByEmailCache {
private static final Logger log = LoggerFactory
.getLogger(AccountByEmailCacheImpl.class);
private static final String CACHE_NAME = "accounts_byemail";
public static Module module() {
return new CacheModule() {
@Override
protected void configure() {
final TypeLiteral<Cache<String, Set<Account.Id>>> type =
new TypeLiteral<Cache<String, Set<Account.Id>>>() {};
core(type, CACHE_NAME).populateWith(Loader.class);
cache(CACHE_NAME,
String.class,
new TypeLiteral<Set<Account.Id>>() {})
.loader(Loader.class);
bind(AccountByEmailCacheImpl.class);
bind(AccountByEmailCache.class).to(AccountByEmailCacheImpl.class);
}
};
}
private final Cache<String, Set<Account.Id>> cache;
private final LoadingCache<String, Set<Account.Id>> cache;
@Inject
AccountByEmailCacheImpl(
@Named(CACHE_NAME) final Cache<String, Set<Account.Id>> cache) {
@Named(CACHE_NAME) LoadingCache<String, Set<Account.Id>> cache) {
this.cache = cache;
}
public Set<Account.Id> get(final String email) {
return cache.get(email);
try {
return cache.get(email);
} catch (ExecutionException e) {
log.warn("Cannot resolve accounts by email", e);
return Collections.emptySet();
}
}
public void evict(final String email) {
cache.remove(email);
if (email != null) {
cache.invalidate(email);
}
}
static class Loader extends EntryCreator<String, Set<Account.Id>> {
static class Loader extends CacheLoader<String, Set<Account.Id>> {
private final SchemaFactory<ReviewDb> schema;
@Inject
@@ -74,10 +89,10 @@ public class AccountByEmailCacheImpl implements AccountByEmailCache {
}
@Override
public Set<Account.Id> createEntry(final String email) throws Exception {
public Set<Account.Id> load(String email) throws Exception {
final ReviewDb db = schema.open();
try {
final HashSet<Account.Id> r = new HashSet<Account.Id>();
Set<Account.Id> r = Sets.newHashSet();
for (Account a : db.accounts().byPreferredEmail(email)) {
r.add(a.getId());
}
@@ -85,30 +100,10 @@ public class AccountByEmailCacheImpl implements AccountByEmailCache {
.byEmailAddress(email)) {
r.add(a.getAccountId());
}
return pack(r);
return ImmutableSet.copyOf(r);
} finally {
db.close();
}
}
@Override
public Set<Account.Id> missing(final String key) {
return Collections.emptySet();
}
private static Set<Account.Id> pack(final Set<Account.Id> c) {
switch (c.size()) {
case 0:
return Collections.emptySet();
case 1:
return one(c);
default:
return Collections.unmodifiableSet(new HashSet<Account.Id>(c));
}
}
private static <T> Set<T> one(final Set<T> c) {
return Collections.singleton(c.iterator().next());
}
}
}

View File

@@ -14,14 +14,16 @@
package com.google.gerrit.server.account;
import com.google.common.base.Optional;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gerrit.reviewdb.server.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.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
@@ -30,14 +32,21 @@ import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutionException;
/** Caches important (but small) account state to avoid database hits. */
@Singleton
public class AccountCacheImpl implements AccountCache {
private static final Logger log = LoggerFactory
.getLogger(AccountCacheImpl.class);
private static final String BYID_NAME = "accounts";
private static final String BYUSER_NAME = "accounts_byname";
@@ -45,13 +54,13 @@ public class AccountCacheImpl implements AccountCache {
return new CacheModule() {
@Override
protected void configure() {
final TypeLiteral<Cache<Account.Id, AccountState>> byIdType =
new TypeLiteral<Cache<Account.Id, AccountState>>() {};
core(byIdType, BYID_NAME).populateWith(ByIdLoader.class);
cache(BYID_NAME, Account.Id.class, AccountState.class)
.loader(ByIdLoader.class);
final TypeLiteral<Cache<String, Account.Id>> byUsernameType =
new TypeLiteral<Cache<String, Account.Id>>() {};
core(byUsernameType, BYUSER_NAME).populateWith(ByNameLoader.class);
cache(BYUSER_NAME,
String.class,
new TypeLiteral<Optional<Account.Id>>() {})
.loader(ByNameLoader.class);
bind(AccountCacheImpl.class);
bind(AccountCache.class).to(AccountCacheImpl.class);
@@ -59,54 +68,76 @@ public class AccountCacheImpl implements AccountCache {
};
}
private final Cache<Account.Id, AccountState> byId;
private final Cache<String, Account.Id> byName;
private final LoadingCache<Account.Id, AccountState> byId;
private final LoadingCache<String, Optional<Account.Id>> byName;
@Inject
AccountCacheImpl(@Named(BYID_NAME) Cache<Account.Id, AccountState> byId,
@Named(BYUSER_NAME) Cache<String, Account.Id> byUsername) {
AccountCacheImpl(@Named(BYID_NAME) LoadingCache<Account.Id, AccountState> byId,
@Named(BYUSER_NAME) LoadingCache<String, Optional<Account.Id>> byUsername) {
this.byId = byId;
this.byName = byUsername;
}
public AccountState get(final Account.Id accountId) {
return byId.get(accountId);
public AccountState get(Account.Id accountId) {
try {
return byId.get(accountId);
} catch (ExecutionException e) {
log.warn("Cannot load AccountState for " + accountId, e);
return missing(accountId);
}
}
@Override
public AccountState getByUsername(String username) {
Account.Id id = byName.get(username);
return id != null ? byId.get(id) : null;
try {
Optional<Account.Id> id = byName.get(username);
return id != null && id.isPresent() ? byId.get(id.get()) : null;
} catch (ExecutionException e) {
log.warn("Cannot load AccountState for " + username, e);
return null;
}
}
public void evict(final Account.Id accountId) {
byId.remove(accountId);
public void evict(Account.Id accountId) {
if (accountId != null) {
byId.invalidate(accountId);
}
}
public void evictByUsername(String username) {
byName.remove(username);
if (username != null) {
byName.invalidate(username);
}
}
static class ByIdLoader extends EntryCreator<Account.Id, AccountState> {
private static AccountState missing(Account.Id accountId) {
Account account = new Account(accountId);
Collection<AccountExternalId> ids = Collections.emptySet();
Set<AccountGroup.UUID> anon = ImmutableSet.of(AccountGroup.ANONYMOUS_USERS);
return new AccountState(account, anon, ids);
}
static class ByIdLoader extends CacheLoader<Account.Id, AccountState> {
private final SchemaFactory<ReviewDb> schema;
private final GroupCache groupCache;
private final Cache<String, Account.Id> byName;
private final LoadingCache<String, Optional<Account.Id>> byName;
@Inject
ByIdLoader(SchemaFactory<ReviewDb> sf, GroupCache groupCache,
@Named(BYUSER_NAME) Cache<String, Account.Id> byUsername) {
@Named(BYUSER_NAME) LoadingCache<String, Optional<Account.Id>> byUsername) {
this.schema = sf;
this.groupCache = groupCache;
this.byName = byUsername;
}
@Override
public AccountState createEntry(final Account.Id key) throws Exception {
public AccountState load(Account.Id key) throws Exception {
final ReviewDb db = schema.open();
try {
final AccountState state = load(db, key);
if (state.getUserName() != null) {
byName.put(state.getUserName(), state.getAccount().getId());
String user = state.getUserName();
if (user != null) {
byName.put(user, Optional.of(state.getAccount().getId()));
}
return state;
} finally {
@@ -142,18 +173,9 @@ public class AccountCacheImpl implements AccountCache {
return new AccountState(account, internalGroups, externalIds);
}
@Override
public AccountState missing(final Account.Id accountId) {
final Account account = new Account(accountId);
final Collection<AccountExternalId> ids = Collections.emptySet();
final Set<AccountGroup.UUID> anonymous =
Collections.singleton(AccountGroup.ANONYMOUS_USERS);
return new AccountState(account, anonymous, ids);
}
}
static class ByNameLoader extends EntryCreator<String, Account.Id> {
static class ByNameLoader extends CacheLoader<String, Optional<Account.Id>> {
private final SchemaFactory<ReviewDb> schema;
@Inject
@@ -162,14 +184,17 @@ public class AccountCacheImpl implements AccountCache {
}
@Override
public Account.Id createEntry(final String username) throws Exception {
public Optional<Account.Id> load(String username) throws Exception {
final ReviewDb db = schema.open();
try {
final AccountExternalId.Key key = new AccountExternalId.Key( //
AccountExternalId.SCHEME_USERNAME, //
username);
final AccountExternalId id = db.accountExternalIds().get(key);
return id != null ? id.getAccountId() : null;
if (id != null) {
return Optional.of(id.getAccountId());
}
return Optional.absent();
} finally {
db.close();
}

View File

@@ -14,12 +14,16 @@
package com.google.gerrit.server.account;
import com.google.common.base.Optional;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupName;
import com.google.gerrit.reviewdb.server.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.gwtorm.server.OrmDuplicateKeyException;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Module;
@@ -27,48 +31,48 @@ import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import java.util.ArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.ExecutionException;
/** Tracks group objects in memory for efficient access. */
@Singleton
public class GroupCacheImpl implements GroupCache {
private static final Logger log = LoggerFactory
.getLogger(GroupCacheImpl.class);
private static final String BYID_NAME = "groups";
private static final String BYNAME_NAME = "groups_byname";
private static final String BYUUID_NAME = "groups_byuuid";
private static final String BYEXT_NAME = "groups_byext";
private static final String BYNAME_LIST = "groups_byname_list";
public static Module module() {
return new CacheModule() {
@Override
protected void configure() {
final TypeLiteral<Cache<AccountGroup.Id, AccountGroup>> byId =
new TypeLiteral<Cache<AccountGroup.Id, AccountGroup>>() {};
core(byId, BYID_NAME).populateWith(ByIdLoader.class);
cache(BYID_NAME,
AccountGroup.Id.class,
new TypeLiteral<Optional<AccountGroup>>() {})
.loader(ByIdLoader.class);
final TypeLiteral<Cache<AccountGroup.NameKey, AccountGroup>> byName =
new TypeLiteral<Cache<AccountGroup.NameKey, AccountGroup>>() {};
core(byName, BYNAME_NAME).populateWith(ByNameLoader.class);
cache(BYNAME_NAME,
String.class,
new TypeLiteral<Optional<AccountGroup>>() {})
.loader(ByNameLoader.class);
final TypeLiteral<Cache<AccountGroup.UUID, AccountGroup>> byUUID =
new TypeLiteral<Cache<AccountGroup.UUID, AccountGroup>>() {};
core(byUUID, BYUUID_NAME).populateWith(ByUUIDLoader.class);
cache(BYUUID_NAME,
String.class,
new TypeLiteral<Optional<AccountGroup>>() {})
.loader(ByUUIDLoader.class);
final TypeLiteral<Cache<AccountGroup.ExternalNameKey, Collection<AccountGroup>>> byExternalName =
new TypeLiteral<Cache<AccountGroup.ExternalNameKey, Collection<AccountGroup>>>() {};
core(byExternalName, BYEXT_NAME) //
.populateWith(ByExternalNameLoader.class);
final TypeLiteral<Cache<ListKey, SortedSet<AccountGroup.NameKey>>> listType =
new TypeLiteral<Cache<ListKey, SortedSet<AccountGroup.NameKey>>>() {};
core(listType, BYNAME_LIST).populateWith(Lister.class);
cache(BYEXT_NAME,
String.class,
new TypeLiteral<Collection<AccountGroup>>() {})
.loader(ByExternalNameLoader.class);
bind(GroupCacheImpl.class);
bind(GroupCache.class).to(GroupCacheImpl.class);
@@ -76,94 +80,126 @@ public class GroupCacheImpl implements GroupCache {
};
}
private final Cache<AccountGroup.Id, AccountGroup> byId;
private final Cache<AccountGroup.NameKey, AccountGroup> byName;
private final Cache<AccountGroup.UUID, AccountGroup> byUUID;
private final Cache<AccountGroup.ExternalNameKey, Collection<AccountGroup>> byExternalName;
private final Cache<ListKey,SortedSet<AccountGroup.NameKey>> list;
private final Lock listLock;
private final LoadingCache<AccountGroup.Id, Optional<AccountGroup>> byId;
private final LoadingCache<String, Optional<AccountGroup>> byName;
private final LoadingCache<String, Optional<AccountGroup>> byUUID;
private final LoadingCache<String, Collection<AccountGroup>> byExternalName;
private final SchemaFactory<ReviewDb> schema;
@Inject
GroupCacheImpl(
@Named(BYID_NAME) Cache<AccountGroup.Id, AccountGroup> byId,
@Named(BYNAME_NAME) Cache<AccountGroup.NameKey, AccountGroup> byName,
@Named(BYUUID_NAME) Cache<AccountGroup.UUID, AccountGroup> byUUID,
@Named(BYEXT_NAME) Cache<AccountGroup.ExternalNameKey, Collection<AccountGroup>> byExternalName,
@Named(BYNAME_LIST) final Cache<ListKey, SortedSet<AccountGroup.NameKey>> list) {
@Named(BYID_NAME) LoadingCache<AccountGroup.Id, Optional<AccountGroup>> byId,
@Named(BYNAME_NAME) LoadingCache<String, Optional<AccountGroup>> byName,
@Named(BYUUID_NAME) LoadingCache<String, Optional<AccountGroup>> byUUID,
@Named(BYEXT_NAME) LoadingCache<String, Collection<AccountGroup>> byExternalName,
SchemaFactory<ReviewDb> schema) {
this.byId = byId;
this.byName = byName;
this.byUUID = byUUID;
this.byExternalName = byExternalName;
this.list = list;
this.listLock = new ReentrantLock(true /* fair */);
this.schema = schema;
}
public AccountGroup get(final AccountGroup.Id groupId) {
return byId.get(groupId);
try {
Optional<AccountGroup> g = byId.get(groupId);
return g.isPresent() ? g.get() : missing(groupId);
} catch (ExecutionException e) {
log.warn("Cannot load group "+groupId, e);
return missing(groupId);
}
}
public void evict(final AccountGroup group) {
byId.remove(group.getId());
byName.remove(group.getNameKey());
byUUID.remove(group.getGroupUUID());
byExternalName.remove(group.getExternalNameKey());
if (group.getId() != null) {
byId.invalidate(group.getId());
}
if (group.getNameKey() != null) {
byName.invalidate(group.getNameKey().get());
}
if (group.getGroupUUID() != null) {
byUUID.invalidate(group.getGroupUUID().get());
}
if (group.getExternalNameKey() != null) {
byExternalName.invalidate(group.getExternalNameKey().get());
}
}
public void evictAfterRename(final AccountGroup.NameKey oldName,
final AccountGroup.NameKey newName) {
byName.remove(oldName);
updateGroupList(oldName, newName);
if (oldName != null) {
byName.invalidate(oldName.get());
}
if (newName != null) {
byName.invalidate(newName.get());
}
}
public AccountGroup get(final AccountGroup.NameKey name) {
return byName.get(name);
public AccountGroup get(AccountGroup.NameKey name) {
if (name == null) {
return null;
}
try {
return byName.get(name.get()).orNull();
} catch (ExecutionException e) {
log.warn(String.format("Cannot lookup group %s by name", name.get()), e);
return null;
}
}
public AccountGroup get(final AccountGroup.UUID uuid) {
return byUUID.get(uuid);
public AccountGroup get(AccountGroup.UUID uuid) {
if (uuid == null) {
return null;
}
try {
return byUUID.get(uuid.get()).orNull();
} catch (ExecutionException e) {
log.warn(String.format("Cannot lookup group %s by name", uuid.get()), e);
return null;
}
}
public Collection<AccountGroup> get(
final AccountGroup.ExternalNameKey externalName) {
return byExternalName.get(externalName);
public Collection<AccountGroup> get(AccountGroup.ExternalNameKey name) {
if (name == null) {
return Collections.emptyList();
}
try {
return byExternalName.get(name.get());
} catch (ExecutionException e) {
log.warn("Cannot lookup external group " + name, e);
return Collections.emptyList();
}
}
@Override
public Iterable<AccountGroup> all() {
final List<AccountGroup> groups = new ArrayList<AccountGroup>();
for (final AccountGroup.NameKey groupName : list.get(ListKey.ALL)) {
final AccountGroup group = get(groupName);
if (group != null) {
groups.add(group);
try {
ReviewDb db = schema.open();
try {
return Collections.unmodifiableList(db.accountGroups().all().toList());
} finally {
db.close();
}
} catch (OrmException e) {
log.warn("Cannot list internal groups", e);
return Collections.emptyList();
}
return Collections.unmodifiableList(groups);
}
@Override
public void onCreateGroup(final AccountGroup.NameKey newGroupName) {
updateGroupList(null, newGroupName);
public void onCreateGroup(AccountGroup.NameKey newGroupName) {
byName.invalidate(newGroupName.get());
}
private void updateGroupList(final AccountGroup.NameKey nameToRemove,
final AccountGroup.NameKey nameToAdd) {
listLock.lock();
try {
SortedSet<AccountGroup.NameKey> n = list.get(ListKey.ALL);
n = new TreeSet<AccountGroup.NameKey>(n);
if (nameToRemove != null) {
n.remove(nameToRemove);
}
if (nameToAdd != null) {
n.add(nameToAdd);
}
list.put(ListKey.ALL, Collections.unmodifiableSortedSet(n));
} finally {
listLock.unlock();
}
private static AccountGroup missing(AccountGroup.Id key) {
AccountGroup.NameKey name = new AccountGroup.NameKey("Deleted Group" + key);
AccountGroup g = new AccountGroup(name, key, null);
g.setType(AccountGroup.Type.SYSTEM);
return g;
}
static class ByIdLoader extends EntryCreator<AccountGroup.Id, AccountGroup> {
static class ByIdLoader extends
CacheLoader<AccountGroup.Id, Optional<AccountGroup>> {
private final SchemaFactory<ReviewDb> schema;
@Inject
@@ -172,32 +208,18 @@ public class GroupCacheImpl implements GroupCache {
}
@Override
public AccountGroup createEntry(final AccountGroup.Id key) throws Exception {
public Optional<AccountGroup> load(final AccountGroup.Id key)
throws Exception {
final ReviewDb db = schema.open();
try {
final AccountGroup group = db.accountGroups().get(key);
if (group != null) {
return group;
} else {
return missing(key);
}
return Optional.fromNullable(db.accountGroups().get(key));
} finally {
db.close();
}
}
@Override
public AccountGroup missing(final AccountGroup.Id key) {
final AccountGroup.NameKey name =
new AccountGroup.NameKey("Deleted Group" + key.toString());
final AccountGroup g = new AccountGroup(name, key, null);
g.setType(AccountGroup.Type.SYSTEM);
return g;
}
}
static class ByNameLoader extends
EntryCreator<AccountGroup.NameKey, AccountGroup> {
static class ByNameLoader extends CacheLoader<String, Optional<AccountGroup>> {
private final SchemaFactory<ReviewDb> schema;
@Inject
@@ -206,25 +228,23 @@ public class GroupCacheImpl implements GroupCache {
}
@Override
public AccountGroup createEntry(final AccountGroup.NameKey key)
public Optional<AccountGroup> load(String name)
throws Exception {
final AccountGroupName r;
final ReviewDb db = schema.open();
try {
r = db.accountGroupNames().get(key);
AccountGroup.NameKey key = new AccountGroup.NameKey(name);
AccountGroupName r = db.accountGroupNames().get(key);
if (r != null) {
return db.accountGroups().get(r.getId());
} else {
return null;
return Optional.fromNullable(db.accountGroups().get(r.getId()));
}
return Optional.absent();
} finally {
db.close();
}
}
}
static class ByUUIDLoader extends
EntryCreator<AccountGroup.UUID, AccountGroup> {
static class ByUUIDLoader extends CacheLoader<String, Optional<AccountGroup>> {
private final SchemaFactory<ReviewDb> schema;
@Inject
@@ -233,15 +253,19 @@ public class GroupCacheImpl implements GroupCache {
}
@Override
public AccountGroup createEntry(final AccountGroup.UUID uuid)
public Optional<AccountGroup> load(String uuid)
throws Exception {
final ReviewDb db = schema.open();
try {
List<AccountGroup> r = db.accountGroups().byUUID(uuid).toList();
List<AccountGroup> r;
r = db.accountGroups().byUUID(new AccountGroup.UUID(uuid)).toList();
if (r.size() == 1) {
return r.get(0);
return Optional.of(r.get(0));
} else if (r.size() == 0) {
return Optional.absent();
} else {
return null;
throw new OrmDuplicateKeyException("Duplicate group UUID " + uuid);
}
} finally {
db.close();
@@ -250,7 +274,7 @@ public class GroupCacheImpl implements GroupCache {
}
static class ByExternalNameLoader extends
EntryCreator<AccountGroup.ExternalNameKey, Collection<AccountGroup>> {
CacheLoader<String, Collection<AccountGroup>> {
private final SchemaFactory<ReviewDb> schema;
@Inject
@@ -259,45 +283,13 @@ public class GroupCacheImpl implements GroupCache {
}
@Override
public Collection<AccountGroup> createEntry(
final AccountGroup.ExternalNameKey key) throws Exception {
final ReviewDb db = schema.open();
try {
return db.accountGroups().byExternalName(key).toList();
} finally {
db.close();
}
}
}
static class ListKey {
static final ListKey ALL = new ListKey();
private ListKey() {
}
}
static class Lister extends EntryCreator<ListKey, SortedSet<AccountGroup.NameKey>> {
private final SchemaFactory<ReviewDb> schema;
@Inject
Lister(final SchemaFactory<ReviewDb> sf) {
schema = sf;
}
@Override
public SortedSet<AccountGroup.NameKey> createEntry(ListKey key)
public Collection<AccountGroup> load(String name)
throws Exception {
final ReviewDb db = schema.open();
try {
final List<AccountGroupName> groupNames =
db.accountGroupNames().all().toList();
final SortedSet<AccountGroup.NameKey> groups =
new TreeSet<AccountGroup.NameKey>();
for (final AccountGroupName groupName : groupNames) {
groups.add(groupName.getNameKey());
}
return Collections.unmodifiableSortedSet(groups);
return ImmutableList.copyOf(db.accountGroups()
.byExternalName(new AccountGroup.ExternalNameKey(name))
.toList());
} finally {
db.close();
}

View File

@@ -14,12 +14,14 @@
package com.google.gerrit.server.account;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupInclude;
import com.google.gerrit.reviewdb.server.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.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Module;
@@ -27,24 +29,30 @@ import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
/** Tracks group inclusions in memory for efficient access. */
@Singleton
public class GroupIncludeCacheImpl implements GroupIncludeCache {
private static final Logger log = LoggerFactory
.getLogger(GroupIncludeCacheImpl.class);
private static final String BYINCLUDE_NAME = "groups_byinclude";
public static Module module() {
return new CacheModule() {
@Override
protected void configure() {
final TypeLiteral<Cache<AccountGroup.UUID, Collection<AccountGroup.UUID>>> byInclude =
new TypeLiteral<Cache<AccountGroup.UUID, Collection<AccountGroup.UUID>>>() {};
core(byInclude, BYINCLUDE_NAME).populateWith(ByIncludeLoader.class);
cache(BYINCLUDE_NAME,
AccountGroup.UUID.class,
new TypeLiteral<Set<AccountGroup.UUID>>() {})
.loader(ByIncludeLoader.class);
bind(GroupIncludeCacheImpl.class);
bind(GroupIncludeCache.class).to(GroupIncludeCacheImpl.class);
@@ -52,24 +60,31 @@ public class GroupIncludeCacheImpl implements GroupIncludeCache {
};
}
private final Cache<AccountGroup.UUID, Collection<AccountGroup.UUID>> byInclude;
private final LoadingCache<AccountGroup.UUID, Set<AccountGroup.UUID>> byInclude;
@Inject
GroupIncludeCacheImpl(
@Named(BYINCLUDE_NAME) Cache<AccountGroup.UUID, Collection<AccountGroup.UUID>> byInclude) {
@Named(BYINCLUDE_NAME) LoadingCache<AccountGroup.UUID, Set<AccountGroup.UUID>> byInclude) {
this.byInclude = byInclude;
}
public Collection<AccountGroup.UUID> getByInclude(AccountGroup.UUID groupId) {
return byInclude.get(groupId);
try {
return byInclude.get(groupId);
} catch (ExecutionException e) {
log.warn("Cannot load included groups", e);
return Collections.emptySet();
}
}
public void evictInclude(AccountGroup.UUID groupId) {
byInclude.remove(groupId);
if (groupId != null) {
byInclude.invalidate(groupId);
}
}
static class ByIncludeLoader extends
EntryCreator<AccountGroup.UUID, Collection<AccountGroup.UUID>> {
CacheLoader<AccountGroup.UUID, Set<AccountGroup.UUID>> {
private final SchemaFactory<ReviewDb> schema;
@Inject
@@ -78,32 +93,28 @@ public class GroupIncludeCacheImpl implements GroupIncludeCache {
}
@Override
public Collection<AccountGroup.UUID> createEntry(final AccountGroup.UUID key) throws Exception {
public Set<AccountGroup.UUID> load(AccountGroup.UUID key) throws Exception {
final ReviewDb db = schema.open();
try {
List<AccountGroup> group = db.accountGroups().byUUID(key).toList();
if (group.size() != 1) {
return Collections.emptyList();
return Collections.emptySet();
}
Set<AccountGroup.Id> ids = new HashSet<AccountGroup.Id>();
for (AccountGroupInclude agi : db.accountGroupIncludes().byInclude(group.get(0).getId())) {
Set<AccountGroup.Id> ids = Sets.newHashSet();
for (AccountGroupInclude agi : db.accountGroupIncludes()
.byInclude(group.get(0).getId())) {
ids.add(agi.getGroupId());
}
Set<AccountGroup.UUID> groupArray = new HashSet<AccountGroup.UUID> ();
Set<AccountGroup.UUID> groupArray = Sets.newHashSet();
for (AccountGroup g : db.accountGroups().get(ids)) {
groupArray.add(g.getGroupUUID());
}
return Collections.unmodifiableCollection(groupArray);
return ImmutableSet.copyOf(groupArray);
} finally {
db.close();
}
}
@Override
public Collection<AccountGroup.UUID> missing(final AccountGroup.UUID key) {
return Collections.emptyList();
}
}
}

View File

@@ -16,10 +16,10 @@ package com.google.gerrit.server.auth.ldap;
import static java.util.concurrent.TimeUnit.HOURS;
import com.google.common.base.Optional;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
@@ -32,15 +32,16 @@ public class LdapModule extends CacheModule {
@Override
protected void configure() {
final TypeLiteral<Cache<String, Set<AccountGroup.UUID>>> groups =
new TypeLiteral<Cache<String, Set<AccountGroup.UUID>>>() {};
core(groups, GROUP_CACHE).maxAge(1, HOURS) //
.populateWith(LdapRealm.MemberLoader.class);
cache(GROUP_CACHE,
String.class,
new TypeLiteral<Set<AccountGroup.UUID>>() {})
.expireAfterWrite(1, HOURS)
.loader(LdapRealm.MemberLoader.class);
final TypeLiteral<Cache<String, Account.Id>> usernames =
new TypeLiteral<Cache<String, Account.Id>>() {};
core(usernames, USERNAME_CACHE) //
.populateWith(LdapRealm.UserLoader.class);
cache(USERNAME_CACHE,
String.class,
new TypeLiteral<Optional<Account.Id>>() {})
.loader(LdapRealm.UserLoader.class);
bind(Realm.class).to(LdapRealm.class).in(Scopes.SINGLETON);
bind(Helper.class);

View File

@@ -16,6 +16,9 @@ package com.google.gerrit.server.auth.ldap;
import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_GERRIT;
import com.google.common.base.Optional;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Iterables;
import com.google.gerrit.common.data.ParameterizedString;
import com.google.gerrit.reviewdb.client.Account;
@@ -32,12 +35,9 @@ import com.google.gerrit.server.account.MaterializedGroupMembership;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.auth.AuthenticationUnavailableException;
import com.google.gerrit.server.auth.ldap.Helper.LdapSchema;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.EntryCreator;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -56,6 +56,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
@@ -70,11 +71,11 @@ class LdapRealm implements Realm {
private final Helper helper;
private final AuthConfig authConfig;
private final EmailExpander emailExpander;
private final Cache<String, Account.Id> usernameCache;
private final LoadingCache<String, Optional<Account.Id>> usernameCache;
private final Set<Account.FieldName> readOnlyAccountFields;
private final Config config;
private final Cache<String, Set<AccountGroup.UUID>> membershipCache;
private final LoadingCache<String, Set<AccountGroup.UUID>> membershipCache;
private final MaterializedGroupMembership.Factory groupMembershipFactory;
@Inject
@@ -82,8 +83,8 @@ class LdapRealm implements Realm {
final Helper helper,
final AuthConfig authConfig,
final EmailExpander emailExpander,
@Named(LdapModule.GROUP_CACHE) final Cache<String, Set<AccountGroup.UUID>> membershipCache,
@Named(LdapModule.USERNAME_CACHE) final Cache<String, Account.Id> usernameCache,
@Named(LdapModule.GROUP_CACHE) final LoadingCache<String, Set<AccountGroup.UUID>> membershipCache,
@Named(LdapModule.USERNAME_CACHE) final LoadingCache<String, Optional<Account.Id>> usernameCache,
@GerritServerConfig final Config config,
final MaterializedGroupMembership.Factory groupMembershipFactory) {
this.helper = helper;
@@ -261,13 +262,21 @@ class LdapRealm implements Realm {
@Override
public void onCreateAccount(final AuthRequest who, final Account account) {
usernameCache.put(who.getLocalUser(), account.getId());
usernameCache.put(who.getLocalUser(), Optional.of(account.getId()));
}
@Override
public GroupMembership groups(final AccountState who) {
String id = findId(who.getExternalIds());
Set<AccountGroup.UUID> groups;
try {
groups = membershipCache.get(id);
} catch (ExecutionException e) {
log.warn(String.format("Cannot lookup groups for %s in LDAP", id), e);
groups = Collections.emptySet();
}
return groupMembershipFactory.create(Iterables.concat(
membershipCache.get(findId(who.getExternalIds())),
groups,
who.getInternalGroups()));
}
@@ -281,8 +290,14 @@ class LdapRealm implements Realm {
}
@Override
public Account.Id lookup(final String accountName) {
return usernameCache.get(accountName);
public Account.Id lookup(String accountName) {
try {
Optional<Account.Id> id = usernameCache.get(accountName);
return id != null ? id.orNull() : null;
} catch (ExecutionException e) {
log.warn(String.format("Cannot lookup account %s in LDAP", accountName), e);
return null;
}
}
@Override
@@ -319,7 +334,7 @@ class LdapRealm implements Realm {
return out;
}
static class UserLoader extends EntryCreator<String, Account.Id> {
static class UserLoader extends CacheLoader<String, Optional<Account.Id>> {
private final SchemaFactory<ReviewDb> schema;
@Inject
@@ -328,25 +343,23 @@ class LdapRealm implements Realm {
}
@Override
public Account.Id createEntry(final String username) throws Exception {
public Optional<Account.Id> load(String username) throws Exception {
final ReviewDb db = schema.open();
try {
final ReviewDb db = schema.open();
try {
final AccountExternalId extId =
db.accountExternalIds().get(
new AccountExternalId.Key(SCHEME_GERRIT, username));
return extId != null ? extId.getAccountId() : null;
} finally {
db.close();
final AccountExternalId extId =
db.accountExternalIds().get(
new AccountExternalId.Key(SCHEME_GERRIT, username));
if (extId != null) {
return Optional.of(extId.getAccountId());
}
} catch (OrmException e) {
log.warn("Cannot query for username in database", e);
return null;
return Optional.absent();
} finally {
db.close();
}
}
}
static class MemberLoader extends EntryCreator<String, Set<AccountGroup.UUID>> {
static class MemberLoader extends CacheLoader<String, Set<AccountGroup.UUID>> {
private final Helper helper;
@Inject
@@ -355,8 +368,7 @@ class LdapRealm implements Realm {
}
@Override
public Set<AccountGroup.UUID> createEntry(final String username)
throws Exception {
public Set<AccountGroup.UUID> load(String username) throws Exception {
final DirContext ctx = helper.open();
try {
return helper.queryForGroups(ctx, username, null);
@@ -368,10 +380,5 @@ class LdapRealm implements Realm {
}
}
}
@Override
public Set<AccountGroup.UUID> missing(final String key) {
return Collections.emptySet();
}
}
}

View File

@@ -1,35 +0,0 @@
// Copyright (C) 2009 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.cache;
/**
* A fast in-memory and/or on-disk based cache.
*
* @type <K> type of key used to lookup entries in the cache.
* @type <V> type of value stored within each cache entry.
*/
public interface Cache<K, V> {
/** Get the element from the cache, or null if not stored in the cache. */
public V get(K key);
/** Put one element into the cache, replacing any existing value. */
public void put(K key, V value);
/** Remove any existing value from the cache, no-op if not present. */
public void remove(K key);
/** Remove all cached items. */
public void removeAll();
}

View File

@@ -0,0 +1,46 @@
// Copyright (C) 2009 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.cache;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.Weigher;
import com.google.inject.TypeLiteral;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
/** Configure a cache declared within a {@link CacheModule} instance. */
public interface CacheBinding<K, V> {
/** Set the total size of the cache. */
CacheBinding<K, V> maximumWeight(long weight);
/** Set the time an element lives before being expired. */
CacheBinding<K, V> expireAfterWrite(long duration, TimeUnit durationUnits);
/** Populate the cache with items from the CacheLoader. */
CacheBinding<K, V> loader(Class<? extends CacheLoader<K, V>> clazz);
/** Algorithm to weigh an object with a method other than the unit weight 1. */
CacheBinding<K, V> weigher(Class<? extends Weigher<K, V>> clazz);
String name();
TypeLiteral<K> keyType();
TypeLiteral<V> valueType();
long maximumWeight();
@Nullable Long expireAfterWrite(TimeUnit unit);
@Nullable Weigher<K, V> weigher();
@Nullable CacheLoader<K, V> loader();
}

View File

@@ -14,33 +14,41 @@
package com.google.gerrit.server.cache;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.Weigher;
import com.google.gerrit.extensions.annotations.Exports;
import com.google.inject.AbstractModule;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.UniqueAnnotations;
import com.google.inject.name.Names;
import com.google.inject.util.Types;
import java.io.Serializable;
import java.lang.reflect.Type;
/**
* Miniature DSL to support binding {@link Cache} instances in Guice.
*/
public abstract class CacheModule extends AbstractModule {
private static final TypeLiteral<Cache<?, ?>> ANY_CACHE =
new TypeLiteral<Cache<?, ?>>() {};
/**
* Declare an unnamed in-memory cache.
* Declare a named in-memory cache.
*
* @param <K> type of key used to lookup entries.
* @param <V> type of value stored by the cache.
* @param type type literal for the cache, this literal will be used to match
* injection sites.
* @return binding to describe the cache. Caller must set at least the name on
* the returned binding.
* @return binding to describe the cache.
*/
protected <K, V> UnnamedCacheBinding<K, V> core(
final TypeLiteral<Cache<K, V>> type) {
return core(Key.get(type));
protected <K, V> CacheBinding<K, V> cache(
String name,
Class<K> keyType,
Class<V> valType) {
return cache(name, TypeLiteral.get(keyType), TypeLiteral.get(valType));
}
/**
@@ -48,74 +56,127 @@ public abstract class CacheModule extends AbstractModule {
*
* @param <K> type of key used to lookup entries.
* @param <V> type of value stored by the cache.
* @param type type literal for the cache, this literal will be used to match
* injection sites. Injection sites are matched by this type literal
* and with {@code @Named} annotations.
* @return binding to describe the cache.
*/
protected <K, V> NamedCacheBinding<K, V> core(
final TypeLiteral<Cache<K, V>> type, final String name) {
return core(Key.get(type, Names.named(name))).name(name);
}
private <K, V> UnnamedCacheBinding<K, V> core(final Key<Cache<K, V>> key) {
final boolean disk = false;
final CacheProvider<K, V> b = new CacheProvider<K, V>(disk, this);
bind(key).toProvider(b).in(Scopes.SINGLETON);
return b;
protected <K, V> CacheBinding<K, V> cache(
String name,
Class<K> keyType,
TypeLiteral<V> valType) {
return cache(name, TypeLiteral.get(keyType), valType);
}
/**
* Declare an unnamed in-memory/on-disk cache.
* Declare a named in-memory cache.
*
* @param <K> type of key used to find entries, must be {@link Serializable}.
* @param <V> type of value stored by the cache, must be {@link Serializable}.
* @param type type literal for the cache, this literal will be used to match
* injection sites. Injection sites are matched by this type literal
* and with {@code @Named} annotations.
* @return binding to describe the cache. Caller must set at least the name on
* the returned binding.
* @param <K> type of key used to lookup entries.
* @param <V> type of value stored by the cache.
* @return binding to describe the cache.
*/
protected <K extends Serializable, V extends Serializable> UnnamedCacheBinding<K, V> disk(
final TypeLiteral<Cache<K, V>> type) {
return disk(Key.get(type));
protected <K, V> CacheBinding<K, V> cache(
String name,
TypeLiteral<K> keyType,
TypeLiteral<V> valType) {
Type type = Types.newParameterizedType(
Cache.class,
keyType.getType(), valType.getType());
@SuppressWarnings("unchecked")
Key<Cache<K, V>> key = (Key<Cache<K, V>>) Key.get(type, Names.named(name));
CacheProvider<K, V> m =
new CacheProvider<K, V>(this, name, keyType, valType);
bind(key).toProvider(m).in(Scopes.SINGLETON);
bind(ANY_CACHE).annotatedWith(Exports.named(name)).to(key);
return m.maximumWeight(1024);
}
<K,V> Provider<CacheLoader<K,V>> bindCacheLoader(
CacheProvider<K, V> m,
Class<? extends CacheLoader<K,V>> impl) {
Type type = Types.newParameterizedType(
Cache.class,
m.keyType().getType(), m.valueType().getType());
Type loadingType = Types.newParameterizedType(
LoadingCache.class,
m.keyType().getType(), m.valueType().getType());
Type loaderType = Types.newParameterizedType(
CacheLoader.class,
m.keyType().getType(), m.valueType().getType());
@SuppressWarnings("unchecked")
Key<LoadingCache<K, V>> key =
(Key<LoadingCache<K, V>>) Key.get(type, Names.named(m.name));
@SuppressWarnings("unchecked")
Key<LoadingCache<K, V>> loadingKey =
(Key<LoadingCache<K, V>>) Key.get(loadingType, Names.named(m.name));
@SuppressWarnings("unchecked")
Key<CacheLoader<K, V>> loaderKey =
(Key<CacheLoader<K, V>>) Key.get(loaderType, Names.named(m.name));
bind(loaderKey).to(impl).in(Scopes.SINGLETON);
bind(loadingKey).to(key);
return getProvider(loaderKey);
}
<K,V> Provider<Weigher<K,V>> bindWeigher(
CacheProvider<K, V> m,
Class<? extends Weigher<K,V>> impl) {
Type weigherType = Types.newParameterizedType(
Weigher.class,
m.keyType().getType(), m.valueType().getType());
@SuppressWarnings("unchecked")
Key<Weigher<K, V>> key =
(Key<Weigher<K, V>>) Key.get(weigherType, Names.named(m.name));
bind(key).to(impl).in(Scopes.SINGLETON);
return getProvider(key);
}
/**
* Declare a named in-memory/on-disk cache.
*
* @param <K> type of key used to find entries, must be {@link Serializable}.
* @param <V> type of value stored by the cache, must be {@link Serializable}.
* @param type type literal for the cache, this literal will be used to match
* injection sites. Injection sites are matched by this type literal
* and with {@code @Named} annotations.
* @param <K> type of key used to lookup entries.
* @param <V> type of value stored by the cache.
* @return binding to describe the cache.
*/
protected <K extends Serializable, V extends Serializable> NamedCacheBinding<K, V> disk(
final TypeLiteral<Cache<K, V>> type, final String name) {
return disk(Key.get(type, Names.named(name))).name(name);
protected <K extends Serializable, V extends Serializable> CacheBinding<K, V> persist(
String name,
Class<K> keyType,
Class<V> valType) {
return persist(name, TypeLiteral.get(keyType), TypeLiteral.get(valType));
}
private <K, V> UnnamedCacheBinding<K, V> disk(final Key<Cache<K, V>> key) {
final boolean disk = true;
final CacheProvider<K, V> b = new CacheProvider<K, V>(disk, this);
bind(key).toProvider(b).in(Scopes.SINGLETON);
return b;
/**
* Declare a named in-memory/on-disk cache.
*
* @param <K> type of key used to lookup entries.
* @param <V> type of value stored by the cache.
* @return binding to describe the cache.
*/
protected <K extends Serializable, V extends Serializable> CacheBinding<K, V> persist(
String name,
Class<K> keyType,
TypeLiteral<V> valType) {
return persist(name, TypeLiteral.get(keyType), valType);
}
<K, V> Provider<EntryCreator<K, V>> getEntryCreator(CacheProvider<K, V> cp,
Class<? extends EntryCreator<K, V>> type) {
Key<EntryCreator<K, V>> key = newKey();
bind(key).to(type).in(Scopes.SINGLETON);
return getProvider(key);
}
@SuppressWarnings("unchecked")
private static <K, V> Key<EntryCreator<K, V>> newKey() {
return (Key<EntryCreator<K, V>>) newKeyImpl();
}
private static Key<?> newKeyImpl() {
return Key.get(EntryCreator.class, UniqueAnnotations.create());
/**
* Declare a named in-memory/on-disk cache.
*
* @param <K> type of key used to lookup entries.
* @param <V> type of value stored by the cache.
* @return binding to describe the cache.
*/
protected <K extends Serializable, V extends Serializable> CacheBinding<K, V> persist(
String name,
TypeLiteral<K> keyType,
TypeLiteral<V> valType) {
return ((CacheProvider<K, V>) cache(name, keyType, valType))
.persist(true);
}
}

View File

@@ -1,19 +0,0 @@
// Copyright (C) 2010 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.cache;
public interface CachePool {
public <K, V> ProxyCache<K, V> register(CacheProvider<K, V> provider);
}

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2009 The Android Open Source Project
// 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.
@@ -14,130 +14,156 @@
package com.google.gerrit.server.cache;
import static com.google.gerrit.server.cache.EvictionPolicy.LFU;
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.Weigher;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.TypeLiteral;
import java.util.concurrent.TimeUnit;
public final class CacheProvider<K, V> implements Provider<Cache<K, V>>,
NamedCacheBinding<K, V>, UnnamedCacheBinding<K, V> {
import javax.annotation.Nullable;
class CacheProvider<K, V>
implements Provider<Cache<K, V>>,
CacheBinding<K, V> {
private final CacheModule module;
private final boolean disk;
private int memoryLimit;
private int diskLimit;
private long maxAge;
private EvictionPolicy evictionPolicy;
private String cacheName;
private ProxyCache<K, V> cache;
private Provider<EntryCreator<K, V>> entryCreator;
final String name;
private final TypeLiteral<K> keyType;
private final TypeLiteral<V> valType;
private boolean persist;
private long maximumWeight;
private Long expireAfterWrite;
private Provider<CacheLoader<K, V>> loader;
private Provider<Weigher<K, V>> weigher;
CacheProvider(final boolean disk, CacheModule module) {
this.disk = disk;
private String plugin;
private MemoryCacheFactory memoryCacheFactory;
private PersistentCacheFactory persistentCacheFactory;
private boolean frozen;
CacheProvider(CacheModule module,
String name,
TypeLiteral<K> keyType,
TypeLiteral<V> valType) {
this.module = module;
this.name = name;
this.keyType = keyType;
this.valType = valType;
}
memoryLimit(1024);
maxAge(90, DAYS);
evictionPolicy(LFU);
if (disk) {
diskLimit(16384);
}
@Inject(optional = true)
void setPluginName(@PluginName String pluginName) {
this.plugin = pluginName;
}
@Inject
void setCachePool(final CachePool pool) {
this.cache = pool.register(this);
void setMemoryCacheFactory(MemoryCacheFactory factory) {
this.memoryCacheFactory = factory;
}
public void bind(Cache<K, V> impl) {
if (cache == null) {
throw new ProvisionException("Cache was never registered");
}
cache.bind(impl);
@Inject(optional = true)
void setPersistentCacheFactory(@Nullable PersistentCacheFactory factory) {
this.persistentCacheFactory = factory;
}
public EntryCreator<K, V> getEntryCreator() {
return entryCreator != null ? entryCreator.get() : null;
}
public String getName() {
if (cacheName == null) {
throw new ProvisionException("Cache has no name");
}
return cacheName;
}
public boolean disk() {
return disk;
}
public int memoryLimit() {
return memoryLimit;
}
public int diskLimit() {
return diskLimit;
}
public long maxAge() {
return maxAge;
}
public EvictionPolicy evictionPolicy() {
return evictionPolicy;
}
public NamedCacheBinding<K, V> name(final String name) {
if (cacheName != null) {
throw new IllegalStateException("Cache name already set");
}
cacheName = name;
return this;
}
public NamedCacheBinding<K, V> memoryLimit(final int objects) {
memoryLimit = objects;
return this;
}
public NamedCacheBinding<K, V> diskLimit(final int objects) {
if (!disk) {
// TODO This should really be a compile time type error, but I'm
// too lazy to create the mess of permutations required to setup
// type safe returns for bindings in our little DSL.
//
throw new IllegalStateException("Cache is not disk based");
}
diskLimit = objects;
return this;
}
public NamedCacheBinding<K, V> maxAge(final long duration, final TimeUnit unit) {
maxAge = SECONDS.convert(duration, unit);
CacheBinding<K, V> persist(boolean p) {
Preconditions.checkState(!frozen, "binding frozen, cannot be modified");
persist = p;
return this;
}
@Override
public NamedCacheBinding<K, V> evictionPolicy(final EvictionPolicy policy) {
evictionPolicy = policy;
public CacheBinding<K, V> maximumWeight(long weight) {
Preconditions.checkState(!frozen, "binding frozen, cannot be modified");
maximumWeight = weight;
return this;
}
public NamedCacheBinding<K, V> populateWith(
Class<? extends EntryCreator<K, V>> creator) {
entryCreator = module.getEntryCreator(this, creator);
@Override
public CacheBinding<K, V> expireAfterWrite(long duration, TimeUnit unit) {
Preconditions.checkState(!frozen, "binding frozen, cannot be modified");
expireAfterWrite = SECONDS.convert(duration, unit);
return this;
}
public Cache<K, V> get() {
if (cache == null) {
throw new ProvisionException("Cache \"" + cacheName + "\" not available");
@Override
public CacheBinding<K, V> loader(Class<? extends CacheLoader<K, V>> impl) {
Preconditions.checkState(!frozen, "binding frozen, cannot be modified");
loader = module.bindCacheLoader(this, impl);
return this;
}
@Override
public CacheBinding<K, V> weigher(Class<? extends Weigher<K, V>> impl) {
Preconditions.checkState(!frozen, "binding frozen, cannot be modified");
weigher = module.bindWeigher(this, impl);
return this;
}
@Override
public String name() {
if (!Strings.isNullOrEmpty(plugin)) {
return plugin + "." + name;
}
return name;
}
@Override
public TypeLiteral<K> keyType() {
return keyType;
}
@Override
public TypeLiteral<V> valueType() {
return valType;
}
@Override
public long maximumWeight() {
return maximumWeight;
}
@Override
@Nullable
public Long expireAfterWrite(TimeUnit unit) {
return expireAfterWrite != null
? unit.convert(expireAfterWrite, SECONDS)
: null;
}
@Override
@Nullable
public Weigher<K, V> weigher() {
return weigher != null ? weigher.get() : null;
}
@Override
@Nullable
public CacheLoader<K, V> loader() {
return loader != null ? loader.get() : null;
}
@Override
public Cache<K, V> get() {
frozen = true;
if (loader != null) {
CacheLoader<K, V> ldr = loader.get();
if (persist && persistentCacheFactory != null) {
return persistentCacheFactory.build(this, ldr);
}
return memoryCacheFactory.build(this, ldr);
} else if (persist && persistentCacheFactory != null) {
return persistentCacheFactory.build(this);
} else {
return memoryCacheFactory.build(this);
}
return cache;
}
}

View File

@@ -1,48 +0,0 @@
// Copyright (C) 2011 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.git;
package com.google.gerrit.server.cache;
import java.util.concurrent.ConcurrentHashMap;
/**
* An infinitely sized cache backed by java.util.ConcurrentHashMap.
* <p>
* This cache type is only suitable for unit tests, as it has no upper limit on
* number of items held in the cache. No upper limit can result in memory leaks
* in production servers.
*/
public class ConcurrentHashMapCache<K, V> implements Cache<K, V> {
private final ConcurrentHashMap<K, V> map = new ConcurrentHashMap<K, V>();
@Override
public V get(K key) {
return map.get(key);
}
@Override
public void put(K key, V value) {
map.put(key, value);
}
@Override
public void remove(K key) {
map.remove(key);
}
@Override
public void removeAll() {
map.clear();
}
}

View File

@@ -1,40 +0,0 @@
// Copyright (C) 2009 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.cache;
/**
* Creates a cache entry on demand when its not found.
*
* @param <K> type of the cache's key.
* @param <V> type of the cache's value element.
*/
public abstract class EntryCreator<K, V> {
/**
* Invoked on a cache miss, to compute the cache entry.
*
* @param key entry whose content needs to be obtained.
* @return new cache content. The caller will automatically put this object
* into the cache.
* @throws Exception the cache content cannot be computed. No entry will be
* stored in the cache, and {@link #missing(Object)} will be invoked
* instead. Future requests for the same key will retry this method.
*/
public abstract V createEntry(K key) throws Exception;
/** Invoked when {@link #createEntry(Object)} fails, by default return null. */
public V missing(K key) {
return null;
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2009 The Android Open Source Project
// 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.
@@ -14,9 +14,14 @@
package com.google.gerrit.server.cache;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
/** Configure a cache declared within a {@link CacheModule} instance. */
public interface UnnamedCacheBinding<K, V> {
/** Set the name of the cache. */
public NamedCacheBinding<K, V> name(String cacheName);
public interface MemoryCacheFactory {
<K, V> Cache<K, V> build(CacheBinding<K, V> def);
<K, V> LoadingCache<K, V> build(
CacheBinding<K, V> def,
CacheLoader<K, V> loader);
}

View File

@@ -1,35 +0,0 @@
// Copyright (C) 2009 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.cache;
import java.util.concurrent.TimeUnit;
/** Configure a cache declared within a {@link CacheModule} instance. */
public interface NamedCacheBinding<K, V> {
/** Set the number of objects to cache in memory. */
public NamedCacheBinding<K, V> memoryLimit(int objects);
/** Set the number of objects to cache in memory. */
public NamedCacheBinding<K, V> diskLimit(int objects);
/** Set the time an element lives before being expired. */
public NamedCacheBinding<K, V> maxAge(long duration, TimeUnit durationUnits);
/** Set the eviction policy for elements when the cache is full. */
public NamedCacheBinding<K, V> evictionPolicy(EvictionPolicy policy);
/** Populate the cache with items from the EntryCreator. */
public NamedCacheBinding<K, V> populateWith(Class<? extends EntryCreator<K, V>> creator);
}

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2009 The Android Open Source Project
// 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.
@@ -14,11 +14,14 @@
package com.google.gerrit.server.cache;
/** How entries should be evicted from the cache. */
public enum EvictionPolicy {
/** Least recently used is evicted first. */
LRU,
import com.google.common.cache.Cache;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
/** Least frequently used is evicted first. */
LFU;
public interface PersistentCacheFactory {
<K, V> Cache<K, V> build(CacheBinding<K, V> def);
<K, V> LoadingCache<K, V> build(
CacheBinding<K, V> def,
CacheLoader<K, V> loader);
}

View File

@@ -1,40 +0,0 @@
// Copyright (C) 2010 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.cache;
/** Proxy around a cache which has not yet been created. */
public final class ProxyCache<K, V> implements Cache<K, V> {
private volatile Cache<K, V> self;
public void bind(Cache<K, V> self) {
this.self = self;
}
public V get(K key) {
return self.get(key);
}
public void put(K key, V value) {
self.put(key, value);
}
public void remove(K key) {
self.remove(key);
}
public void removeAll() {
self.removeAll();
}
}

View File

@@ -16,9 +16,11 @@ package com.google.gerrit.server.config;
import static com.google.inject.Scopes.SINGLETON;
import com.google.common.cache.Cache;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
import com.google.gerrit.extensions.events.NewProjectCreatedListener;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.rules.PrologModule;
@@ -68,7 +70,7 @@ import com.google.gerrit.server.util.IdGenerator;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gerrit.server.workflow.FunctionState;
import com.google.inject.Inject;
import com.google.inject.servlet.RequestScoped;
import com.google.inject.TypeLiteral;
import org.apache.velocity.runtime.RuntimeInstance;
import org.eclipse.jgit.lib.Config;
@@ -156,6 +158,7 @@ public class GerritGlobalModule extends FactoryModule {
factory(FunctionState.Factory.class);
bind(GitReferenceUpdated.class);
DynamicMap.mapOf(binder(), new TypeLiteral<Cache<?, ?>>() {});
DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
DynamicSet.setOf(binder(), NewProjectCreatedListener.class);

View File

@@ -32,6 +32,7 @@ import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
@@ -232,16 +233,19 @@ public class EventFactory {
public void addPatchSetFileNames(PatchSetAttribute patchSetAttribute,
Change change, PatchSet patchSet) {
PatchList patchList = patchListCache.get(change, patchSet);
for (PatchListEntry patch : patchList.getPatches()) {
if (patchSetAttribute.files == null) {
patchSetAttribute.files = new ArrayList<PatchAttribute>();
}
try {
PatchList patchList = patchListCache.get(change, patchSet);
for (PatchListEntry patch : patchList.getPatches()) {
if (patchSetAttribute.files == null) {
patchSetAttribute.files = new ArrayList<PatchAttribute>();
}
PatchAttribute p = new PatchAttribute();
p.file = patch.getNewName();
p.type = patch.getChangeType();
patchSetAttribute.files.add(p);
PatchAttribute p = new PatchAttribute();
p.file = patch.getNewName();
p.type = patch.getChangeType();
patchSetAttribute.files.add(p);
}
} catch (PatchListNotAvailableException e) {
}
}

View File

@@ -17,10 +17,8 @@ package com.google.gerrit.server.git;
import static com.google.gerrit.server.git.GitRepositoryManager.REF_REJECT_COMMITS;
import com.google.gerrit.common.errors.PermissionDeniedException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.project.ProjectControl;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -44,7 +42,9 @@ import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
public class BanCommit {
@@ -55,25 +55,23 @@ public class BanCommit {
BanCommit create();
}
private final Provider<CurrentUser> currentUser;
private final Provider<IdentifiedUser> currentUser;
private final GitRepositoryManager repoManager;
private final AccountCache accountCache;
private final PersonIdent gerritIdent;
@Inject
BanCommit(final Provider<CurrentUser> currentUser,
final GitRepositoryManager repoManager, final AccountCache accountCache,
BanCommit(final Provider<IdentifiedUser> currentUser,
final GitRepositoryManager repoManager,
@GerritPersonIdent final PersonIdent gerritIdent) {
this.currentUser = currentUser;
this.repoManager = repoManager;
this.accountCache = accountCache;
this.gerritIdent = gerritIdent;
}
public BanCommitResult ban(final ProjectControl projectControl,
final List<ObjectId> commitsToBan, final String reason)
throws PermissionDeniedException, IOException,
IncompleteUserInfoException, InterruptedException, MergeException {
InterruptedException, MergeException {
if (!projectControl.isOwner()) {
throw new PermissionDeniedException(
"No project owner: not permitted to ban commits");
@@ -148,16 +146,10 @@ public class BanCommit {
return result;
}
private PersonIdent createPersonIdent() throws IncompleteUserInfoException {
final String userName = currentUser.get().getUserName();
final Account account = accountCache.getByUsername(userName).getAccount();
if (account.getFullName() == null) {
throw new IncompleteUserInfoException(userName, "full name");
}
if (account.getPreferredEmail() == null) {
throw new IncompleteUserInfoException(userName, "preferred email");
}
return new PersonIdent(account.getFullName(), account.getPreferredEmail());
private PersonIdent createPersonIdent() {
Date now = new Date();
TimeZone tz = gerritIdent.getTimeZone();
return currentUser.get().newCommitterIdent(now, tz);
}
private static ObjectId commit(final NoteMap noteMap,

View File

@@ -14,13 +14,12 @@
package com.google.gerrit.server.git;
import com.google.common.cache.Cache;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import org.eclipse.jgit.lib.ObjectId;
@@ -38,19 +37,17 @@ public class TagCache {
return new CacheModule() {
@Override
protected void configure() {
final TypeLiteral<Cache<EntryKey, EntryVal>> type =
new TypeLiteral<Cache<EntryKey, EntryVal>>() {};
disk(type, CACHE_NAME);
persist(CACHE_NAME, String.class, EntryVal.class);
bind(TagCache.class);
}
};
}
private final Cache<EntryKey, EntryVal> cache;
private final Cache<String, EntryVal> cache;
private final Object createLock = new Object();
@Inject
TagCache(@Named(CACHE_NAME) Cache<EntryKey, EntryVal> cache) {
TagCache(@Named(CACHE_NAME) Cache<String, EntryVal> cache) {
this.cache = cache;
}
@@ -74,7 +71,7 @@ public class TagCache {
// never fail with an exception. Some of these references can be null
// (e.g. not all projects are cached, or the cache is not current).
//
EntryVal val = cache.get(new EntryKey(name));
EntryVal val = cache.getIfPresent(name.get());
if (val != null) {
TagSetHolder holder = val.holder;
if (holder != null) {
@@ -87,54 +84,22 @@ public class TagCache {
}
TagSetHolder get(Project.NameKey name) {
EntryKey key = new EntryKey(name);
EntryVal val = cache.get(key);
EntryVal val = cache.getIfPresent(name.get());
if (val == null) {
synchronized (createLock) {
val = cache.get(key);
val = cache.getIfPresent(name.get());
if (val == null) {
val = new EntryVal();
val.holder = new TagSetHolder(name);
cache.put(key, val);
cache.put(name.get(), val);
}
}
}
return val.holder;
}
static class EntryKey implements Serializable {
static final long serialVersionUID = 1L;
private transient String name;
EntryKey(Project.NameKey name) {
this.name = name.get();
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(Object o) {
if (o instanceof EntryKey) {
return name.equals(((EntryKey) o).name);
}
return false;
}
private void readObject(ObjectInputStream in) throws IOException {
name = in.readUTF();
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeUTF(name);
}
}
static class EntryVal implements Serializable {
static final long serialVersionUID = EntryKey.serialVersionUID;
static final long serialVersionUID = 1L;
transient TagSetHolder holder;

View File

@@ -58,6 +58,15 @@ class TagSet {
return tags.get(id);
}
int weigh() {
int refCnt = refs.size();
int bits = refCnt / 8;
int size = 16 + 3*8 + 16 + 16;
size += (16 + 16 + 8+ 4 + 36 + 120) * refCnt;
size += (16 + 36 + 16 + bits) * tags.size();
return size;
}
void updateFastForward(String refName, ObjectId oldValue,
ObjectId newValue) {
CachedRef ref = refs.get(refName);

View File

@@ -35,6 +35,7 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.NotifyConfig;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListEntry;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.Predicate;
@@ -270,13 +271,12 @@ public abstract class ChangeEmail extends OutgoingEmail {
}
}
/** Get the patch list corresponding to this patch set. */
protected PatchList getPatchList() {
protected PatchList getPatchList() throws PatchListNotAvailableException {
if (patchSet != null) {
return args.patchListCache.get(change, patchSet);
}
return null;
throw new PatchListNotAvailableException("no patchSet specified");
}
/** Get the project entity the change is in; null if its been deleted. */

View File

@@ -21,6 +21,7 @@ import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.patch.PatchFile;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -81,7 +82,14 @@ public class CommentSender extends ReplyToChangeSender {
final Repository repo = getRepository();
try {
final PatchList patchList = repo != null ? getPatchList() : null;
PatchList patchList = null;
if (repo != null) {
try {
patchList = getPatchList();
} catch (PatchListNotAvailableException e) {
patchList = null;
}
}
Patch.Key currentFileKey = null;
PatchFile currentFileData = null;

View File

@@ -114,6 +114,9 @@ public class IntraLineDiffKey implements Serializable {
public String toString() {
StringBuilder n = new StringBuilder();
n.append("IntraLineDiffKey[");
if (projectKey != null) {
n.append(projectKey.get()).append(" ");
}
n.append(aId.name());
n.append("..");
n.append(bId.name());

View File

@@ -15,7 +15,7 @@
package com.google.gerrit.server.patch;
import com.google.gerrit.server.cache.EntryCreator;
import com.google.common.cache.CacheLoader;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
@@ -35,9 +35,8 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
class IntraLineLoader extends EntryCreator<IntraLineDiffKey, IntraLineDiff> {
private static final Logger log = LoggerFactory
.getLogger(IntraLineLoader.class);
class IntraLineLoader extends CacheLoader<IntraLineDiffKey, IntraLineDiff> {
static final Logger log = LoggerFactory.getLogger(IntraLineLoader.class);
private static final Pattern BLANK_LINE_RE = Pattern
.compile("^[ \\t]*(|[{}]|/\\*\\*?|\\*)[ \\t]*$");
@@ -62,7 +61,7 @@ class IntraLineLoader extends EntryCreator<IntraLineDiffKey, IntraLineDiff> {
}
@Override
public IntraLineDiff createEntry(IntraLineDiffKey key) throws Exception {
public IntraLineDiff load(IntraLineDiffKey key) throws Exception {
Worker w = workerPool.poll();
if (w == null) {
w = new Worker();
@@ -119,7 +118,7 @@ class IntraLineLoader extends EntryCreator<IntraLineDiffKey, IntraLineDiff> {
throws Exception {
if (!input.offer(new Input(key))) {
log.error("Cannot enqueue task to thread " + thread.getName());
return null;
return Result.TIMEOUT;
}
Result r = result.poll(timeoutMillis, TimeUnit.MILLISECONDS);

View File

@@ -0,0 +1,28 @@
// 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.patch;
import com.google.common.cache.Weigher;
/** Approximates memory usage for IntralineDiff in bytes of memory used. */
public class IntraLineWeigher implements
Weigher<IntraLineDiffKey, IntraLineDiff> {
@Override
public int weigh(IntraLineDiffKey key, IntraLineDiff value) {
return 16 + 8*8 + 2*36 // Size of IntraLineDiffKey, 64 bit JVM
+ 16 + 2*8 + 16+8+4+20 // Size of IntraLineDiff, 64 bit JVM
+ (8 + 16 + 4*4) * value.getEdits().size();
}
}

View File

@@ -19,9 +19,10 @@ import com.google.gerrit.reviewdb.client.PatchSet;
/** Provides a cached list of {@link PatchListEntry}. */
public interface PatchListCache {
public PatchList get(PatchListKey key);
public PatchList get(PatchListKey key) throws PatchListNotAvailableException;
public PatchList get(Change change, PatchSet patchSet);
public PatchList get(Change change, PatchSet patchSet)
throws PatchListNotAvailableException;
public IntraLineDiff getIntraLineDiff(IntraLineDiffKey key);
}

View File

@@ -15,24 +15,23 @@
package com.google.gerrit.server.patch;
import com.google.common.cache.LoadingCache;
import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.cache.EvictionPolicy;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import java.util.concurrent.ExecutionException;
/** Provides a cached list of {@link PatchListEntry}. */
@Singleton
public class PatchListCacheImpl implements PatchListCache {
@@ -43,21 +42,15 @@ public class PatchListCacheImpl implements PatchListCache {
return new CacheModule() {
@Override
protected void configure() {
final TypeLiteral<Cache<PatchListKey, PatchList>> fileType =
new TypeLiteral<Cache<PatchListKey, PatchList>>() {};
disk(fileType, FILE_NAME) //
.memoryLimit(128) // very large items, cache only a few
.evictionPolicy(EvictionPolicy.LRU) // prefer most recent
.populateWith(PatchListLoader.class) //
;
persist(FILE_NAME, PatchListKey.class, PatchList.class)
.maximumWeight(10 << 20)
.loader(PatchListLoader.class)
.weigher(PatchListWeigher.class);
final TypeLiteral<Cache<IntraLineDiffKey, IntraLineDiff>> intraType =
new TypeLiteral<Cache<IntraLineDiffKey, IntraLineDiff>>() {};
disk(intraType, INTRA_NAME) //
.memoryLimit(128) // very large items, cache only a few
.evictionPolicy(EvictionPolicy.LRU) // prefer most recent
.populateWith(IntraLineLoader.class) //
;
persist(INTRA_NAME, IntraLineDiffKey.class, IntraLineDiff.class)
.maximumWeight(10 << 20)
.loader(IntraLineLoader.class)
.weigher(IntraLineWeigher.class);
bind(PatchListCacheImpl.class);
bind(PatchListCache.class).to(PatchListCacheImpl.class);
@@ -65,14 +58,14 @@ public class PatchListCacheImpl implements PatchListCache {
};
}
private final Cache<PatchListKey, PatchList> fileCache;
private final Cache<IntraLineDiffKey, IntraLineDiff> intraCache;
private final LoadingCache<PatchListKey, PatchList> fileCache;
private final LoadingCache<IntraLineDiffKey, IntraLineDiff> intraCache;
private final boolean computeIntraline;
@Inject
PatchListCacheImpl(
@Named(FILE_NAME) final Cache<PatchListKey, PatchList> fileCache,
@Named(INTRA_NAME) final Cache<IntraLineDiffKey, IntraLineDiff> intraCache,
@Named(FILE_NAME) LoadingCache<PatchListKey, PatchList> fileCache,
@Named(INTRA_NAME) LoadingCache<IntraLineDiffKey, IntraLineDiff> intraCache,
@GerritServerConfig Config cfg) {
this.fileCache = fileCache;
this.intraCache = intraCache;
@@ -82,11 +75,19 @@ public class PatchListCacheImpl implements PatchListCache {
cfg.getBoolean("cache", "diff", "intraline", true));
}
public PatchList get(final PatchListKey key) {
return fileCache.get(key);
@Override
public PatchList get(PatchListKey key) throws PatchListNotAvailableException {
try {
return fileCache.get(key);
} catch (ExecutionException e) {
PatchListLoader.log.warn("Error computing " + key, e);
throw new PatchListNotAvailableException(e.getCause());
}
}
public PatchList get(final Change change, final PatchSet patchSet) {
@Override
public PatchList get(final Change change, final PatchSet patchSet)
throws PatchListNotAvailableException {
final Project.NameKey projectKey = change.getProject();
final ObjectId a = null;
final ObjectId b = ObjectId.fromString(patchSet.getRevision().get());
@@ -97,11 +98,12 @@ public class PatchListCacheImpl implements PatchListCache {
@Override
public IntraLineDiff getIntraLineDiff(IntraLineDiffKey key) {
if (computeIntraline) {
IntraLineDiff d = intraCache.get(key);
if (d == null) {
d = new IntraLineDiff(IntraLineDiff.Status.ERROR);
try {
return intraCache.get(key);
} catch (ExecutionException e) {
IntraLineLoader.log.warn("Error computing " + key, e);
return new IntraLineDiff(IntraLineDiff.Status.ERROR);
}
return d;
} else {
return new IntraLineDiff(IntraLineDiff.Status.DISABLED);
}

View File

@@ -122,6 +122,22 @@ public class PatchListEntry {
this.deletions = deletions;
}
int weigh() {
int size = 16 + 6*8 + 2*4 + 20 + 16+8+4+20;
size += stringSize(oldName);
size += stringSize(newName);
size += header.length;
size += (8 + 16 + 4*4) * edits.size();
return size;
}
private static int stringSize(String str) {
if (str != null) {
return 16 + 3*4 + 16 + str.length() * 2;
}
return 0;
}
public ChangeType getChangeType() {
return changeType;
}

View File

@@ -15,9 +15,9 @@
package com.google.gerrit.server.patch;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.common.cache.CacheLoader;
import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
import com.google.gerrit.server.cache.EntryCreator;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
@@ -54,6 +54,8 @@ import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.TemporaryBuffer;
import org.eclipse.jgit.util.io.DisabledOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
@@ -62,7 +64,9 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
class PatchListLoader extends EntryCreator<PatchListKey, PatchList> {
class PatchListLoader extends CacheLoader<PatchListKey, PatchList> {
static final Logger log = LoggerFactory.getLogger(PatchListLoader.class);
private final GitRepositoryManager repoManager;
@Inject
@@ -71,7 +75,7 @@ class PatchListLoader extends EntryCreator<PatchListKey, PatchList> {
}
@Override
public PatchList createEntry(final PatchListKey key) throws Exception {
public PatchList load(final PatchListKey key) throws Exception {
final Repository repo = repoManager.openRepository(key.projectKey);
try {
return readPatchList(key, repo);

View File

@@ -12,12 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.git;
package com.google.gerrit.server.patch;
public class IncompleteUserInfoException extends Exception {
public class PatchListNotAvailableException extends Exception {
private static final long serialVersionUID = 1L;
public IncompleteUserInfoException(final String userName, final String missingInfo) {
super("For the user \"" + userName + "\" " + missingInfo + " is not set.");
public PatchListNotAvailableException(String message) {
super(message);
}
public PatchListNotAvailableException(Throwable cause) {
super(cause);
}
}

View File

@@ -0,0 +1,30 @@
// 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.patch;
import com.google.common.cache.Weigher;
/** Approximates memory usage for PatchList in bytes of memory used. */
public class PatchListWeigher implements Weigher<PatchListKey, PatchList> {
@Override
public int weigh(PatchListKey key, PatchList value) {
int size = 16 + 4*8 + 2*36 // Size of PatchListKey, 64 bit JVM
+ 16 + 3*8 + 3*4 + 20; // Size of PatchList, 64 bit JVM
for (PatchListEntry e : value.getPatches()) {
size += e.weigh();
}
return size;
}
}

View File

@@ -14,10 +14,11 @@
package com.google.gerrit.server.project;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Sets;
import com.google.gerrit.reviewdb.client.Project;
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.git.GitRepositoryManager;
import com.google.gerrit.server.git.ProjectConfig;
@@ -27,20 +28,24 @@ import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/** Cache of project information, including access rights. */
@Singleton
public class ProjectCacheImpl implements ProjectCache {
private static final Logger log = LoggerFactory
.getLogger(ProjectCacheImpl.class);
private static final String CACHE_NAME = "projects";
private static final String CACHE_LIST = "project_list";
@@ -48,13 +53,14 @@ public class ProjectCacheImpl implements ProjectCache {
return new CacheModule() {
@Override
protected void configure() {
final TypeLiteral<Cache<Project.NameKey, ProjectState>> nameType =
new TypeLiteral<Cache<Project.NameKey, ProjectState>>() {};
core(nameType, CACHE_NAME).populateWith(Loader.class);
cache(CACHE_NAME, String.class, ProjectState.class)
.loader(Loader.class);
final TypeLiteral<Cache<ListKey, SortedSet<Project.NameKey>>> listType =
new TypeLiteral<Cache<ListKey, SortedSet<Project.NameKey>>>() {};
core(listType, CACHE_LIST).populateWith(Lister.class);
cache(CACHE_LIST,
ListKey.class,
new TypeLiteral<SortedSet<Project.NameKey>>() {})
.maximumWeight(1)
.loader(Lister.class);
bind(ProjectCacheImpl.class);
bind(ProjectCache.class).to(ProjectCacheImpl.class);
@@ -63,16 +69,16 @@ public class ProjectCacheImpl implements ProjectCache {
}
private final AllProjectsName allProjectsName;
private final Cache<Project.NameKey, ProjectState> byName;
private final Cache<ListKey,SortedSet<Project.NameKey>> list;
private final LoadingCache<String, ProjectState> byName;
private final LoadingCache<ListKey, SortedSet<Project.NameKey>> list;
private final Lock listLock;
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,
@Named(CACHE_NAME) LoadingCache<String, ProjectState> byName,
@Named(CACHE_LIST) LoadingCache<ListKey, SortedSet<Project.NameKey>> list,
ProjectCacheClock clock) {
this.allProjectsName = allProjectsName;
this.byName = byName;
@@ -99,18 +105,26 @@ public class ProjectCacheImpl implements ProjectCache {
* @return the cached data; null if no such project exists.
*/
public ProjectState get(final Project.NameKey projectName) {
ProjectState state = byName.get(projectName);
if (state != null && state.needsRefresh(clock.read())) {
byName.remove(projectName);
state = byName.get(projectName);
if (projectName == null) {
return null;
}
try {
ProjectState state = byName.get(projectName.get());
if (state != null && state.needsRefresh(clock.read())) {
byName.invalidate(projectName.get());
state = byName.get(projectName.get());
}
return state;
} catch (ExecutionException e) {
log.warn(String.format("Cannot read project %s", projectName.get()), e);
return null;
}
return state;
}
/** Invalidate the cached information about the given project. */
public void evict(final Project p) {
if (p != null) {
byName.remove(p.getNameKey());
byName.invalidate(p.getNameKey().get());
}
}
@@ -118,10 +132,11 @@ public class ProjectCacheImpl implements ProjectCache {
public void remove(final Project p) {
listLock.lock();
try {
SortedSet<Project.NameKey> n = list.get(ListKey.ALL);
n = new TreeSet<Project.NameKey>(n);
SortedSet<Project.NameKey> n = Sets.newTreeSet(list.get(ListKey.ALL));
n.remove(p.getNameKey());
list.put(ListKey.ALL, Collections.unmodifiableSortedSet(n));
} catch (ExecutionException e) {
log.warn("Cannot list avaliable projects", e);
} finally {
listLock.unlock();
}
@@ -132,10 +147,11 @@ public class ProjectCacheImpl implements ProjectCache {
public void onCreateProject(Project.NameKey newProjectName) {
listLock.lock();
try {
SortedSet<Project.NameKey> n = list.get(ListKey.ALL);
n = new TreeSet<Project.NameKey>(n);
SortedSet<Project.NameKey> n = Sets.newTreeSet(list.get(ListKey.ALL));
n.add(newProjectName);
list.put(ListKey.ALL, Collections.unmodifiableSortedSet(n));
} catch (ExecutionException e) {
log.warn("Cannot list avaliable projects", e);
} finally {
listLock.unlock();
}
@@ -143,18 +159,28 @@ public class ProjectCacheImpl implements ProjectCache {
@Override
public Iterable<Project.NameKey> all() {
return list.get(ListKey.ALL);
try {
return list.get(ListKey.ALL);
} catch (ExecutionException e) {
log.warn("Cannot list available projects", e);
return Collections.emptyList();
}
}
@Override
public Iterable<Project.NameKey> byName(final String pfx) {
final Iterable<Project.NameKey> src;
try {
src = list.get(ListKey.ALL).tailSet(new Project.NameKey(pfx));
} catch (ExecutionException e) {
return Collections.emptyList();
}
return new Iterable<Project.NameKey>() {
@Override
public Iterator<Project.NameKey> iterator() {
return new Iterator<Project.NameKey>() {
private Iterator<Project.NameKey> itr = src.iterator();
private Project.NameKey next;
private Iterator<Project.NameKey> itr =
list.get(ListKey.ALL).tailSet(new Project.NameKey(pfx)).iterator();
@Override
public boolean hasNext() {
@@ -196,7 +222,7 @@ public class ProjectCacheImpl implements ProjectCache {
};
}
static class Loader extends EntryCreator<Project.NameKey, ProjectState> {
static class Loader extends CacheLoader<String, ProjectState> {
private final ProjectState.Factory projectStateFactory;
private final GitRepositoryManager mgr;
@@ -207,19 +233,15 @@ public class ProjectCacheImpl implements ProjectCache {
}
@Override
public ProjectState createEntry(Project.NameKey key) throws Exception {
public ProjectState load(String projectName) throws Exception {
Project.NameKey key = new Project.NameKey(projectName);
Repository git = mgr.openRepository(key);
try {
Repository git = mgr.openRepository(key);
try {
final ProjectConfig cfg = new ProjectConfig(key);
cfg.load(git);
return projectStateFactory.create(cfg);
} finally {
git.close();
}
} catch (RepositoryNotFoundException notFound) {
return null;
ProjectConfig cfg = new ProjectConfig(key);
cfg.load(git);
return projectStateFactory.create(cfg);
} finally {
git.close();
}
}
}
@@ -231,7 +253,7 @@ public class ProjectCacheImpl implements ProjectCache {
}
}
static class Lister extends EntryCreator<ListKey, SortedSet<Project.NameKey>> {
static class Lister extends CacheLoader<ListKey, SortedSet<Project.NameKey>> {
private final GitRepositoryManager mgr;
@Inject
@@ -240,7 +262,7 @@ public class ProjectCacheImpl implements ProjectCache {
}
@Override
public SortedSet<Project.NameKey> createEntry(ListKey key) throws Exception {
public SortedSet<Project.NameKey> load(ListKey key) throws Exception {
return mgr.list();
}
}

View File

@@ -14,14 +14,13 @@
package com.google.gerrit.server.project;
import com.google.common.cache.Cache;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.util.MostSpecificComparator;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import java.util.Arrays;
@@ -38,9 +37,7 @@ public class SectionSortCache {
return new CacheModule() {
@Override
protected void configure() {
final TypeLiteral<Cache<EntryKey, EntryVal>> type =
new TypeLiteral<Cache<EntryKey, EntryVal>>() {};
core(type, CACHE_NAME);
cache(CACHE_NAME, EntryKey.class, EntryVal.class);
bind(SectionSortCache.class);
}
};
@@ -60,7 +57,7 @@ public class SectionSortCache {
}
EntryKey key = new EntryKey(ref, sections);
EntryVal val = cache.get(key);
EntryVal val = cache.getIfPresent(key);
if (val != null) {
int[] srcIdx = val.order;
if (srcIdx != null) {

View File

@@ -31,6 +31,7 @@ import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
@@ -142,7 +143,14 @@ public class ChangeData {
return null;
}
PatchList p = cache.get(c, ps);
PatchList p;
try {
p = cache.get(c, ps);
} catch (PatchListNotAvailableException e) {
currentFiles = new String[0];
return currentFiles;
}
List<String> r = new ArrayList<String>(p.getPatches().size());
for (PatchListEntry e : p.getPatches()) {
if (Patch.COMMIT_MSG.equals(e.getNewName())) {