This is an automated email from the ASF dual-hosted git repository.

quantranhong1999 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git


The following commit(s) were added to refs/heads/master by this push:
     new 9118d30a28 Leverage strong consistency within 
SolveMailboxInconsistenciesService (#3101)
9118d30a28 is described below

commit 9118d30a2834b6efcffc9dfffa82ee54ab083eb5
Author: Benoit TELLIER <[email protected]>
AuthorDate: Wed Jul 29 09:54:09 2026 +0200

    Leverage strong consistency within SolveMailboxInconsistenciesService 
(#3101)
---
 .../cassandra/mail/CassandraMailboxPathV3DAO.java   | 12 +++++++++++-
 .../task/SolveMailboxInconsistenciesService.java    | 13 ++++++++-----
 .../cassandra/mail/CassandraMailboxMapperTest.java  | 13 +++++++++----
 .../SolveMailboxInconsistenciesServiceTest.java     | 21 +++++++++++++++------
 .../rabbitmq/ConsistencyTasksIntegrationTest.java   | 12 ++++++++++--
 5 files changed, 53 insertions(+), 18 deletions(-)

diff --git 
a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathV3DAO.java
 
b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathV3DAO.java
index 266e9193d4..43ea89ef8b 100644
--- 
a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathV3DAO.java
+++ 
b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathV3DAO.java
@@ -180,7 +180,17 @@ public class CassandraMailboxPathV3DAO {
     }
 
     public Flux<Mailbox> listAll() {
-        return cassandraAsyncExecutor.executeRows(selectAll.bind())
+        return listAll(JamesExecutionProfiles.ConsistencyChoice.WEAK);
+    }
+
+    public Flux<Mailbox> listAll(JamesExecutionProfiles.ConsistencyChoice 
consistencyChoice) {
+        BoundStatementBuilder statementBuilder = 
selectAll.boundStatementBuilder();
+
+        if (consistencyChoice.equals(STRONG)) {
+            statementBuilder.setExecutionProfile(lwtProfile);
+        }
+
+        return cassandraAsyncExecutor.executeRows(statementBuilder.build())
             .map(this::fromRowToCassandraIdAndPath)
             .map(FunctionalUtils.toFunction(this::logReadSuccess));
     }
diff --git 
a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesService.java
 
b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesService.java
index 9061e2d818..654f631469 100644
--- 
a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesService.java
+++ 
b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesService.java
@@ -148,9 +148,11 @@ public class SolveMailboxInconsistenciesService {
      *
      * This inconsistency arise if mailbox creation fails or upon partial 
deletes.
      *
-     * In both case removing the dandling path registration solves the 
inconsistency
-     *
-     * In order to solve this inconsistency, we can simply re-reference the 
mailboxPath.
+     * The path table being the source of truth, we re-create the missing 
MailboxDao projection
+     * from the registered path entry rather than dropping the path 
registration: the mailbox may
+     * still hold messages (keyed by id) that would otherwise become 
unreachable. We re-read the
+     * path entry with STRONG consistency first, so a registration that has 
meanwhile been removed
+     * is left untouched.
      */
     private static class OrphanMailboxPathDAOEntry implements Inconsistency {
         private final Mailbox mailbox;
@@ -161,7 +163,8 @@ public class SolveMailboxInconsistenciesService {
 
         @Override
         public Mono<Result> fix(Context context, CassandraMailboxDAO 
mailboxDAO, CassandraMailboxPathV3DAO pathV3DAO) {
-            return pathV3DAO.delete(mailbox.generateAssociatedPath())
+            return pathV3DAO.retrieve(mailbox.generateAssociatedPath(), STRONG)
+                .flatMap(mailboxDAO::save)
                 .doOnSuccess(any -> {
                     LOGGER.info("Inconsistency fixed for orphan mailboxPath {} 
- {}",
                         mailbox.getMailboxId().serialize(),
@@ -628,7 +631,7 @@ public class SolveMailboxInconsistenciesService {
     }
 
     private Flux<Result> processMailboxPathDaoInconsistencies(Context context, 
boolean autoMerge) {
-        return mailboxPathV3DAO.listAll()
+        return mailboxPathV3DAO.listAll(STRONG)
             .flatMap(entry -> detectMailboxPathDaoInconsistency(entry, 
autoMerge), DEFAULT_CONCURRENCY)
             .doOnNext(any -> context.incrementProcessedMailboxPathEntries())
             // Detect every inconsistency first, then fix them one at a time. 
Resolving a same-mailbox
diff --git 
a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperTest.java
 
b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperTest.java
index c27ffd68b0..c8ce2bf8a0 100644
--- 
a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperTest.java
+++ 
b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperTest.java
@@ -342,11 +342,16 @@ class CassandraMailboxMapperTest {
                         .onErrorResume(e -> Mono.empty())
                         .block());
 
+                // The path table is the source of truth: the missing mailbox 
projection is re-created
+                // from the registered path entry, rather than dropping that 
registration, so that
+                // messages held by this mailbox (keyed by id) do not become 
unreachable.
                 SoftAssertions.assertSoftly(Throwing.consumer(softly -> {
-                    softly.assertThatThrownBy(() -> 
MailboxReactorUtils.blockOptional(testee.findMailboxById(MAILBOX_ID)))
-                        .isInstanceOf(MailboxNotFoundException.class);
-                    
softly.assertThat(MailboxReactorUtils.blockOptional(testee.findMailboxByPath(MAILBOX_PATH)))
-                        .isEmpty();
+                    softly(softly)
+                        .assertThat(testee.findMailboxById(MAILBOX_ID).block())
+                        .isEqualTo(MAILBOX);
+                    softly(softly)
+                        
.assertThat(testee.findMailboxByPath(MAILBOX_PATH).block())
+                        .isEqualTo(MAILBOX);
                 }));
             }
         }
diff --git 
a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesServiceTest.java
 
b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesServiceTest.java
index 1512595411..a2cd31802e 100644
--- 
a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesServiceTest.java
+++ 
b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesServiceTest.java
@@ -314,11 +314,14 @@ class SolveMailboxInconsistenciesServiceTest {
 
         testee.fixMailboxInconsistencies(new Context()).block();
 
+        // The path table is the source of truth: rather than dropping the 
dangling path registration
+        // (and losing the reference to a mailbox that may still hold 
messages), the missing projection
+        // is re-created from it, yielding a consistent registration on both 
sides.
         SoftAssertions.assertSoftly(softly -> {
             
softly.assertThat(mailboxDAO.retrieveAllMailboxes().collectList().block())
-                .isEmpty();
+                .containsExactlyInAnyOrder(MAILBOX);
             softly.assertThat(mailboxPathV3DAO.listAll().collectList().block())
-                .isEmpty();
+                .containsExactlyInAnyOrder(MAILBOX);
         });
     }
 
@@ -386,11 +389,15 @@ class SolveMailboxInconsistenciesServiceTest {
 
         testee.fixMailboxInconsistencies(new Context()).block();
 
+        // The path registration is the source of truth and is thus preserved: 
the missing projection
+        // is re-created for the registered mailbox instead of dropping the 
registration. The stale
+        // projection squatting that path is left untouched and reported as a 
conflicting entry, as
+        // merging the two mailboxes requires the admin (or auto-merge) to 
arbitrate.
         SoftAssertions.assertSoftly(softly -> {
             
softly.assertThat(mailboxDAO.retrieveAllMailboxes().collectList().block())
-                .containsExactlyInAnyOrder(MAILBOX);
+                .containsExactlyInAnyOrder(MAILBOX, MAILBOX_2);
             softly.assertThat(mailboxPathV3DAO.listAll().collectList().block())
-                .isEmpty();
+                .containsExactlyInAnyOrder(MAILBOX_2);
         });
     }
 
@@ -402,11 +409,13 @@ class SolveMailboxInconsistenciesServiceTest {
         testee.fixMailboxInconsistencies(new Context()).block();
         testee.fixMailboxInconsistencies(new Context()).block();
 
+        // Re-creating the missing projection reaches a stable state: further 
runs only keep reporting
+        // the unresolved conflict, they do not alter the data any more.
         SoftAssertions.assertSoftly(softly -> {
             
softly.assertThat(mailboxDAO.retrieveAllMailboxes().collectList().block())
-                .containsExactlyInAnyOrder(MAILBOX);
+                .containsExactlyInAnyOrder(MAILBOX, MAILBOX_2);
             softly.assertThat(mailboxPathV3DAO.listAll().collectList().block())
-                .containsExactlyInAnyOrder(MAILBOX);
+                .containsExactlyInAnyOrder(MAILBOX_2);
         });
     }
 
diff --git 
a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/ConsistencyTasksIntegrationTest.java
 
b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/ConsistencyTasksIntegrationTest.java
index 89345760e2..ef06e0fc00 100644
--- 
a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/ConsistencyTasksIntegrationTest.java
+++ 
b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/ConsistencyTasksIntegrationTest.java
@@ -33,6 +33,7 @@ import static 
org.apache.james.jmap.JMAPTestingConstants.LOCALHOST_IP;
 import static org.apache.james.webadmin.Constants.SEPARATOR;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.hamcrest.CoreMatchers.hasItems;
 import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.is;
@@ -212,7 +213,7 @@ class ConsistencyTasksIntegrationTest {
     }
 
     @Test
-    void shouldSolveMailboxesInconsistency(GuiceJamesServer server) {
+    void shouldSolveMailboxesInconsistency(GuiceJamesServer server) throws 
Exception {
         // schema version 6 or higher required to run solve mailbox 
inconsistencies task
         String upgradeTaskId = with().post(UPGRADE_TO_LATEST_VERSION)
             .jsonPath()
@@ -252,7 +253,14 @@ class ConsistencyTasksIntegrationTest {
             .basePath(TasksRoutes.BASE)
             .get(solveConsistenciesTaskId + "/await");
 
-        assertThatCode(() -> 
testIMAPClient.create(TEST_MAILBOX)).doesNotThrowAnyException();
+        // The path registration is the source of truth: the task re-creates 
the missing mailbox
+        // projection from it rather than dropping the registration, as the 
mailbox may already hold
+        // messages (keyed by id) that would otherwise become unreachable. The 
mailbox thus ends up
+        // fully usable - hence selectable, and no longer creatable.
+        assertThat(testIMAPClient.sendCommand("SELECT " + TEST_MAILBOX))
+            .contains("SELECT completed.");
+        assertThatThrownBy(() -> testIMAPClient.create(TEST_MAILBOX))
+            .hasMessageContaining("Mailbox already exists");
     }
 
     @Tag(Unstable.TAG)


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to