From a46773c01483e659a3318e0e9b9fd85167eb7d95 Mon Sep 17 00:00:00 2001 From: xiangying Date: Sun, 15 Dec 2024 00:02:17 +0800 Subject: [PATCH 1/8] [improve][txn] New Transaction API --- pulsar-transaction-contrib/pom.xml | 26 +++ .../src/main/java/api/Transaction.java | 161 ++++++++++++++++++ .../src/main/java/impl/TransactionImpl.java | 99 +++++++++++ 3 files changed, 286 insertions(+) create mode 100644 pulsar-transaction-contrib/src/main/java/api/Transaction.java create mode 100644 pulsar-transaction-contrib/src/main/java/impl/TransactionImpl.java diff --git a/pulsar-transaction-contrib/pom.xml b/pulsar-transaction-contrib/pom.xml index c9f0077..e6f0bab 100644 --- a/pulsar-transaction-contrib/pom.xml +++ b/pulsar-transaction-contrib/pom.xml @@ -22,7 +22,33 @@ 1.0.0-SNAPSHOT 2024 + + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + pulsar-transaction-contrib + + + org.apache.pulsar + pulsar-client-all + ${pulsar.version} + + + junit + junit + 4.13.1 + test + + + diff --git a/pulsar-transaction-contrib/src/main/java/api/Transaction.java b/pulsar-transaction-contrib/src/main/java/api/Transaction.java new file mode 100644 index 0000000..10b095f --- /dev/null +++ b/pulsar-transaction-contrib/src/main/java/api/Transaction.java @@ -0,0 +1,161 @@ +package api; +/* + * 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. + */ + +import org.apache.pulsar.client.api.Consumer; +import org.apache.pulsar.client.api.MessageId; +import org.apache.pulsar.client.api.transaction.TxnID; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +/** + * Interface representing an optimized and extended transaction interface in the Pulsar contributors' library. + *

+ * This interface provides enhancements and extensions to the base transaction interface in Pulsar. + * It specifically addresses the issue where using CumulativeAck with transactions could not prevent message consumed repeated. + * Additionally, it clarifies and optimizes ambiguous methods for better usability and clarity. + *

+ */ +public interface Transaction { + + /** + * Records a message in the transaction. + *

+ * This method is used to include a message in the current transaction. The message will not be automatically + * acknowledged when the transaction is committed. Instead, it must be explicitly acknowledged by calling + * one of the ack methods. + *

+ * + * @param messageId the ID of the message to record + * @param consumer the consumer that received the message + */ + void recordMsg(MessageId messageId, Consumer consumer); + + /** + * Asynchronously acknowledges all received messages for a specific consumer in the transaction. + *

+ * This method is used to acknowledge all messages that have been recorded for the specified consumer in the transaction. + * The acknowledgment is asynchronous, and the future can be used to determine when the operation is complete. + *

+ * + * @param consumer the consumer that received the messages + * @return a CompletableFuture that will be completed when the acknowledgment is complete + */ + CompletableFuture ackAllReceivedMsgsAsync(Consumer consumer); + + /** + * Acknowledges all received messages for a specific consumer in the transaction. + *

+ * This method is a synchronous version of {@link #ackAllReceivedMsgsAsync(Consumer)}. + * It will block until the acknowledgment is complete. + *

+ * + * @param consumer the consumer that received the messages + * @throws ExecutionException if the acknowledgment fails + * @throws InterruptedException if the thread is interrupted while waiting for the acknowledgment to complete + */ + void ackAllReceivedMsgs(Consumer consumer) throws ExecutionException, InterruptedException; + + /** + * Acknowledges all received messages in the transaction. + *

+ * This method is a convenience method that acknowledges all messages across all consumers. + * It will block until the acknowledgment is complete. + *

+ * + * @throws ExecutionException if the acknowledgment fails + * @throws InterruptedException if the thread is interrupted while waiting for the acknowledgment to complete + */ + void ackAllReceivedMsgs() throws ExecutionException, InterruptedException; + + /** + * Asynchronously acknowledges all received messages in the transaction. + *

+ * This method is a convenience method that acknowledges all messages across all consumers. + * The acknowledgment is asynchronous, and the future can be used to determine when the operation is complete. + *

+ * + * @return a CompletableFuture that will be completed when the acknowledgment is complete + */ + CompletableFuture ackAllReceivedMsgsAsync(); + + /** + * Asynchronously commits the transaction. + *

+ * This method is used to commit the transaction, making all sent messages and acknowledgments effective. + * When the transaction is committed, consumers receive the transaction messages and the pending-ack state becomes ack state. + * The commit is asynchronous, and the future can be used to determine when the operation is complete. + *

+ * + * @return a CompletableFuture that will be completed when the commit is complete + */ + CompletableFuture commitAsync(); + + /** + * Asynchronously aborts the transaction. + *

+ * This method is used to abort the transaction, discarding all send messages and acknowledgments. + * The abort is asynchronous, and the future can be used to determine when the operation is complete. + *

+ * + * @return a CompletableFuture that will be completed when the abort is complete + */ + CompletableFuture abortAsync(); + + /** + * Commits the transaction. + *

+ * This method is a synchronous version of {@link #commitAsync()}. + * It will block until the commit is complete. + *

+ * + * @throws ExecutionException if the commit fails + * @throws InterruptedException if the thread is interrupted while waiting for the commit to complete + */ + void commit() throws ExecutionException, InterruptedException; + + /** + * Aborts the transaction. + *

+ * This method is a synchronous version of {@link #abortAsync()}. + * It will block until the abort is complete. + *

+ * + * @throws ExecutionException if the abort fails + * @throws InterruptedException if the thread is interrupted while waiting for the abort to complete + */ + void abort() throws ExecutionException, InterruptedException; + + /** + * Gets the transaction ID. + *

+ * This method returns the unique identifier for the transaction. + *

+ * + * @return the transaction ID + */ + TxnID getTxnID(); + + /** + * Gets the current state of the transaction. + *

+ * This method returns the current state of the transaction, which can be used to determine if the transaction + * is open, committed, aborted, error or timeout. + *

+ * + * @return the current state of the transaction + */ + org.apache.pulsar.client.api.transaction.Transaction.State getState(); +} \ No newline at end of file diff --git a/pulsar-transaction-contrib/src/main/java/impl/TransactionImpl.java b/pulsar-transaction-contrib/src/main/java/impl/TransactionImpl.java new file mode 100644 index 0000000..48a46f0 --- /dev/null +++ b/pulsar-transaction-contrib/src/main/java/impl/TransactionImpl.java @@ -0,0 +1,99 @@ +/* + * 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 impl; + +import api.Transaction; +import lombok.Getter; +import org.apache.pulsar.client.api.Consumer; +import org.apache.pulsar.client.api.MessageId; +import org.apache.pulsar.client.api.transaction.TxnID; +import org.apache.pulsar.common.util.FutureUtil; + +import java.util.List; +import java.util.concurrent.*; +import java.util.stream.Collectors; + +public class TransactionImpl implements Transaction { + @Getter + ConcurrentHashMap, List> receivedMessages = new ConcurrentHashMap<>(); + org.apache.pulsar.client.api.transaction.Transaction transaction; + public TransactionImpl(org.apache.pulsar.client.api.transaction.Transaction transaction) { + this.transaction = transaction; + } + + @Override + public void recordMsg(MessageId messageId, Consumer consumer) { + receivedMessages.computeIfAbsent(consumer, k -> new CopyOnWriteArrayList<>()).add(messageId); + } + + @Override + public CompletableFuture ackAllReceivedMsgsAsync(Consumer consumer) { + List messageIds = receivedMessages.get(consumer); + if (messageIds != null) { + return consumer.acknowledgeAsync(messageIds, transaction); + } + return CompletableFuture.completedFuture(null); + } + + @Override + public void ackAllReceivedMsgs(Consumer consumer) throws ExecutionException, InterruptedException { + ackAllReceivedMsgsAsync(consumer).get(); + } + + @Override + public void ackAllReceivedMsgs() throws ExecutionException, InterruptedException { + ackAllReceivedMsgsAsync().get(); + } + + @Override + public CompletableFuture ackAllReceivedMsgsAsync() { + return FutureUtil.waitForAll( + receivedMessages.keySet().stream() + .map(this::ackAllReceivedMsgsAsync) + .collect(Collectors.toList()) + ); + } + + @Override + public CompletableFuture commitAsync() { + return transaction.commit(); + } + + @Override + public CompletableFuture abortAsync() { + return transaction.abort(); + } + + @Override + public void commit() throws ExecutionException, InterruptedException { + transaction.commit().get(); + } + + @Override + public void abort() throws ExecutionException, InterruptedException { + transaction.abort().get(); + } + + + @Override + public TxnID getTxnID() { + return transaction.getTxnID(); + } + + @Override + public org.apache.pulsar.client.api.transaction.Transaction.State getState() { + return transaction.getState(); + } +} From d0a29d390ea8da5523be79deed4707fda7b08e2b Mon Sep 17 00:00:00 2001 From: xiangying Date: Sun, 15 Dec 2024 00:26:21 +0800 Subject: [PATCH 2/8] add unit test --- .../src/test/java/TransactionImplTest.java | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 pulsar-transaction-contrib/src/test/java/TransactionImplTest.java diff --git a/pulsar-transaction-contrib/src/test/java/TransactionImplTest.java b/pulsar-transaction-contrib/src/test/java/TransactionImplTest.java new file mode 100644 index 0000000..9574ef1 --- /dev/null +++ b/pulsar-transaction-contrib/src/test/java/TransactionImplTest.java @@ -0,0 +1,214 @@ +import impl.TransactionImpl; +import org.apache.pulsar.client.api.Consumer; +import org.apache.pulsar.client.api.MessageId; +import org.apache.pulsar.client.api.transaction.TxnID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +public class TransactionImplTest { + + private TransactionImpl transactionImpl; + private org.apache.pulsar.client.api.transaction.Transaction mockTransaction; + private List> mockConsumers; + private List messageIds; + + @BeforeEach + public void setUp() { + mockTransaction = mock(org.apache.pulsar.client.api.transaction.Transaction.class); + mockConsumers = new ArrayList<>(); + messageIds = new ArrayList<>(); + + // Create two mock consumers and two message IDs + for (int i = 0; i < 2; i++) { + Consumer mockConsumer = mock(Consumer.class); + MessageId messageId = mock(MessageId.class); + mockConsumers.add(mockConsumer); + messageIds.add(messageId); + } + + transactionImpl = new TransactionImpl(mockTransaction); + } + + @Test + public void testRecordMsg() { + // Record a message for a consumer + Consumer consumer = mockConsumers.get(0); + MessageId messageId = messageIds.get(0); + transactionImpl.recordMsg(messageId, consumer); + + // Verify the message is recorded for the consumer + assertTrue(transactionImpl.getReceivedMessages().get(consumer).contains(messageId)); + } + + @Test + public void testAckAllReceivedMsgsAsync() throws ExecutionException, InterruptedException { + // Record messages for different consumers + for (int i = 0; i < mockConsumers.size(); i++) { + Consumer consumer = mockConsumers.get(i); + MessageId messageId = messageIds.get(i); + transactionImpl.recordMsg(messageId, consumer); + } + + // Mock the acknowledgeAsync method for each consumer + for (Consumer consumer : mockConsumers) { + when(consumer.acknowledgeAsync(anyList(), any())).thenReturn(CompletableFuture.completedFuture(null)); + } + + // Call the ackAllReceivedMsgsAsync method + CompletableFuture future = transactionImpl.ackAllReceivedMsgsAsync(); + future.get(); + + // Verify each consumer called the correct acknowledgeAsync method with the correct message IDs + for (int i = 0; i < mockConsumers.size(); i++) { + Consumer consumer = mockConsumers.get(i); + MessageId messageId = messageIds.get(i); + verify(consumer).acknowledgeAsync(eq(List.of(messageId)), eq(mockTransaction)); + } + } + + @Test + public void testAckAllReceivedMsgs() throws ExecutionException, InterruptedException { + // Record messages for a consumer + Consumer consumer = mockConsumers.get(0); + MessageId messageId = messageIds.get(0); + transactionImpl.recordMsg(messageId, consumer); + + // Mock the acknowledgeAsync method for the consumer + when(consumer.acknowledgeAsync(anyList(), any())).thenReturn(CompletableFuture.completedFuture(null)); + + // Call the ackAllReceivedMsgs method + transactionImpl.ackAllReceivedMsgs(consumer); + + // Verify the consumer called the correct acknowledgeAsync method with the correct message IDs + verify(consumer).acknowledgeAsync(eq(List.of(messageId)), eq(mockTransaction)); + } + + @Test + public void testAckAllReceivedMsgsAll() throws ExecutionException, InterruptedException { + // Record messages for different consumers + for (int i = 0; i < mockConsumers.size(); i++) { + Consumer consumer = mockConsumers.get(i); + MessageId messageId = messageIds.get(i); + transactionImpl.recordMsg(messageId, consumer); + } + + // Mock the acknowledgeAsync method for each consumer + for (Consumer consumer : mockConsumers) { + when(consumer.acknowledgeAsync(anyList(), any())).thenReturn(CompletableFuture.completedFuture(null)); + } + + // Call the ackAllReceivedMsgs method + transactionImpl.ackAllReceivedMsgs(); + + // Verify each consumer called the correct acknowledgeAsync method with the correct message IDs + for (int i = 0; i < mockConsumers.size(); i++) { + Consumer consumer = mockConsumers.get(i); + MessageId messageId = messageIds.get(i); + verify(consumer).acknowledgeAsync(eq(List.of(messageId)), eq(mockTransaction)); + } + } + + @Test + public void testAckAllReceivedMsgsAsyncAll() throws ExecutionException, InterruptedException { + // Record messages for different consumers + for (int i = 0; i < mockConsumers.size(); i++) { + Consumer consumer = mockConsumers.get(i); + MessageId messageId = messageIds.get(i); + transactionImpl.recordMsg(messageId, consumer); + } + + // Mock the acknowledgeAsync method for each consumer + for (Consumer consumer : mockConsumers) { + when(consumer.acknowledgeAsync(anyList(), any())).thenReturn(CompletableFuture.completedFuture(null)); + } + + // Call the ackAllReceivedMsgsAsync method + CompletableFuture future = transactionImpl.ackAllReceivedMsgsAsync(); + future.get(); + + // Verify each consumer called the correct acknowledgeAsync method with the correct message IDs + for (int i = 0; i < mockConsumers.size(); i++) { + Consumer consumer = mockConsumers.get(i); + MessageId messageId = messageIds.get(i); + verify(consumer).acknowledgeAsync(eq(List.of(messageId)), eq(mockTransaction)); + } + } + + @Test + public void testCommitAsync() throws ExecutionException, InterruptedException { + // Mock the commit method of the transaction + when(mockTransaction.commit()).thenReturn(CompletableFuture.completedFuture(null)); + + // Call the commitAsync method + CompletableFuture future = transactionImpl.commitAsync(); + future.get(); + + // Verify the commit method was called + verify(mockTransaction).commit(); + } + + @Test + public void testAbortAsync() throws ExecutionException, InterruptedException { + // Mock the abort method of the transaction + when(mockTransaction.abort()).thenReturn(CompletableFuture.completedFuture(null)); + + // Call the abortAsync method + CompletableFuture future = transactionImpl.abortAsync(); + future.get(); + + // Verify the abort method was called + verify(mockTransaction).abort(); + } + + @Test + public void testCommit() throws ExecutionException, InterruptedException { + // Mock the commit method of the transaction + when(mockTransaction.commit()).thenReturn(CompletableFuture.completedFuture(null)); + + // Call the commit method + transactionImpl.commit(); + + // Verify the commit method was called + verify(mockTransaction).commit(); + } + + @Test + public void testAbort() throws ExecutionException, InterruptedException { + // Mock the abort method of the transaction + when(mockTransaction.abort()).thenReturn(CompletableFuture.completedFuture(null)); + + // Call the abort method + transactionImpl.abort(); + + // Verify the abort method was called + verify(mockTransaction).abort(); + } + + @Test + public void testGetTxnID() { + // Mock the getTxnID method of the transaction + TxnID txnID = mock(TxnID.class); + when(mockTransaction.getTxnID()).thenReturn(txnID); + + // Call the getTxnID method + assertEquals(txnID, transactionImpl.getTxnID()); + } + + @Test + public void testGetState() { + // Mock the getState method of the transaction + org.apache.pulsar.client.api.transaction.Transaction.State state = org.apache.pulsar.client.api.transaction.Transaction.State.OPEN; + when(mockTransaction.getState()).thenReturn(state); + + // Call the getState method + assertEquals(state, transactionImpl.getState()); + } +} \ No newline at end of file From f478136cac8cd6b91e8991112370ae89a83a8026 Mon Sep 17 00:00:00 2001 From: xiangying Date: Sun, 15 Dec 2024 00:26:33 +0800 Subject: [PATCH 3/8] java 17 --- pulsar-transaction-contrib/pom.xml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pulsar-transaction-contrib/pom.xml b/pulsar-transaction-contrib/pom.xml index e6f0bab..0f70c1b 100644 --- a/pulsar-transaction-contrib/pom.xml +++ b/pulsar-transaction-contrib/pom.xml @@ -28,8 +28,8 @@ org.apache.maven.plugins maven-compiler-plugin - 8 - 8 + 17 + 17 @@ -49,6 +49,18 @@ 4.13.1 test + + org.junit.jupiter + junit-jupiter-api + 5.11.0 + test + + + org.mockito + mockito-core + 5.12.0 + test + From 4451ab9dc130aa885c01206d539946b8d9370bcc Mon Sep 17 00:00:00 2001 From: xiangying Date: Sun, 15 Dec 2024 15:44:27 +0800 Subject: [PATCH 4/8] Add test demo and form code --- pulsar-transaction-contrib/pom.xml | 41 +-- .../src/main/java/api/Transaction.java | 161 ------------ .../src/main/java/impl/TransactionImpl.java | 99 ------- .../apache/pulsar/txn/api/Transaction.java | 174 +++++++++++++ .../pulsar/txn/api/TransactionFactory.java | 40 +++ .../apache/pulsar/txn/api/package-info.java | 14 + .../pulsar/txn/impl/TransactionImpl.java | 109 ++++++++ .../apache/pulsar/txn/impl/package-info.java | 14 + .../src/test/java/TransactionImplTest.java | 214 ---------------- .../pulsar/txn/SingletonPulsarContainer.java | 70 +++++ .../apache/pulsar/txn/TransactionDemo.java | 82 ++++++ .../pulsar/txn/TransactionImplTest.java | 241 ++++++++++++++++++ 12 files changed, 767 insertions(+), 492 deletions(-) delete mode 100644 pulsar-transaction-contrib/src/main/java/api/Transaction.java delete mode 100644 pulsar-transaction-contrib/src/main/java/impl/TransactionImpl.java create mode 100644 pulsar-transaction-contrib/src/main/java/org/apache/pulsar/txn/api/Transaction.java create mode 100644 pulsar-transaction-contrib/src/main/java/org/apache/pulsar/txn/api/TransactionFactory.java create mode 100644 pulsar-transaction-contrib/src/main/java/org/apache/pulsar/txn/api/package-info.java create mode 100644 pulsar-transaction-contrib/src/main/java/org/apache/pulsar/txn/impl/TransactionImpl.java create mode 100644 pulsar-transaction-contrib/src/main/java/org/apache/pulsar/txn/impl/package-info.java delete mode 100644 pulsar-transaction-contrib/src/test/java/TransactionImplTest.java create mode 100644 pulsar-transaction-contrib/src/test/java/org/apache/pulsar/txn/SingletonPulsarContainer.java create mode 100644 pulsar-transaction-contrib/src/test/java/org/apache/pulsar/txn/TransactionDemo.java create mode 100644 pulsar-transaction-contrib/src/test/java/org/apache/pulsar/txn/TransactionImplTest.java diff --git a/pulsar-transaction-contrib/pom.xml b/pulsar-transaction-contrib/pom.xml index 0f70c1b..9f386b0 100644 --- a/pulsar-transaction-contrib/pom.xml +++ b/pulsar-transaction-contrib/pom.xml @@ -21,34 +21,21 @@ pulsar-java-contrib 1.0.0-SNAPSHOT - 2024 - - - - org.apache.maven.plugins - maven-compiler-plugin - - 17 - 17 - - - - pulsar-transaction-contrib - - org.apache.pulsar - pulsar-client-all - ${pulsar.version} - junit junit 4.13.1 test + + org.apache.pulsar + pulsar-client-all + ${pulsar.version} + org.junit.jupiter junit-jupiter-api @@ -61,6 +48,24 @@ 5.12.0 test + + org.testcontainers + pulsar + test + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + + + + + 2024 diff --git a/pulsar-transaction-contrib/src/main/java/api/Transaction.java b/pulsar-transaction-contrib/src/main/java/api/Transaction.java deleted file mode 100644 index 10b095f..0000000 --- a/pulsar-transaction-contrib/src/main/java/api/Transaction.java +++ /dev/null @@ -1,161 +0,0 @@ -package api; -/* - * 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. - */ - -import org.apache.pulsar.client.api.Consumer; -import org.apache.pulsar.client.api.MessageId; -import org.apache.pulsar.client.api.transaction.TxnID; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -/** - * Interface representing an optimized and extended transaction interface in the Pulsar contributors' library. - *

- * This interface provides enhancements and extensions to the base transaction interface in Pulsar. - * It specifically addresses the issue where using CumulativeAck with transactions could not prevent message consumed repeated. - * Additionally, it clarifies and optimizes ambiguous methods for better usability and clarity. - *

- */ -public interface Transaction { - - /** - * Records a message in the transaction. - *

- * This method is used to include a message in the current transaction. The message will not be automatically - * acknowledged when the transaction is committed. Instead, it must be explicitly acknowledged by calling - * one of the ack methods. - *

- * - * @param messageId the ID of the message to record - * @param consumer the consumer that received the message - */ - void recordMsg(MessageId messageId, Consumer consumer); - - /** - * Asynchronously acknowledges all received messages for a specific consumer in the transaction. - *

- * This method is used to acknowledge all messages that have been recorded for the specified consumer in the transaction. - * The acknowledgment is asynchronous, and the future can be used to determine when the operation is complete. - *

- * - * @param consumer the consumer that received the messages - * @return a CompletableFuture that will be completed when the acknowledgment is complete - */ - CompletableFuture ackAllReceivedMsgsAsync(Consumer consumer); - - /** - * Acknowledges all received messages for a specific consumer in the transaction. - *

- * This method is a synchronous version of {@link #ackAllReceivedMsgsAsync(Consumer)}. - * It will block until the acknowledgment is complete. - *

- * - * @param consumer the consumer that received the messages - * @throws ExecutionException if the acknowledgment fails - * @throws InterruptedException if the thread is interrupted while waiting for the acknowledgment to complete - */ - void ackAllReceivedMsgs(Consumer consumer) throws ExecutionException, InterruptedException; - - /** - * Acknowledges all received messages in the transaction. - *

- * This method is a convenience method that acknowledges all messages across all consumers. - * It will block until the acknowledgment is complete. - *

- * - * @throws ExecutionException if the acknowledgment fails - * @throws InterruptedException if the thread is interrupted while waiting for the acknowledgment to complete - */ - void ackAllReceivedMsgs() throws ExecutionException, InterruptedException; - - /** - * Asynchronously acknowledges all received messages in the transaction. - *

- * This method is a convenience method that acknowledges all messages across all consumers. - * The acknowledgment is asynchronous, and the future can be used to determine when the operation is complete. - *

- * - * @return a CompletableFuture that will be completed when the acknowledgment is complete - */ - CompletableFuture ackAllReceivedMsgsAsync(); - - /** - * Asynchronously commits the transaction. - *

- * This method is used to commit the transaction, making all sent messages and acknowledgments effective. - * When the transaction is committed, consumers receive the transaction messages and the pending-ack state becomes ack state. - * The commit is asynchronous, and the future can be used to determine when the operation is complete. - *

- * - * @return a CompletableFuture that will be completed when the commit is complete - */ - CompletableFuture commitAsync(); - - /** - * Asynchronously aborts the transaction. - *

- * This method is used to abort the transaction, discarding all send messages and acknowledgments. - * The abort is asynchronous, and the future can be used to determine when the operation is complete. - *

- * - * @return a CompletableFuture that will be completed when the abort is complete - */ - CompletableFuture abortAsync(); - - /** - * Commits the transaction. - *

- * This method is a synchronous version of {@link #commitAsync()}. - * It will block until the commit is complete. - *

- * - * @throws ExecutionException if the commit fails - * @throws InterruptedException if the thread is interrupted while waiting for the commit to complete - */ - void commit() throws ExecutionException, InterruptedException; - - /** - * Aborts the transaction. - *

- * This method is a synchronous version of {@link #abortAsync()}. - * It will block until the abort is complete. - *

- * - * @throws ExecutionException if the abort fails - * @throws InterruptedException if the thread is interrupted while waiting for the abort to complete - */ - void abort() throws ExecutionException, InterruptedException; - - /** - * Gets the transaction ID. - *

- * This method returns the unique identifier for the transaction. - *

- * - * @return the transaction ID - */ - TxnID getTxnID(); - - /** - * Gets the current state of the transaction. - *

- * This method returns the current state of the transaction, which can be used to determine if the transaction - * is open, committed, aborted, error or timeout. - *

- * - * @return the current state of the transaction - */ - org.apache.pulsar.client.api.transaction.Transaction.State getState(); -} \ No newline at end of file diff --git a/pulsar-transaction-contrib/src/main/java/impl/TransactionImpl.java b/pulsar-transaction-contrib/src/main/java/impl/TransactionImpl.java deleted file mode 100644 index 48a46f0..0000000 --- a/pulsar-transaction-contrib/src/main/java/impl/TransactionImpl.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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 impl; - -import api.Transaction; -import lombok.Getter; -import org.apache.pulsar.client.api.Consumer; -import org.apache.pulsar.client.api.MessageId; -import org.apache.pulsar.client.api.transaction.TxnID; -import org.apache.pulsar.common.util.FutureUtil; - -import java.util.List; -import java.util.concurrent.*; -import java.util.stream.Collectors; - -public class TransactionImpl implements Transaction { - @Getter - ConcurrentHashMap, List> receivedMessages = new ConcurrentHashMap<>(); - org.apache.pulsar.client.api.transaction.Transaction transaction; - public TransactionImpl(org.apache.pulsar.client.api.transaction.Transaction transaction) { - this.transaction = transaction; - } - - @Override - public void recordMsg(MessageId messageId, Consumer consumer) { - receivedMessages.computeIfAbsent(consumer, k -> new CopyOnWriteArrayList<>()).add(messageId); - } - - @Override - public CompletableFuture ackAllReceivedMsgsAsync(Consumer consumer) { - List messageIds = receivedMessages.get(consumer); - if (messageIds != null) { - return consumer.acknowledgeAsync(messageIds, transaction); - } - return CompletableFuture.completedFuture(null); - } - - @Override - public void ackAllReceivedMsgs(Consumer consumer) throws ExecutionException, InterruptedException { - ackAllReceivedMsgsAsync(consumer).get(); - } - - @Override - public void ackAllReceivedMsgs() throws ExecutionException, InterruptedException { - ackAllReceivedMsgsAsync().get(); - } - - @Override - public CompletableFuture ackAllReceivedMsgsAsync() { - return FutureUtil.waitForAll( - receivedMessages.keySet().stream() - .map(this::ackAllReceivedMsgsAsync) - .collect(Collectors.toList()) - ); - } - - @Override - public CompletableFuture commitAsync() { - return transaction.commit(); - } - - @Override - public CompletableFuture abortAsync() { - return transaction.abort(); - } - - @Override - public void commit() throws ExecutionException, InterruptedException { - transaction.commit().get(); - } - - @Override - public void abort() throws ExecutionException, InterruptedException { - transaction.abort().get(); - } - - - @Override - public TxnID getTxnID() { - return transaction.getTxnID(); - } - - @Override - public org.apache.pulsar.client.api.transaction.Transaction.State getState() { - return transaction.getState(); - } -} diff --git a/pulsar-transaction-contrib/src/main/java/org/apache/pulsar/txn/api/Transaction.java b/pulsar-transaction-contrib/src/main/java/org/apache/pulsar/txn/api/Transaction.java new file mode 100644 index 0000000..d673032 --- /dev/null +++ b/pulsar-transaction-contrib/src/main/java/org/apache/pulsar/txn/api/Transaction.java @@ -0,0 +1,174 @@ +package org.apache.pulsar.txn.api; + +/* + * 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. + */ + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import org.apache.pulsar.client.api.Consumer; +import org.apache.pulsar.client.api.MessageId; +import org.apache.pulsar.client.api.Producer; +import org.apache.pulsar.client.api.TypedMessageBuilder; +import org.apache.pulsar.client.api.transaction.TxnID; + +/** + * Interface representing an optimized and extended transaction interface in the Pulsar + * contributors' library. + * + *

This interface provides enhancements and extensions to the base transaction interface in + * Pulsar. It specifically addresses the issue where using CumulativeAck with transactions could not + * prevent message consumed repeated. Additionally, it clarifies and optimizes ambiguous methods for + * better usability and clarity. + */ +public interface Transaction { + + /** + * Records a message in the transaction. + * + *

This method is used to include a message in the current transaction. The message will not be + * automatically acknowledged when the transaction is committed. Instead, it must be explicitly + * acknowledged by calling one of the ack methods. + * + * @param messageId the ID of the message to record + * @param consumer the consumer that received the message + */ + void recordMsg(MessageId messageId, Consumer consumer); + + /** + * Asynchronously acknowledges all received messages for a specific consumer in the transaction. + * + *

This method is used to acknowledge all messages that have been recorded for the specified + * consumer in the transaction. The acknowledgment is asynchronous, and the future can be used to + * determine when the operation is complete. + * + * @param consumer the consumer that received the messages + * @return a CompletableFuture that will be completed when the acknowledgment is complete + */ + CompletableFuture ackAllReceivedMsgsAsync(Consumer consumer); + + /** + * Acknowledges all received messages for a specific consumer in the transaction. + * + *

This method is a synchronous version of {@link #ackAllReceivedMsgsAsync(Consumer)}. It will + * block until the acknowledgment is complete. + * + * @param consumer the consumer that received the messages + * @throws ExecutionException if the acknowledgment fails + * @throws InterruptedException if the thread is interrupted while waiting for the acknowledgment + * to complete + */ + void ackAllReceivedMsgs(Consumer consumer) throws ExecutionException, InterruptedException; + + /** + * Acknowledges all received messages in the transaction. + * + *

This method is a convenience method that acknowledges all messages across all consumers. It + * will block until the acknowledgment is complete. + * + * @throws ExecutionException if the acknowledgment fails + * @throws InterruptedException if the thread is interrupted while waiting for the acknowledgment + * to complete + */ + void ackAllReceivedMsgs() throws ExecutionException, InterruptedException; + + /** + * Asynchronously acknowledges all received messages in the transaction. + * + *

This method is a convenience method that acknowledges all messages across all consumers. The + * acknowledgment is asynchronous, and the future can be used to determine when the operation is + * complete. + * + * @return a CompletableFuture that will be completed when the acknowledgment is complete + */ + CompletableFuture ackAllReceivedMsgsAsync(); + + /** + * Creates a new transactional message builder for the given producer. + * + *

This method returns a {@link TypedMessageBuilder} instance that is bound to the specified + * producer and transaction. The returned message builder can be used to construct and send + * messages within the context of a transaction. + * + * @param producer the producer instance used to send messages + * @param the type of messages produced by the producer + * @return a TypedMessageBuilder instance for building transactional messages + */ + TypedMessageBuilder newTransactionMessage(Producer producer); + + /** + * Asynchronously commits the transaction. + * + *

This method is used to commit the transaction, making all sent messages and acknowledgments + * effective. When the transaction is committed, consumers receive the transaction messages and + * the pending-ack state becomes ack state. The commit is asynchronous, and the future can be used + * to determine when the operation is complete. + * + * @return a CompletableFuture that will be completed when the commit is complete + */ + CompletableFuture commitAsync(); + + /** + * Asynchronously aborts the transaction. + * + *

This method is used to abort the transaction, discarding all send messages and + * acknowledgments. The abort is asynchronous, and the future can be used to determine when the + * operation is complete. + * + * @return a CompletableFuture that will be completed when the abort is complete + */ + CompletableFuture abortAsync(); + + /** + * Commits the transaction. + * + *

This method is a synchronous version of {@link #commitAsync()}. It will block until the + * commit is complete. + * + * @throws ExecutionException if the commit fails + * @throws InterruptedException if the thread is interrupted while waiting for the commit to + * complete + */ + void commit() throws ExecutionException, InterruptedException; + + /** + * Aborts the transaction. + * + *

This method is a synchronous version of {@link #abortAsync()}. It will block until the abort + * is complete. + * + * @throws ExecutionException if the abort fails + * @throws InterruptedException if the thread is interrupted while waiting for the abort to + * complete + */ + void abort() throws ExecutionException, InterruptedException; + + /** + * Gets the transaction ID. + * + *

This method returns the unique identifier for the transaction. + * + * @return the transaction ID + */ + TxnID getTxnID(); + + /** + * Gets the current state of the transaction. + * + *

This method returns the current state of the transaction, which can be used to determine if + * the transaction is open, committed, aborted, error or timeout. + * + * @return the current state of the transaction + */ + org.apache.pulsar.client.api.transaction.Transaction.State getState(); +} diff --git a/pulsar-transaction-contrib/src/main/java/org/apache/pulsar/txn/api/TransactionFactory.java b/pulsar-transaction-contrib/src/main/java/org/apache/pulsar/txn/api/TransactionFactory.java new file mode 100644 index 0000000..b540b64 --- /dev/null +++ b/pulsar-transaction-contrib/src/main/java/org/apache/pulsar/txn/api/TransactionFactory.java @@ -0,0 +1,40 @@ +/* + * 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 org.apache.pulsar.txn.api; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import org.apache.pulsar.client.api.PulsarClient; +import org.apache.pulsar.txn.impl.TransactionImpl; + +public class TransactionFactory { + + /** + * Creates a new transaction with the specified timeout. + * + * @param pulsarClient the Pulsar client instance + * @param timeout the transaction timeout + * @param unit the time unit of the timeout + * @return a CompletableFuture that will be completed with the new transaction + */ + public static CompletableFuture createTransaction( + PulsarClient pulsarClient, long timeout, TimeUnit unit) { + // Create a transaction with the specified timeout + return pulsarClient + .newTransaction() + .withTransactionTimeout(timeout, unit) + .build() + .thenApply(TransactionImpl::new); + } +} diff --git a/pulsar-transaction-contrib/src/main/java/org/apache/pulsar/txn/api/package-info.java b/pulsar-transaction-contrib/src/main/java/org/apache/pulsar/txn/api/package-info.java new file mode 100644 index 0000000..a1cf663 --- /dev/null +++ b/pulsar-transaction-contrib/src/main/java/org/apache/pulsar/txn/api/package-info.java @@ -0,0 +1,14 @@ +/* + * 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 org.apache.pulsar.txn.api; diff --git a/pulsar-transaction-contrib/src/main/java/org/apache/pulsar/txn/impl/TransactionImpl.java b/pulsar-transaction-contrib/src/main/java/org/apache/pulsar/txn/impl/TransactionImpl.java new file mode 100644 index 0000000..94434cd --- /dev/null +++ b/pulsar-transaction-contrib/src/main/java/org/apache/pulsar/txn/impl/TransactionImpl.java @@ -0,0 +1,109 @@ +/* + * 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 org.apache.pulsar.txn.impl; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; +import lombok.Getter; +import org.apache.pulsar.client.api.Consumer; +import org.apache.pulsar.client.api.MessageId; +import org.apache.pulsar.client.api.Producer; +import org.apache.pulsar.client.api.TypedMessageBuilder; +import org.apache.pulsar.client.api.transaction.TxnID; +import org.apache.pulsar.common.util.FutureUtil; +import org.apache.pulsar.txn.api.Transaction; + +public class TransactionImpl implements Transaction { + @Getter + ConcurrentHashMap, List> receivedMessages = new ConcurrentHashMap<>(); + + org.apache.pulsar.client.api.transaction.Transaction transaction; + + public TransactionImpl(org.apache.pulsar.client.api.transaction.Transaction transaction) { + this.transaction = transaction; + } + + @Override + public void recordMsg(MessageId messageId, Consumer consumer) { + receivedMessages.computeIfAbsent(consumer, k -> new CopyOnWriteArrayList<>()).add(messageId); + } + + @Override + public CompletableFuture ackAllReceivedMsgsAsync(Consumer consumer) { + List messageIds = receivedMessages.remove(consumer); + if (messageIds != null) { + return consumer.acknowledgeAsync(messageIds, transaction); + } + return CompletableFuture.completedFuture(null); + } + + @Override + public void ackAllReceivedMsgs(Consumer consumer) + throws ExecutionException, InterruptedException { + ackAllReceivedMsgsAsync(consumer).get(); + } + + @Override + public void ackAllReceivedMsgs() throws ExecutionException, InterruptedException { + ackAllReceivedMsgsAsync().get(); + } + + @Override + public CompletableFuture ackAllReceivedMsgsAsync() { + return FutureUtil.waitForAll( + receivedMessages.keySet().stream() + .map(this::ackAllReceivedMsgsAsync) + .collect(Collectors.toList())); + } + + @Override + public TypedMessageBuilder newTransactionMessage(Producer producer) { + return producer.newMessage(transaction); + } + + @Override + public CompletableFuture commitAsync() { + return transaction.commit(); + } + + @Override + public CompletableFuture abortAsync() { + return transaction.abort(); + } + + @Override + public void commit() throws ExecutionException, InterruptedException { + transaction.commit().get(); + } + + @Override + public void abort() throws ExecutionException, InterruptedException { + transaction.abort().get(); + } + + @Override + public TxnID getTxnID() { + return transaction.getTxnID(); + } + + @Override + public org.apache.pulsar.client.api.transaction.Transaction.State getState() { + return transaction.getState(); + } +} diff --git a/pulsar-transaction-contrib/src/main/java/org/apache/pulsar/txn/impl/package-info.java b/pulsar-transaction-contrib/src/main/java/org/apache/pulsar/txn/impl/package-info.java new file mode 100644 index 0000000..2ec84e4 --- /dev/null +++ b/pulsar-transaction-contrib/src/main/java/org/apache/pulsar/txn/impl/package-info.java @@ -0,0 +1,14 @@ +/* + * 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 org.apache.pulsar.txn.impl; diff --git a/pulsar-transaction-contrib/src/test/java/TransactionImplTest.java b/pulsar-transaction-contrib/src/test/java/TransactionImplTest.java deleted file mode 100644 index 9574ef1..0000000 --- a/pulsar-transaction-contrib/src/test/java/TransactionImplTest.java +++ /dev/null @@ -1,214 +0,0 @@ -import impl.TransactionImpl; -import org.apache.pulsar.client.api.Consumer; -import org.apache.pulsar.client.api.MessageId; -import org.apache.pulsar.client.api.transaction.TxnID; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -import static org.mockito.Mockito.*; -import static org.junit.jupiter.api.Assertions.*; - -public class TransactionImplTest { - - private TransactionImpl transactionImpl; - private org.apache.pulsar.client.api.transaction.Transaction mockTransaction; - private List> mockConsumers; - private List messageIds; - - @BeforeEach - public void setUp() { - mockTransaction = mock(org.apache.pulsar.client.api.transaction.Transaction.class); - mockConsumers = new ArrayList<>(); - messageIds = new ArrayList<>(); - - // Create two mock consumers and two message IDs - for (int i = 0; i < 2; i++) { - Consumer mockConsumer = mock(Consumer.class); - MessageId messageId = mock(MessageId.class); - mockConsumers.add(mockConsumer); - messageIds.add(messageId); - } - - transactionImpl = new TransactionImpl(mockTransaction); - } - - @Test - public void testRecordMsg() { - // Record a message for a consumer - Consumer consumer = mockConsumers.get(0); - MessageId messageId = messageIds.get(0); - transactionImpl.recordMsg(messageId, consumer); - - // Verify the message is recorded for the consumer - assertTrue(transactionImpl.getReceivedMessages().get(consumer).contains(messageId)); - } - - @Test - public void testAckAllReceivedMsgsAsync() throws ExecutionException, InterruptedException { - // Record messages for different consumers - for (int i = 0; i < mockConsumers.size(); i++) { - Consumer consumer = mockConsumers.get(i); - MessageId messageId = messageIds.get(i); - transactionImpl.recordMsg(messageId, consumer); - } - - // Mock the acknowledgeAsync method for each consumer - for (Consumer consumer : mockConsumers) { - when(consumer.acknowledgeAsync(anyList(), any())).thenReturn(CompletableFuture.completedFuture(null)); - } - - // Call the ackAllReceivedMsgsAsync method - CompletableFuture future = transactionImpl.ackAllReceivedMsgsAsync(); - future.get(); - - // Verify each consumer called the correct acknowledgeAsync method with the correct message IDs - for (int i = 0; i < mockConsumers.size(); i++) { - Consumer consumer = mockConsumers.get(i); - MessageId messageId = messageIds.get(i); - verify(consumer).acknowledgeAsync(eq(List.of(messageId)), eq(mockTransaction)); - } - } - - @Test - public void testAckAllReceivedMsgs() throws ExecutionException, InterruptedException { - // Record messages for a consumer - Consumer consumer = mockConsumers.get(0); - MessageId messageId = messageIds.get(0); - transactionImpl.recordMsg(messageId, consumer); - - // Mock the acknowledgeAsync method for the consumer - when(consumer.acknowledgeAsync(anyList(), any())).thenReturn(CompletableFuture.completedFuture(null)); - - // Call the ackAllReceivedMsgs method - transactionImpl.ackAllReceivedMsgs(consumer); - - // Verify the consumer called the correct acknowledgeAsync method with the correct message IDs - verify(consumer).acknowledgeAsync(eq(List.of(messageId)), eq(mockTransaction)); - } - - @Test - public void testAckAllReceivedMsgsAll() throws ExecutionException, InterruptedException { - // Record messages for different consumers - for (int i = 0; i < mockConsumers.size(); i++) { - Consumer consumer = mockConsumers.get(i); - MessageId messageId = messageIds.get(i); - transactionImpl.recordMsg(messageId, consumer); - } - - // Mock the acknowledgeAsync method for each consumer - for (Consumer consumer : mockConsumers) { - when(consumer.acknowledgeAsync(anyList(), any())).thenReturn(CompletableFuture.completedFuture(null)); - } - - // Call the ackAllReceivedMsgs method - transactionImpl.ackAllReceivedMsgs(); - - // Verify each consumer called the correct acknowledgeAsync method with the correct message IDs - for (int i = 0; i < mockConsumers.size(); i++) { - Consumer consumer = mockConsumers.get(i); - MessageId messageId = messageIds.get(i); - verify(consumer).acknowledgeAsync(eq(List.of(messageId)), eq(mockTransaction)); - } - } - - @Test - public void testAckAllReceivedMsgsAsyncAll() throws ExecutionException, InterruptedException { - // Record messages for different consumers - for (int i = 0; i < mockConsumers.size(); i++) { - Consumer consumer = mockConsumers.get(i); - MessageId messageId = messageIds.get(i); - transactionImpl.recordMsg(messageId, consumer); - } - - // Mock the acknowledgeAsync method for each consumer - for (Consumer consumer : mockConsumers) { - when(consumer.acknowledgeAsync(anyList(), any())).thenReturn(CompletableFuture.completedFuture(null)); - } - - // Call the ackAllReceivedMsgsAsync method - CompletableFuture future = transactionImpl.ackAllReceivedMsgsAsync(); - future.get(); - - // Verify each consumer called the correct acknowledgeAsync method with the correct message IDs - for (int i = 0; i < mockConsumers.size(); i++) { - Consumer consumer = mockConsumers.get(i); - MessageId messageId = messageIds.get(i); - verify(consumer).acknowledgeAsync(eq(List.of(messageId)), eq(mockTransaction)); - } - } - - @Test - public void testCommitAsync() throws ExecutionException, InterruptedException { - // Mock the commit method of the transaction - when(mockTransaction.commit()).thenReturn(CompletableFuture.completedFuture(null)); - - // Call the commitAsync method - CompletableFuture future = transactionImpl.commitAsync(); - future.get(); - - // Verify the commit method was called - verify(mockTransaction).commit(); - } - - @Test - public void testAbortAsync() throws ExecutionException, InterruptedException { - // Mock the abort method of the transaction - when(mockTransaction.abort()).thenReturn(CompletableFuture.completedFuture(null)); - - // Call the abortAsync method - CompletableFuture future = transactionImpl.abortAsync(); - future.get(); - - // Verify the abort method was called - verify(mockTransaction).abort(); - } - - @Test - public void testCommit() throws ExecutionException, InterruptedException { - // Mock the commit method of the transaction - when(mockTransaction.commit()).thenReturn(CompletableFuture.completedFuture(null)); - - // Call the commit method - transactionImpl.commit(); - - // Verify the commit method was called - verify(mockTransaction).commit(); - } - - @Test - public void testAbort() throws ExecutionException, InterruptedException { - // Mock the abort method of the transaction - when(mockTransaction.abort()).thenReturn(CompletableFuture.completedFuture(null)); - - // Call the abort method - transactionImpl.abort(); - - // Verify the abort method was called - verify(mockTransaction).abort(); - } - - @Test - public void testGetTxnID() { - // Mock the getTxnID method of the transaction - TxnID txnID = mock(TxnID.class); - when(mockTransaction.getTxnID()).thenReturn(txnID); - - // Call the getTxnID method - assertEquals(txnID, transactionImpl.getTxnID()); - } - - @Test - public void testGetState() { - // Mock the getState method of the transaction - org.apache.pulsar.client.api.transaction.Transaction.State state = org.apache.pulsar.client.api.transaction.Transaction.State.OPEN; - when(mockTransaction.getState()).thenReturn(state); - - // Call the getState method - assertEquals(state, transactionImpl.getState()); - } -} \ No newline at end of file diff --git a/pulsar-transaction-contrib/src/test/java/org/apache/pulsar/txn/SingletonPulsarContainer.java b/pulsar-transaction-contrib/src/test/java/org/apache/pulsar/txn/SingletonPulsarContainer.java new file mode 100644 index 0000000..afd3bdc --- /dev/null +++ b/pulsar-transaction-contrib/src/test/java/org/apache/pulsar/txn/SingletonPulsarContainer.java @@ -0,0 +1,70 @@ +/* + * 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 org.apache.pulsar.txn; +import java.io.IOException; +import java.time.Duration; +import java.util.Properties; +import lombok.extern.slf4j.Slf4j; +import org.apache.pulsar.client.admin.PulsarAdmin; +import org.apache.pulsar.client.api.PulsarClient; +import org.apache.pulsar.client.api.PulsarClientException; +import org.testcontainers.containers.PulsarContainer; +import org.testcontainers.utility.DockerImageName; + +@Slf4j +public class SingletonPulsarContainer { + + private static final PulsarContainer PULSAR_CONTAINER; + + static { + PULSAR_CONTAINER = new PulsarContainer(getPulsarImage()) + .withEnv("PULSAR_PREFIX_acknowledgmentAtBatchIndexLevelEnabled", "true") + .withEnv("PULSAR_PREFIX_transactionCoordinatorEnabled", "true") + .withStartupTimeout(Duration.ofMinutes(3)); + PULSAR_CONTAINER.start(); + } + + private static DockerImageName getPulsarImage() { + return DockerImageName.parse("apachepulsar/pulsar:" + getPulsarImageVersion()); + } + + private static String getPulsarImageVersion() { + String pulsarVersion = ""; + Properties properties = new Properties(); + try { + properties.load(SingletonPulsarContainer.class.getClassLoader() + .getResourceAsStream("pulsar-container.properties")); + if (!properties.isEmpty()) { + pulsarVersion = properties.getProperty("pulsar.version"); + } + } catch (IOException e) { + log.error("Failed to load pulsar version. " + e.getCause()); + } + return pulsarVersion; + } + + static PulsarClient createPulsarClient() throws PulsarClientException { + return PulsarClient.builder() + .serviceUrl(SingletonPulsarContainer.PULSAR_CONTAINER.getPulsarBrokerUrl()) + .enableTransaction(true) + .build(); + } + + static PulsarAdmin createPulsarAdmin() throws PulsarClientException { + return PulsarAdmin.builder() + .serviceHttpUrl(SingletonPulsarContainer.PULSAR_CONTAINER.getHttpServiceUrl()) + .build(); + } +} diff --git a/pulsar-transaction-contrib/src/test/java/org/apache/pulsar/txn/TransactionDemo.java b/pulsar-transaction-contrib/src/test/java/org/apache/pulsar/txn/TransactionDemo.java new file mode 100644 index 0000000..308c521 --- /dev/null +++ b/pulsar-transaction-contrib/src/test/java/org/apache/pulsar/txn/TransactionDemo.java @@ -0,0 +1,82 @@ +/* + * 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 org.apache.pulsar.txn; + +import java.util.concurrent.TimeUnit; +import org.apache.pulsar.client.api.Consumer; +import org.apache.pulsar.client.api.Message; +import org.apache.pulsar.client.api.MessageId; +import org.apache.pulsar.client.api.Producer; +import org.apache.pulsar.client.api.PulsarClient; +import org.apache.pulsar.client.api.Schema; +import org.apache.pulsar.txn.api.Transaction; +import org.apache.pulsar.txn.api.TransactionFactory; +import org.testng.annotations.Test; + +public class TransactionDemo { + + @Test + public void transactionDemo() throws Exception { + String pubTopic = "persistent://public/default/my-pub-topic"; + String subTopic = "persistent://public/default/my-sub-topic"; + String subscription = "my-subscription"; + + // Create a Pulsar client instance + PulsarClient client = SingletonPulsarContainer.createPulsarClient(); + + // Create a Transaction object + // Use TransactionFactory to create a transaction object with a timeout of 5 seconds + Transaction transaction = + TransactionFactory.createTransaction(client, 5, TimeUnit.SECONDS).get(); + + // Create producers and a consumer + // Create two producers to send messages to different topics + Producer producerToPubTopic = client.newProducer(Schema.STRING).topic(pubTopic).create(); + Producer producerToSubTopic = client.newProducer(Schema.STRING).topic(subTopic).create(); + + // Create a consumer to receive messages from the subTopic + Consumer consumerFromSubTopic = client + .newConsumer(Schema.STRING) + .subscriptionName(subscription) + .topic(subTopic) + .subscribe(); + + // Send a message to the Sub Topic + producerToSubTopic.send("Hello World"); + + // Receive a message + Message receivedMessage = consumerFromSubTopic.receive(); + MessageId receivedMessageId = receivedMessage.getMessageId(); + + // Record the message in the transaction + transaction.recordMsg(receivedMessageId, consumerFromSubTopic); + + // Forward the transaction message to the pub topic + // Use the transaction message builder to forward the received message to the pubTopic + transaction.newTransactionMessage(producerToSubTopic).value(receivedMessage.getValue()).send(); + + // Acknowledge all received messages + // Acknowledge all messages received from the subTopic within the transaction + transaction.ackAllReceivedMsgs(consumerFromSubTopic); + + // Commit the transaction + // Commit the transaction to ensure all recorded messages and acknowledgments take effect + transaction.commit(); + + // Close the consumer, producers, and client to release resources + consumerFromSubTopic.close(); + producerToSubTopic.close(); + client.close(); + } +} \ No newline at end of file diff --git a/pulsar-transaction-contrib/src/test/java/org/apache/pulsar/txn/TransactionImplTest.java b/pulsar-transaction-contrib/src/test/java/org/apache/pulsar/txn/TransactionImplTest.java new file mode 100644 index 0000000..0eb18bf --- /dev/null +++ b/pulsar-transaction-contrib/src/test/java/org/apache/pulsar/txn/TransactionImplTest.java @@ -0,0 +1,241 @@ +/* + * 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 org.apache.pulsar.txn; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import org.apache.pulsar.client.api.Consumer; +import org.apache.pulsar.client.api.MessageId; +import org.apache.pulsar.client.api.transaction.TxnID; +import org.apache.pulsar.txn.impl.TransactionImpl; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +public class TransactionImplTest { + + private TransactionImpl transactionImpl; + private org.apache.pulsar.client.api.transaction.Transaction mockTransaction; + private List> mockConsumers; + private List messageIds; + + @BeforeClass + public void setUp() { + mockTransaction = mock(org.apache.pulsar.client.api.transaction.Transaction.class); + mockConsumers = new ArrayList<>(); + messageIds = new ArrayList<>(); + + // Create two mock consumers and two message IDs + for (int i = 0; i < 2; i++) { + Consumer mockConsumer = mock(Consumer.class); + MessageId messageId = mock(MessageId.class); + mockConsumers.add(mockConsumer); + messageIds.add(messageId); + } + + transactionImpl = new TransactionImpl(mockTransaction); + } + + @Test + public void testRecordMsg() { + // Record a message for a consumer + Consumer consumer = mockConsumers.get(0); + MessageId messageId = messageIds.get(0); + transactionImpl.recordMsg(messageId, consumer); + + // Verify the message is recorded for the consumer + assertTrue(transactionImpl.getReceivedMessages().get(consumer).contains(messageId)); + } + + @Test + public void testAckAllReceivedMsgsAsync() throws ExecutionException, InterruptedException { + // Record messages for different consumers + for (int i = 0; i < mockConsumers.size(); i++) { + Consumer consumer = mockConsumers.get(i); + MessageId messageId = messageIds.get(i); + transactionImpl.recordMsg(messageId, consumer); + } + + // Mock the acknowledgeAsync method for each consumer + for (Consumer consumer : mockConsumers) { + when(consumer.acknowledgeAsync(anyList(), any())) + .thenReturn(CompletableFuture.completedFuture(null)); + } + + // Call the ackAllReceivedMsgsAsync method + CompletableFuture future = transactionImpl.ackAllReceivedMsgsAsync(); + future.get(); + + // Verify each consumer called the correct acknowledgeAsync method with the correct message IDs + for (int i = 0; i < mockConsumers.size(); i++) { + Consumer consumer = mockConsumers.get(i); + MessageId messageId = messageIds.get(i); + verify(consumer).acknowledgeAsync(eq(List.of(messageId)), eq(mockTransaction)); + } + } + + @Test + public void testAckAllReceivedMsgs() throws ExecutionException, InterruptedException { + // Record messages for a consumer + Consumer consumer = mockConsumers.get(0); + MessageId messageId = messageIds.get(0); + transactionImpl.recordMsg(messageId, consumer); + + // Mock the acknowledgeAsync method for the consumer + when(consumer.acknowledgeAsync(anyList(), any())) + .thenReturn(CompletableFuture.completedFuture(null)); + + // Call the ackAllReceivedMsgs method + transactionImpl.ackAllReceivedMsgs(consumer); + + // Verify the consumer called the correct acknowledgeAsync method with the correct message IDs + verify(consumer).acknowledgeAsync(eq(List.of(messageId)), eq(mockTransaction)); + // Verify the message Ids were removed from the transaction context after acked. + assertEquals(transactionImpl.getReceivedMessages().size(), 0); + } + + @Test + public void testAckAllReceivedMsgsAll() throws ExecutionException, InterruptedException { + // Record messages for different consumers + for (int i = 0; i < mockConsumers.size(); i++) { + Consumer consumer = mockConsumers.get(i); + MessageId messageId = messageIds.get(i); + transactionImpl.recordMsg(messageId, consumer); + } + + // Mock the acknowledgeAsync method for each consumer + for (Consumer consumer : mockConsumers) { + when(consumer.acknowledgeAsync(anyList(), any())) + .thenReturn(CompletableFuture.completedFuture(null)); + } + + // Call the ackAllReceivedMsgs method + transactionImpl.ackAllReceivedMsgs(); + + // Verify each consumer called the correct acknowledgeAsync method with the correct message IDs + for (int i = 0; i < mockConsumers.size(); i++) { + Consumer consumer = mockConsumers.get(i); + MessageId messageId = messageIds.get(i); + verify(consumer).acknowledgeAsync(eq(List.of(messageId)), eq(mockTransaction)); + } + } + + @Test + public void testAckAllReceivedMsgsAsyncAll() throws ExecutionException, InterruptedException { + // Record messages for different consumers + for (int i = 0; i < mockConsumers.size(); i++) { + Consumer consumer = mockConsumers.get(i); + MessageId messageId = messageIds.get(i); + transactionImpl.recordMsg(messageId, consumer); + } + + // Mock the acknowledgeAsync method for each consumer + for (Consumer consumer : mockConsumers) { + when(consumer.acknowledgeAsync(anyList(), any())) + .thenReturn(CompletableFuture.completedFuture(null)); + } + + // Call the ackAllReceivedMsgsAsync method + CompletableFuture future = transactionImpl.ackAllReceivedMsgsAsync(); + future.get(); + + // Verify each consumer called the correct acknowledgeAsync method with the correct message IDs + for (int i = 0; i < mockConsumers.size(); i++) { + Consumer consumer = mockConsumers.get(i); + MessageId messageId = messageIds.get(i); + verify(consumer).acknowledgeAsync(eq(List.of(messageId)), eq(mockTransaction)); + } + } + + @Test + public void testCommitAsync() throws ExecutionException, InterruptedException { + // Mock the commit method of the transaction + when(mockTransaction.commit()).thenReturn(CompletableFuture.completedFuture(null)); + + // Call the commitAsync method + CompletableFuture future = transactionImpl.commitAsync(); + future.get(); + + // Verify the commit method was called + verify(mockTransaction).commit(); + } + + @Test + public void testAbortAsync() throws ExecutionException, InterruptedException { + // Mock the abort method of the transaction + when(mockTransaction.abort()).thenReturn(CompletableFuture.completedFuture(null)); + + // Call the abortAsync method + CompletableFuture future = transactionImpl.abortAsync(); + future.get(); + + // Verify the abort method was called + verify(mockTransaction).abort(); + } + + @Test + public void testCommit() throws ExecutionException, InterruptedException { + // Mock the commit method of the transaction + when(mockTransaction.commit()).thenReturn(CompletableFuture.completedFuture(null)); + + // Call the commit method + transactionImpl.commit(); + + // Verify the commit method was called + verify(mockTransaction).commit(); + } + + @Test + public void testAbort() throws ExecutionException, InterruptedException { + // Mock the abort method of the transaction + when(mockTransaction.abort()).thenReturn(CompletableFuture.completedFuture(null)); + + // Call the abort method + transactionImpl.abort(); + + // Verify the abort method was called + verify(mockTransaction).abort(); + } + + @Test + public void testGetTxnID() { + // Mock the getTxnID method of the transaction + TxnID txnID = mock(TxnID.class); + when(mockTransaction.getTxnID()).thenReturn(txnID); + + // Call the getTxnID method + assertEquals(txnID, transactionImpl.getTxnID()); + } + + @Test + public void testGetState() { + // Mock the getState method of the transaction + org.apache.pulsar.client.api.transaction.Transaction.State state = + org.apache.pulsar.client.api.transaction.Transaction.State.OPEN; + when(mockTransaction.getState()).thenReturn(state); + + // Call the getState method + assertEquals(state, transactionImpl.getState()); + } +} From ac124dfbf7dc38ae76934a15a1bc8fb264ab09a5 Mon Sep 17 00:00:00 2001 From: xiangying Date: Sun, 15 Dec 2024 17:39:41 +0800 Subject: [PATCH 5/8] pcip --- pcip/pcip-3.md | 238 +++++++++++++++++++++++++++++++ pcip/static/img/pcip-3/image.png | Bin 0 -> 147332 bytes 2 files changed, 238 insertions(+) create mode 100644 pcip/pcip-3.md create mode 100644 pcip/static/img/pcip-3/image.png diff --git a/pcip/pcip-3.md b/pcip/pcip-3.md new file mode 100644 index 0000000..e0c7a66 --- /dev/null +++ b/pcip/pcip-3.md @@ -0,0 +1,238 @@ +# PCIP-3:Pulsar Extended Transaction API Enhancement Proposal +# Background knowledge +When users consume messages using the Fail-over subscription mode and confirm messages using cumulative Ack, duplicate consumption may occur. In this case, even if users use Transaction, they cannot achieve Just-Once. +As shown in the figure below, in failover mode, the two consumer1 and consumer2 started simultaneously frequently undergo two disconnection switches. +Finally, Consumer1 consumed M1, M2, M4, M5; Consumer2 consumed M1, M2, M3. M1 and M2 were consumed twice. +![image.png](static/img/pcip-3/image.png) + +# Goals +Solve the problem of cumulative ack consumption duplication by designing a new transaction API. +Why not fix this issue in the Pulsar main repository? +- The complexity of fixing problems without modifying the Client API is high, and the problem-solving cycle is long. +- There are indeed many confusing usage postures for existing transaction APIs + - For example, the abort () and commit () methods may seem synchronous in name, but they are actually asynchronous. In actual use, you need to use abort ().get () and commit ().get (). +- Modifying the Client API in the Pulsar main repository is a difficult task because we cannot determine whether the new solution is necessarily perfect, and the time cycle for updating the API is long. + The benefit of solving this problem in the contributor repository is: +- By wrapping the original Transaction API, this problem can be solved in a concise way. This wrapping can be seen as a best practice that does not affect the use of existing users, while providing a reference solution for users who encounter similar problems. +# High Level Design +Design a new API to place the context of message sending and consumption within Transaction, which not only solves the problem of repeated consumption, but also retains sufficient scalability for possible optimization in the future. +- Solve the problem of repeated consumption - use the function of individual ack messagelist to batch ack messages instead of the original cumulative ack. +- Retained sufficient scalability - sending messages requires using Transaction to construct messages, and consumed transaction messages are recorded in Transaction. Later, more optimizations can be added using this information without changing the interface. + +## Public-facing Changes +### Public API +The org.apache.pulsar.txn.api.Transaction interface is an optimized and extended transaction interface designed to enhance usability and clarity in the Pulsar contributors' library. It addresses issues with CumulativeAck and transactions not preventing repeated message consumption, and it refines ambiguous methods for better clarity. Key features include: +- Message Recording: Records messages in a transaction without automatic acknowledgment. +- Asynchronous and Synchronous Acknowledgment: Supports both asynchronous and synchronous acknowledgment of all received messages for specific consumers or across all consumers. +- Transactional Message Builder: Creates a new transactional message builder for a given producer to construct and send messages within a transaction context. +- Committing and Aborting Transactions: Offers both asynchronous and synchronous methods to commit or abort transactions, ensuring the effectiveness of message sends and acknowledgments. +- Transaction ID and State Retrieval: Provides methods to retrieve the unique transaction ID and its current state to determine the transaction's lifecycle phase. +```java +/** +* Interface representing an optimized and extended transaction interface in the Pulsar +* contributors' library. +* +*

This interface provides enhancements and extensions to the base transaction interface in +* Pulsar. It specifically addresses the issue where using CumulativeAck with transactions could not +* prevent message consumed repeated. Additionally, it clarifies and optimizes ambiguous methods for +* better usability and clarity. + */ + public interface Transaction { + +/** +* Records a message in the transaction. +* +*

This method is used to include a message in the current transaction. The message will not be +* automatically acknowledged when the transaction is committed. Instead, it must be explicitly +* acknowledged by calling one of the ack methods. +* +* @param messageId the ID of the message to record +* @param consumer the consumer that received the message + */ + void recordMsg(MessageId messageId, Consumer consumer); + +/** +* Asynchronously acknowledges all received messages for a specific consumer in the transaction. +* +*

This method is used to acknowledge all messages that have been recorded for the specified +* consumer in the transaction. The acknowledgment is asynchronous, and the future can be used to +* determine when the operation is complete. +* +* @param consumer the consumer that received the messages +* @return a CompletableFuture that will be completed when the acknowledgment is complete + */ + CompletableFuture ackAllReceivedMsgsAsync(Consumer consumer); + +/** +* Acknowledges all received messages for a specific consumer in the transaction. +* +*

This method is a synchronous version of {@link #ackAllReceivedMsgsAsync(Consumer)}. It will +* block until the acknowledgment is complete. +* +* @param consumer the consumer that received the messages +* @throws ExecutionException if the acknowledgment fails +* @throws InterruptedException if the thread is interrupted while waiting for the acknowledgment +* to complete +*/ +void ackAllReceivedMsgs(Consumer consumer) throws ExecutionException, InterruptedException; + +/** +* Acknowledges all received messages in the transaction. +* +*

This method is a convenience method that acknowledges all messages across all consumers. It +* will block until the acknowledgment is complete. +* +* @throws ExecutionException if the acknowledgment fails +* @throws InterruptedException if the thread is interrupted while waiting for the acknowledgment +* to complete +*/ +void ackAllReceivedMsgs() throws ExecutionException, InterruptedException; + +/** +* Asynchronously acknowledges all received messages in the transaction. +* +*

This method is a convenience method that acknowledges all messages across all consumers. The +* acknowledgment is asynchronous, and the future can be used to determine when the operation is +* complete. +* +* @return a CompletableFuture that will be completed when the acknowledgment is complete + */ + CompletableFuture ackAllReceivedMsgsAsync(); + +/** +* Creates a new transactional message builder for the given producer. +* +*

This method returns a {@link TypedMessageBuilder} instance that is bound to the specified +* producer and transaction. The returned message builder can be used to construct and send +* messages within the context of a transaction. +* +* @param producer the producer instance used to send messages +* @param the type of messages produced by the producer +* @return a TypedMessageBuilder instance for building transactional messages + */ + TypedMessageBuilder newTransactionMessage(Producer producer); + +/** +* Asynchronously commits the transaction. +* +*

This method is used to commit the transaction, making all sent messages and acknowledgments +* effective. When the transaction is committed, consumers receive the transaction messages and +* the pending-ack state becomes ack state. The commit is asynchronous, and the future can be used +* to determine when the operation is complete. +* +* @return a CompletableFuture that will be completed when the commit is complete + */ + CompletableFuture commitAsync(); + +/** +* Asynchronously aborts the transaction. +* +*

This method is used to abort the transaction, discarding all send messages and +* acknowledgments. The abort is asynchronous, and the future can be used to determine when the +* operation is complete. +* +* @return a CompletableFuture that will be completed when the abort is complete + */ + CompletableFuture abortAsync(); + +/** +* Commits the transaction. +* +*

This method is a synchronous version of {@link #commitAsync()}. It will block until the +* commit is complete. +* +* @throws ExecutionException if the commit fails +* @throws InterruptedException if the thread is interrupted while waiting for the commit to +* complete +*/ +void commit() throws ExecutionException, InterruptedException; + +/** +* Aborts the transaction. +* +*

This method is a synchronous version of {@link #abortAsync()}. It will block until the abort +* is complete. +* +* @throws ExecutionException if the abort fails +* @throws InterruptedException if the thread is interrupted while waiting for the abort to +* complete +*/ +void abort() throws ExecutionException, InterruptedException; + +/** +* Gets the transaction ID. +* +*

This method returns the unique identifier for the transaction. +* +* @return the transaction ID + */ + TxnID getTxnID(); + +/** +* Gets the current state of the transaction. +* +*

This method returns the current state of the transaction, which can be used to determine if +* the transaction is open, committed, aborted, error or timeout. +* +* @return the current state of the transaction + */ + org.apache.pulsar.client.api.transaction.Transaction.State getState(); + } +``` + +# Get started +## Quick Start +```java +public void transactionDemo() throws Exception { +String pubTopic = "persistent://public/default/my-pub-topic"; +String subTopic = "persistent://public/default/my-sub-topic"; +String subscription = "my-subscription"; + +// Create a Pulsar client instance +PulsarClient client = SingletonPulsarContainer.createPulsarClient(); + +// Create a Transaction object +// Use TransactionFactory to create a transaction object with a timeout of 5 seconds +Transaction transaction = +TransactionFactory.createTransaction(client, 5, TimeUnit.SECONDS).get(); + +// Create producers and a consumer +// Create two producers to send messages to different topics +Producer producerToPubTopic = client.newProducer(Schema.STRING).topic(pubTopic).create(); +Producer producerToSubTopic = client.newProducer(Schema.STRING).topic(subTopic).create(); + +// Create a consumer to receive messages from the subTopic +Consumer consumerFromSubTopic = client +.newConsumer(Schema.STRING) +.subscriptionName(subscription) +.topic(subTopic) +.subscribe(); + +// Send a message to the Sub Topic +producerToSubTopic.send("Hello World"); + +// Receive a message +Message receivedMessage = consumerFromSubTopic.receive(); +MessageId receivedMessageId = receivedMessage.getMessageId(); + +// Record the message in the transaction +transaction.recordMsg(receivedMessageId, consumerFromSubTopic); + +// Forward the transaction message to the pub topic +// Use the transaction message builder to forward the received message to the pubTopic +transaction.newTransactionMessage(producerToSubTopic).value(receivedMessage.getValue()).send(); + +// Acknowledge all received messages +// Acknowledge all messages received from the subTopic within the transaction +transaction.ackAllReceivedMsgs(consumerFromSubTopic); + +// Commit the transaction +// Commit the transaction to ensure all recorded messages and acknowledgments take effect +transaction.commit(); + +// Close the consumer, producers, and client to release resources +consumerFromSubTopic.close(); +producerToSubTopic.close(); +client.close(); +} +``` \ No newline at end of file diff --git a/pcip/static/img/pcip-3/image.png b/pcip/static/img/pcip-3/image.png new file mode 100644 index 0000000000000000000000000000000000000000..f64ccede536534c13b08dab753ef98aaa1f97d91 GIT binary patch literal 147332 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˯n5745H++E` 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

guu%3cs&&IiREo4{+wEy0X5Ba^;iOXzE= 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 P@b9sVl60}8fzST{`H}S} literal 0 HcmV?d00001 From 3ac637b4001ba705743d46474d39e2ccfb7deff0 Mon Sep 17 00:00:00 2001 From: xiangying Date: Sun, 15 Dec 2024 18:33:25 +0800 Subject: [PATCH 6/8] cumulative-ack-issue --- pcip/pcip-3.md | 3 ++- .../pcip-3/{image.png => cumulative-ack-issue.png} | Bin 2 files changed, 2 insertions(+), 1 deletion(-) rename pcip/static/img/pcip-3/{image.png => cumulative-ack-issue.png} (100%) diff --git a/pcip/pcip-3.md b/pcip/pcip-3.md index e0c7a66..da0466a 100644 --- a/pcip/pcip-3.md +++ b/pcip/pcip-3.md @@ -3,7 +3,8 @@ When users consume messages using the Fail-over subscription mode and confirm messages using cumulative Ack, duplicate consumption may occur. In this case, even if users use Transaction, they cannot achieve Just-Once. As shown in the figure below, in failover mode, the two consumer1 and consumer2 started simultaneously frequently undergo two disconnection switches. Finally, Consumer1 consumed M1, M2, M4, M5; Consumer2 consumed M1, M2, M3. M1 and M2 were consumed twice. -![image.png](static/img/pcip-3/image.png) + +![cumulative-ack-issue.png](static/img/pcip-3/cumulative-ack-issue.png) # Goals Solve the problem of cumulative ack consumption duplication by designing a new transaction API. diff --git a/pcip/static/img/pcip-3/image.png b/pcip/static/img/pcip-3/cumulative-ack-issue.png similarity index 100% rename from pcip/static/img/pcip-3/image.png rename to pcip/static/img/pcip-3/cumulative-ack-issue.png From 83906ae7e465b2747560bf1625d6d994e208de52 Mon Sep 17 00:00:00 2001 From: xiangying Date: Sun, 15 Dec 2024 18:45:36 +0800 Subject: [PATCH 7/8] fix test --- .../test/java/org/apache/pulsar/txn/TransactionImplTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pulsar-transaction-contrib/src/test/java/org/apache/pulsar/txn/TransactionImplTest.java b/pulsar-transaction-contrib/src/test/java/org/apache/pulsar/txn/TransactionImplTest.java index 0eb18bf..54e6165 100644 --- a/pulsar-transaction-contrib/src/test/java/org/apache/pulsar/txn/TransactionImplTest.java +++ b/pulsar-transaction-contrib/src/test/java/org/apache/pulsar/txn/TransactionImplTest.java @@ -31,6 +31,7 @@ import org.apache.pulsar.client.api.transaction.TxnID; import org.apache.pulsar.txn.impl.TransactionImpl; import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class TransactionImplTest { @@ -40,7 +41,7 @@ public class TransactionImplTest { private List> mockConsumers; private List messageIds; - @BeforeClass + @BeforeMethod public void setUp() { mockTransaction = mock(org.apache.pulsar.client.api.transaction.Transaction.class); mockConsumers = new ArrayList<>(); From f84f2281e4f3d6dbb062f306a7dd001c4cc0e3ec Mon Sep 17 00:00:00 2001 From: xiangying Date: Sun, 15 Dec 2024 18:48:34 +0800 Subject: [PATCH 8/8] optimize dependency --- pom.xml | 2 ++ pulsar-transaction-contrib/pom.xml | 10 ++-------- .../org/apache/pulsar/txn/TransactionImplTest.java | 1 - 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 749f363..e57a6e2 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,8 @@ 2.12.0 4.2.2 1.20.1 + 4.13.1 + 5.12.0 diff --git a/pulsar-transaction-contrib/pom.xml b/pulsar-transaction-contrib/pom.xml index 9f386b0..01b4501 100644 --- a/pulsar-transaction-contrib/pom.xml +++ b/pulsar-transaction-contrib/pom.xml @@ -28,7 +28,7 @@ junit junit - 4.13.1 + ${junit.version} test @@ -36,16 +36,10 @@ pulsar-client-all ${pulsar.version} - - org.junit.jupiter - junit-jupiter-api - 5.11.0 - test - org.mockito mockito-core - 5.12.0 + ${mockito.version} test diff --git a/pulsar-transaction-contrib/src/test/java/org/apache/pulsar/txn/TransactionImplTest.java b/pulsar-transaction-contrib/src/test/java/org/apache/pulsar/txn/TransactionImplTest.java index 54e6165..e4171a6 100644 --- a/pulsar-transaction-contrib/src/test/java/org/apache/pulsar/txn/TransactionImplTest.java +++ b/pulsar-transaction-contrib/src/test/java/org/apache/pulsar/txn/TransactionImplTest.java @@ -30,7 +30,6 @@ import org.apache.pulsar.client.api.MessageId; import org.apache.pulsar.client.api.transaction.TxnID; import org.apache.pulsar.txn.impl.TransactionImpl; -import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test;