Forward project-wide change index deletions When a project is deleted, all of its changes are removed from the index. Listen to the core onAllChangesDeletedForProject hook and forward a single project-scoped change-index event so peers can delete all change documents for that project in one operation. Key changes: - Add handling of onAllChangesDeletedForProject in IndexEventHandler and enqueue a task that emits a consolidated change-index event. - Extend ChangeIndexEvent with an "all deleted for project" marker (deleted=true, changeId=0), plus helpers to create/detect it. - Teach IndexEventRouter to route the special event to a new ForwardedIndexChangeHandler.deleteAllForProject path. - Implement deleteAllForProject in the handler, invoking ChangeIndexer.deleteAllForProject(Project.NameKey) under a forwarded context to avoid echo loops. Bug: Issue 440670678 Change-Id: I933a06bf9ce75eb6998f828425234f4677e5bcd7
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandler.java index 15f7c13..8a52eb5 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandler.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandler.java
@@ -16,6 +16,7 @@ import com.google.common.base.Splitter; import com.google.gerrit.entities.Change; +import com.google.gerrit.entities.Project; import com.google.gerrit.server.index.change.ChangeIndexer; import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.util.ManualRequestContext; @@ -103,6 +104,28 @@ } } + /** + * Deletes all change index entries associated with the given project. + * + * <p>This method is invoked when a project-wide deletion event is received from a peer in a + * multi-site setup. It removes all changes for the specified project from the local index. + * + * <p>The method temporarily marks the execution context as a forwarded event to prevent the + * resulting index updates from being re-forwarded to other peers, which would otherwise create + * event loops. + * + * @param projectName the name of the project whose changes should be removed from the index + */ + public void deleteAllForProject(String projectName) { + try { + Context.setForwardedEvent(true); + log.debug("Deleting all change indexes for project {}", projectName); + indexer.deleteAllForProject(Project.nameKey(projectName)); + } finally { + Context.unsetForwardedEvent(); + } + } + @Override protected void reindex(String id) { try (ManualRequestContext ctx = oneOffCtx.open()) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/events/ChangeIndexEvent.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/events/ChangeIndexEvent.java index 64fbdfb..af4c0bc 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/events/ChangeIndexEvent.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/events/ChangeIndexEvent.java
@@ -22,12 +22,37 @@ public class ChangeIndexEvent extends IndexEvent { static final String TYPE = "change-index"; + private static final int ALL_CHANGES_FOR_PROJECT = 0; public String projectName; public int changeId; public String targetSha; public boolean deleted; + /** + * Creates a {@link ChangeIndexEvent} that represents the deletion of all changes belonging to the + * given project. + * + * @param projectName the name of the project + * @param instanceId the originating instance identifier + * @return an event marking all project changes as deleted + */ + public static ChangeIndexEvent allChangesDeletedForProject( + String projectName, String instanceId) { + return new ChangeIndexEvent( + projectName, ALL_CHANGES_FOR_PROJECT, /* deleted */ true, instanceId); + } + + /** + * Checks whether the given event represents the deletion of all changes for a project. + * + * @param event the event to check + * @return {@code true} if the event signals a project-wide deletion, {@code false} otherwise + */ + public static boolean isAllChangesDeletedForProject(ChangeIndexEvent event) { + return event.deleted && event.changeId == ALL_CHANGES_FOR_PROJECT; + } + public ChangeIndexEvent(String projectName, int changeId, boolean deleted, String instanceId) { super(TYPE, instanceId); this.projectName = projectName;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/IndexEventRouter.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/IndexEventRouter.java index a647bce..a23ca33 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/IndexEventRouter.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/IndexEventRouter.java
@@ -71,11 +71,16 @@ public void route(IndexEvent sourceEvent) throws IOException { if (sourceEvent instanceof ChangeIndexEvent) { ChangeIndexEvent changeIndexEvent = (ChangeIndexEvent) sourceEvent; - ForwardedIndexingHandler.Operation operation = changeIndexEvent.deleted ? DELETE : INDEX; - indexChangeHandler.index( - changeIndexEvent.projectName + "~" + changeIndexEvent.changeId, - operation, - Optional.of(changeIndexEvent)); + + if (ChangeIndexEvent.isAllChangesDeletedForProject(changeIndexEvent)) { + indexChangeHandler.deleteAllForProject(changeIndexEvent.projectName); + } else { + ForwardedIndexingHandler.Operation operation = changeIndexEvent.deleted ? DELETE : INDEX; + indexChangeHandler.index( + changeIndexEvent.projectName + "~" + changeIndexEvent.changeId, + operation, + Optional.of(changeIndexEvent)); + } } else if (sourceEvent instanceof AccountIndexEvent) { AccountIndexEvent accountIndexEvent = (AccountIndexEvent) sourceEvent; indexAccountHandler.indexAsync(Account.id(accountIndexEvent.accountId), INDEX);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java index 9aee56e..83f610d 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java +++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/IndexEventHandler.java
@@ -85,6 +85,11 @@ } @Override + public void onAllChangesDeletedForProject(String projectName) { + currCtx.onlyWithContext((ctx) -> executeAllChangesDeletedForProject(projectName)); + } + + @Override public void onChangeDeleted(int id) { executeDeleteChangeTask(id); } @@ -111,6 +116,17 @@ } } + private void executeAllChangesDeletedForProject(String projectName) { + if (!Context.isForwardedEvent()) { + IndexChangeTask task = + new IndexChangeTask( + ChangeIndexEvent.allChangesDeletedForProject(projectName, instanceId)); + if (queuedTasks.add(task)) { + executor.execute(task); + } + } + } + private void executeIndexChangeTask(String projectName, int id) { if (!Context.isForwardedEvent()) { ChangeChecker checker = changeChecker.create(projectName + "~" + id);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/event/IndexEventRouterTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/IndexEventRouterTest.java index 4fa1735..8239cf2 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/multisite/event/IndexEventRouterTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/IndexEventRouterTest.java
@@ -154,4 +154,16 @@ verifyNoInteractions( indexAccountHandler, indexChangeHandler, indexGroupHandler, indexProjectHandler); } + + @Test + public void routerShouldSendEventsToTheAppropriateHandler_allChangesDeletedForProject() + throws Exception { + ChangeIndexEvent event = + ChangeIndexEvent.allChangesDeletedForProject("projectName", INSTANCE_ID); + router.route(event); + + verify(indexChangeHandler).deleteAllForProject(event.projectName); + + verifyNoInteractions(indexAccountHandler, indexGroupHandler, indexProjectHandler); + } }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandlerTest.java index 7133a0d..984e39f 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandlerTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandlerTest.java
@@ -203,6 +203,13 @@ verify(indexerMock, times(1)).index(any(ChangeNotes.class)); } + @Test + public void shouldDeleteAllForProject() { + handler.deleteAllForProject(TEST_PROJECT); + + verify(indexerMock, times(1)).deleteAllForProject(Project.nameKey(TEST_PROJECT)); + } + private void setupChangeAccessRelatedMocks(boolean changeExist, boolean changeUpToDate) throws Exception { setupChangeAccessRelatedMocks(