ChangeIndexer: Add method to reindex a change if it is stale

At this point we have enough wiring to write a larger test in
AbstractQueryChangesTest for StalenessChecker.

Change-Id: Ia17351761fb4ac3ed56e360f28fd2f0a9e3c3ed0
This commit is contained in:
Dave Borowitz
2016-11-22 09:13:34 -05:00
parent 38eba5f350
commit 4eb8d92adb
2 changed files with 108 additions and 12 deletions

View File

@@ -14,6 +14,7 @@
package com.google.gerrit.server.index.change; package com.google.gerrit.server.index.change;
import static com.google.gerrit.server.git.QueueProvider.QueueType.BATCH;
import static com.google.gerrit.server.extensions.events.EventUtil.logEventListenerError; import static com.google.gerrit.server.extensions.events.EventUtil.logEventListenerError;
import com.google.common.base.Function; import com.google.common.base.Function;
@@ -29,6 +30,7 @@ import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.index.Index; import com.google.gerrit.server.index.Index;
import com.google.gerrit.server.index.IndexExecutor;
import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.NotesMigration; import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.query.change.ChangeData;
@@ -102,8 +104,10 @@ public class ChangeIndexer {
private final ChangeNotes.Factory changeNotesFactory; private final ChangeNotes.Factory changeNotesFactory;
private final ChangeData.Factory changeDataFactory; private final ChangeData.Factory changeDataFactory;
private final ThreadLocalRequestContext context; private final ThreadLocalRequestContext context;
private final ListeningExecutorService batchExecutor;
private final ListeningExecutorService executor; private final ListeningExecutorService executor;
private final DynamicSet<ChangeIndexedListener> indexedListeners; private final DynamicSet<ChangeIndexedListener> indexedListeners;
private final StalenessChecker stalenessChecker;
@AssistedInject @AssistedInject
ChangeIndexer(SchemaFactory<ReviewDb> schemaFactory, ChangeIndexer(SchemaFactory<ReviewDb> schemaFactory,
@@ -112,6 +116,8 @@ public class ChangeIndexer {
ChangeData.Factory changeDataFactory, ChangeData.Factory changeDataFactory,
ThreadLocalRequestContext context, ThreadLocalRequestContext context,
DynamicSet<ChangeIndexedListener> indexedListeners, DynamicSet<ChangeIndexedListener> indexedListeners,
StalenessChecker stalenessChecker,
@IndexExecutor(BATCH) ListeningExecutorService batchExecutor,
@Assisted ListeningExecutorService executor, @Assisted ListeningExecutorService executor,
@Assisted ChangeIndex index) { @Assisted ChangeIndex index) {
this.executor = executor; this.executor = executor;
@@ -121,6 +127,8 @@ public class ChangeIndexer {
this.changeDataFactory = changeDataFactory; this.changeDataFactory = changeDataFactory;
this.context = context; this.context = context;
this.indexedListeners = indexedListeners; this.indexedListeners = indexedListeners;
this.stalenessChecker = stalenessChecker;
this.batchExecutor = batchExecutor;
this.index = index; this.index = index;
this.indexes = null; this.indexes = null;
} }
@@ -132,6 +140,8 @@ public class ChangeIndexer {
ChangeData.Factory changeDataFactory, ChangeData.Factory changeDataFactory,
ThreadLocalRequestContext context, ThreadLocalRequestContext context,
DynamicSet<ChangeIndexedListener> indexedListeners, DynamicSet<ChangeIndexedListener> indexedListeners,
StalenessChecker stalenessChecker,
@IndexExecutor(BATCH) ListeningExecutorService batchExecutor,
@Assisted ListeningExecutorService executor, @Assisted ListeningExecutorService executor,
@Assisted ChangeIndexCollection indexes) { @Assisted ChangeIndexCollection indexes) {
this.executor = executor; this.executor = executor;
@@ -141,6 +151,8 @@ public class ChangeIndexer {
this.changeDataFactory = changeDataFactory; this.changeDataFactory = changeDataFactory;
this.context = context; this.context = context;
this.indexedListeners = indexedListeners; this.indexedListeners = indexedListeners;
this.stalenessChecker = stalenessChecker;
this.batchExecutor = batchExecutor;
this.index = null; this.index = null;
this.indexes = indexes; this.indexes = indexes;
} }
@@ -245,28 +257,54 @@ public class ChangeIndexer {
new DeleteTask(id).call(); new DeleteTask(id).call();
} }
/**
* Asynchronously check if a change is stale, and reindex if it is.
* <p>
* Always run on the batch executor, even if this indexer instance is
* configured to use a different executor.
*
* @param project the project to which the change belongs.
* @param id ID of the change to index.
* @return future for reindexing the change; returns true if the change was
* stale.
*/
public CheckedFuture<Boolean, IOException> reindexIfStale(
Project.NameKey project, Change.Id id) {
return submit(new ReindexIfStaleTask(project, id), batchExecutor);
}
private Collection<ChangeIndex> getWriteIndexes() { private Collection<ChangeIndex> getWriteIndexes() {
return indexes != null return indexes != null
? indexes.getWriteIndexes() ? indexes.getWriteIndexes()
: Collections.singleton(index); : Collections.singleton(index);
} }
private CheckedFuture<?, IOException> submit(Callable<?> task) { private <T> CheckedFuture<T, IOException> submit(Callable<T> task) {
return submit(task, executor);
}
private static <T> CheckedFuture<T, IOException> submit(Callable<T> task,
ListeningExecutorService executor) {
return Futures.makeChecked( return Futures.makeChecked(
Futures.nonCancellationPropagating(executor.submit(task)), MAPPER); Futures.nonCancellationPropagating(executor.submit(task)), MAPPER);
} }
private class IndexTask implements Callable<Void> { private abstract class AbstractIndexTask<T> implements Callable<T> {
private final Project.NameKey project; protected final Project.NameKey project;
private final Change.Id id; protected final Change.Id id;
private IndexTask(Project.NameKey project, Change.Id id) { protected AbstractIndexTask(Project.NameKey project, Change.Id id) {
this.project = project; this.project = project;
this.id = id; this.id = id;
} }
protected abstract T callImpl(Provider<ReviewDb> db) throws Exception;
@Override @Override
public Void call() throws Exception { public abstract String toString();
@Override
public final T call() throws Exception {
try { try {
final AtomicReference<Provider<ReviewDb>> dbRef = final AtomicReference<Provider<ReviewDb>> dbRef =
Atomics.newReference(); Atomics.newReference();
@@ -295,10 +333,7 @@ public class ChangeIndexer {
}; };
RequestContext oldCtx = context.setContext(newCtx); RequestContext oldCtx = context.setContext(newCtx);
try { try {
ChangeData cd = newChangeData( return callImpl(newCtx.getReviewDbProvider());
newCtx.getReviewDbProvider().get(), project, id);
index(cd);
return null;
} finally { } finally {
context.setContext(oldCtx); context.setContext(oldCtx);
Provider<ReviewDb> db = dbRef.get(); Provider<ReviewDb> db = dbRef.get();
@@ -307,17 +342,31 @@ public class ChangeIndexer {
} }
} }
} catch (Exception e) { } catch (Exception e) {
log.error(String.format("Failed to index change %d", id.get()), e); log.error("Failed to execute " + this, e);
throw e; throw e;
} }
} }
}
private class IndexTask extends AbstractIndexTask<Void> {
private IndexTask(Project.NameKey project, Change.Id id) {
super(project, id);
}
@Override
public Void callImpl(Provider<ReviewDb> db) throws Exception {
ChangeData cd = newChangeData(db.get(), project, id);
index(cd);
return null;
}
@Override @Override
public String toString() { public String toString() {
return "index-change-" + id.get(); return "index-change-" + id;
} }
} }
// Not AbstractIndexTask as it doesn't need ReviewDb.
private class DeleteTask implements Callable<Void> { private class DeleteTask implements Callable<Void> {
private final Change.Id id; private final Change.Id id;
@@ -339,6 +388,26 @@ public class ChangeIndexer {
} }
} }
private class ReindexIfStaleTask extends AbstractIndexTask<Boolean> {
private ReindexIfStaleTask(Project.NameKey project, Change.Id id) {
super(project, id);
}
@Override
public Boolean callImpl(Provider<ReviewDb> db) throws Exception {
if (!stalenessChecker.isStale(id)) {
return false;
}
index(newChangeData(db.get(), project, id));
return true;
}
@Override
public String toString() {
return "reindex-if-stale-change-" + id;
}
}
// Avoid auto-rebuilding when reindexing if reading is disabled. This just // Avoid auto-rebuilding when reindexing if reading is disabled. This just
// increases contention on the meta ref from a background indexing thread // increases contention on the meta ref from a background indexing thread
// with little benefit. The next actual write to the entity may still incur a // with little benefit. The next actual write to the entity may still incur a

