This is an automated email from the ASF dual-hosted git repository. rcordier 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 37c88ebdbd JAMES-2586 Fix concurrency issue of updating ACL in RLSSupportPostgresMailboxMapper 37c88ebdbd is described below commit 37c88ebdbdfa5aa6bb426ce9fbd3c285fa5e985f Author: hung phan <hp...@linagora.com> AuthorDate: Mon Feb 24 14:09:44 2025 +0700 JAMES-2586 Fix concurrency issue of updating ACL in RLSSupportPostgresMailboxMapper --- .../postgres/mail/PostgresMailboxMemberDAO.java | 3 +- .../mail/RLSSupportPostgresMailboxMapper.java | 47 +++++++++++++--------- .../RLSSupportPostgresMailboxMapperACLTest.java | 32 ++++++++++----- .../store/mail/model/MailboxMapperACLTest.java | 3 +- 4 files changed, 52 insertions(+), 33 deletions(-) diff --git a/mailbox/postgres/src/main/java/org/apache/james/mailbox/postgres/mail/PostgresMailboxMemberDAO.java b/mailbox/postgres/src/main/java/org/apache/james/mailbox/postgres/mail/PostgresMailboxMemberDAO.java index 5cf73eb29f..b5557ab11f 100644 --- a/mailbox/postgres/src/main/java/org/apache/james/mailbox/postgres/mail/PostgresMailboxMemberDAO.java +++ b/mailbox/postgres/src/main/java/org/apache/james/mailbox/postgres/mail/PostgresMailboxMemberDAO.java @@ -52,7 +52,8 @@ public class PostgresMailboxMemberDAO { .map(username -> dslContext.newRecord(USER_NAME, MAILBOX_ID) .value1(username.asString()) .value2(mailboxId.asUuid())) - .toList()))); + .toList()) + .onConflictDoNothing())); } public Mono<Void> delete(PostgresMailboxId mailboxId, List<Username> usernames) { diff --git a/mailbox/postgres/src/main/java/org/apache/james/mailbox/postgres/mail/RLSSupportPostgresMailboxMapper.java b/mailbox/postgres/src/main/java/org/apache/james/mailbox/postgres/mail/RLSSupportPostgresMailboxMapper.java index aa3db2b311..efa77a24eb 100644 --- a/mailbox/postgres/src/main/java/org/apache/james/mailbox/postgres/mail/RLSSupportPostgresMailboxMapper.java +++ b/mailbox/postgres/src/main/java/org/apache/james/mailbox/postgres/mail/RLSSupportPostgresMailboxMapper.java @@ -19,20 +19,21 @@ package org.apache.james.mailbox.postgres.mail; +import java.time.Duration; import java.util.function.Function; import org.apache.james.core.Username; import org.apache.james.mailbox.acl.ACLDiff; import org.apache.james.mailbox.acl.PositiveUserACLDiff; +import org.apache.james.mailbox.exception.UnsupportedRightException; import org.apache.james.mailbox.model.Mailbox; import org.apache.james.mailbox.model.MailboxACL; import org.apache.james.mailbox.postgres.PostgresMailboxId; import org.apache.james.mailbox.postgres.mail.dao.PostgresMailboxDAO; -import com.github.fge.lambdas.Throwing; - import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.util.retry.Retry; public class RLSSupportPostgresMailboxMapper extends PostgresMailboxMapper { private final PostgresMailboxDAO postgresMailboxDAO; @@ -56,30 +57,36 @@ public class RLSSupportPostgresMailboxMapper extends PostgresMailboxMapper { @Override public Mono<ACLDiff> updateACL(Mailbox mailbox, MailboxACL.ACLCommand mailboxACLCommand) { - MailboxACL oldACL = mailbox.getACL(); - MailboxACL newACL = Throwing.supplier(() -> oldACL.apply(mailboxACLCommand)).get(); - ACLDiff aclDiff = ACLDiff.computeDiff(oldACL, newACL); - PositiveUserACLDiff userACLDiff = new PositiveUserACLDiff(aclDiff); - return upsertACL(mailbox, newACL, aclDiff, userACLDiff); + return postgresMailboxDAO.getACL(mailbox.getMailboxId()) + .flatMap(pairMailboxACLAndVersion -> { + try { + MailboxACL newACL = pairMailboxACLAndVersion.getLeft().apply(mailboxACLCommand); + return postgresMailboxDAO.upsertACL(mailbox.getMailboxId(), newACL, pairMailboxACLAndVersion.getRight()) + .thenReturn(ACLDiff.computeDiff(pairMailboxACLAndVersion.getLeft(), newACL)); + } catch (UnsupportedRightException e) { + throw new RuntimeException(e); + } + }).retryWhen(Retry.backoff(3, Duration.ofMillis(100)) + .filter(throwable -> throwable instanceof PostgresACLUpsertException)) + .flatMap(aclDiff -> updateMembersOfMailbox(mailbox, new PositiveUserACLDiff(aclDiff)) + .thenReturn(aclDiff)); } @Override public Mono<ACLDiff> setACL(Mailbox mailbox, MailboxACL mailboxACL) { - MailboxACL oldACL = mailbox.getACL(); - ACLDiff aclDiff = ACLDiff.computeDiff(oldACL, mailboxACL); - PositiveUserACLDiff userACLDiff = new PositiveUserACLDiff(aclDiff); - return upsertACL(mailbox, mailboxACL, aclDiff, userACLDiff); + return postgresMailboxDAO.getACL(mailbox.getMailboxId()) + .flatMap(pairMailboxACLAndVersion -> + postgresMailboxDAO.upsertACL(mailbox.getMailboxId(), mailboxACL, pairMailboxACLAndVersion.getRight()) + .thenReturn(ACLDiff.computeDiff(pairMailboxACLAndVersion.getLeft(), mailboxACL))).retryWhen(Retry.backoff(3, Duration.ofMillis(100)) + .filter(throwable -> throwable instanceof PostgresACLUpsertException)) + .flatMap(aclDiff -> updateMembersOfMailbox(mailbox, new PositiveUserACLDiff(aclDiff)) + .thenReturn(aclDiff)); } - private Mono<ACLDiff> upsertACL(Mailbox mailbox, MailboxACL newACL, ACLDiff aclDiff, PositiveUserACLDiff userACLDiff) { - return postgresMailboxDAO.upsertACL(mailbox.getMailboxId(), newACL) - .then(postgresMailboxMemberDAO.delete(PostgresMailboxId.class.cast(mailbox.getMailboxId()), - userACLDiff.removedEntries().map(entry -> Username.of(entry.getKey().getName())).toList())) + private Mono<Void> updateMembersOfMailbox(Mailbox mailbox, PositiveUserACLDiff userACLDiff) { + return postgresMailboxMemberDAO.delete(PostgresMailboxId.class.cast(mailbox.getMailboxId()), + userACLDiff.removedEntries().map(entry -> Username.of(entry.getKey().getName())).toList()) .then(postgresMailboxMemberDAO.insert(PostgresMailboxId.class.cast(mailbox.getMailboxId()), - userACLDiff.addedEntries().map(entry -> Username.of(entry.getKey().getName())).toList())) - .then(Mono.fromCallable(() -> { - mailbox.setACL(newACL); - return aclDiff; - })); + userACLDiff.addedEntries().map(entry -> Username.of(entry.getKey().getName())).toList())); } } diff --git a/mailbox/postgres/src/test/java/org/apache/james/mailbox/postgres/mail/RLSSupportPostgresMailboxMapperACLTest.java b/mailbox/postgres/src/test/java/org/apache/james/mailbox/postgres/mail/RLSSupportPostgresMailboxMapperACLTest.java index 9811865f8d..3e8edd578e 100644 --- a/mailbox/postgres/src/test/java/org/apache/james/mailbox/postgres/mail/RLSSupportPostgresMailboxMapperACLTest.java +++ b/mailbox/postgres/src/test/java/org/apache/james/mailbox/postgres/mail/RLSSupportPostgresMailboxMapperACLTest.java @@ -19,29 +19,39 @@ package org.apache.james.mailbox.postgres.mail; -import java.util.concurrent.ExecutionException; +import java.time.Instant; +import org.apache.james.backends.postgres.PostgresConfiguration; import org.apache.james.backends.postgres.PostgresExtension; import org.apache.james.backends.postgres.PostgresModule; -import org.apache.james.mailbox.postgres.mail.dao.PostgresMailboxDAO; +import org.apache.james.blob.api.BlobId; +import org.apache.james.blob.api.BucketName; +import org.apache.james.blob.api.PlainBlobId; +import org.apache.james.blob.memory.MemoryBlobStoreDAO; +import org.apache.james.mailbox.MailboxSessionUtil; +import org.apache.james.mailbox.StringBackedAttachmentIdFactory; +import org.apache.james.mailbox.postgres.PostgresMailboxSessionMapperFactory; +import org.apache.james.mailbox.store.mail.AttachmentIdAssignationStrategy; import org.apache.james.mailbox.store.mail.MailboxMapper; import org.apache.james.mailbox.store.mail.model.MailboxMapperACLTest; -import org.junit.jupiter.api.Disabled; +import org.apache.james.server.blob.deduplication.DeDuplicationBlobStore; +import org.apache.james.utils.UpdatableTickingClock; import org.junit.jupiter.api.extension.RegisterExtension; class RLSSupportPostgresMailboxMapperACLTest extends MailboxMapperACLTest { @RegisterExtension - static PostgresExtension postgresExtension = PostgresExtension.withoutRowLevelSecurity(PostgresModule.aggregateModules(PostgresMailboxModule.MODULE, + static PostgresExtension postgresExtension = PostgresExtension.withRowLevelSecurity(PostgresModule.aggregateModules(PostgresMailboxModule.MODULE, PostgresMailboxMemberModule.MODULE)); @Override protected MailboxMapper createMailboxMapper() { - return new RLSSupportPostgresMailboxMapper(new PostgresMailboxDAO(postgresExtension.getDefaultPostgresExecutor()), - new PostgresMailboxMemberDAO(postgresExtension.getDefaultPostgresExecutor())); - } - - @Override - @Disabled("not yet implemented") - protected void updateAclShouldWorkWellInMultiThreadEnv() throws ExecutionException, InterruptedException { + BlobId.Factory blobIdFactory = new PlainBlobId.Factory(); + PostgresMailboxSessionMapperFactory postgresMailboxSessionMapperFactory = new PostgresMailboxSessionMapperFactory(postgresExtension.getExecutorFactory(), + new UpdatableTickingClock(Instant.now()), + new DeDuplicationBlobStore(new MemoryBlobStoreDAO(), BucketName.DEFAULT, blobIdFactory), + blobIdFactory, + PostgresConfiguration.builder().username("a").password("a").rowLevelSecurityEnabled().byPassRLSUser("b").byPassRLSPassword("b").build(), + new AttachmentIdAssignationStrategy.Default(new StringBackedAttachmentIdFactory())); + return postgresMailboxSessionMapperFactory.getMailboxMapper(MailboxSessionUtil.create(BENWA)); } } diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MailboxMapperACLTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MailboxMapperACLTest.java index d0e9ebccaf..f2411d8318 100644 --- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MailboxMapperACLTest.java +++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MailboxMapperACLTest.java @@ -43,6 +43,7 @@ import org.junit.jupiter.api.Test; import com.google.common.collect.ImmutableMap; public abstract class MailboxMapperACLTest { + public static Username BENWA = Username.of("be...@domain.org"); private static final UidValidity UID_VALIDITY = UidValidity.of(42); private static final Username USER = Username.of("user"); private static final Username USER_1 = Username.of("user1"); @@ -57,7 +58,7 @@ public abstract class MailboxMapperACLTest { @BeforeEach void setUp() { mailboxMapper = createMailboxMapper(); - MailboxPath benwaInboxPath = MailboxPath.forUser(Username.of("benwa"), "INBOX"); + MailboxPath benwaInboxPath = MailboxPath.forUser(BENWA, "INBOX"); benwaInboxMailbox = mailboxMapper.create(benwaInboxPath, UID_VALIDITY).block(); } --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org