View File

@@ -52,6 +52,7 @@ import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Patch; import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
@@ -1611,6 +1612,32 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
cd.currentApprovals(); cd.currentApprovals();
} }
@Test
public void reindexIfStale() throws Exception {
Account.Id user = createAccount("user");
Project.NameKey project = new Project.NameKey("repo");
TestRepository<Repo> repo = createProject(project.get());
Change change = insert(repo, newChange(repo));
PatchSet ps = db.patchSets().get(change.currentPatchSetId());
requestContext.setContext(newRequestContext(user));
assertThat(changeEditModifier.createEdit(change, ps))
.isEqualTo(RefUpdate.Result.NEW);
assertQuery("has:edit", change);
assertThat(indexer.reindexIfStale(project, change.getId()).get()).isFalse();
// Delete edit ref behind index's back.
RefUpdate ru = repo.getRepository().updateRef(
RefNames.refsEdit(user, change.getId(), ps.getId()));
ru.setForceUpdate(true);
assertThat(ru.delete()).isEqualTo(RefUpdate.Result.FORCED);
// Index is stale.
assertQuery("has:edit", change);
assertThat(indexer.reindexIfStale(project, change.getId()).get()).isTrue();
assertQuery("has:edit");
}
protected ChangeInserter newChange(TestRepository<Repo> repo) protected ChangeInserter newChange(TestRepository<Repo> repo)
throws Exception { throws Exception {
return newChange(repo, null, null, null, null); return newChange(repo, null, null, null, null);