/** * Should be called before the start of each new transaction. Note that prior to the first invocation * of this method, you must invoke {@link #initTransactions()} exactly one time. * * @throws IllegalStateException if no transactional.id has been configured or if {@link #initTransactions()} * has not yet been invoked * @throws ProducerFencedException if another producer with the same transactional.id is active * @throws org.apache.kafka.common.errors.UnsupportedVersionException fatal error indicating the broker * does not support transactions (i.e. if its version is lower than 0.11.0.0) * @throws org.apache.kafka.common.errors.AuthorizationException fatal error indicating that the configured * transactional.id is not authorized. See the exception for more details * @throws KafkaException if the producer has encountered a previous fatal error or for any other unexpected error */ public void beginTransaction() throws ProducerFencedException { throwIfNoTransactionManager(); transactionManager.beginTransaction(); }
@Test(expected = KafkaException.class) public void testMaybeAddPartitionToTransactionAfterAbortableError() { long pid = 13131L; short epoch = 1; doInitTransactions(pid, epoch); transactionManager.beginTransaction(); transactionManager.transitionToAbortableError(new KafkaException()); transactionManager.maybeAddPartitionToTransaction(new TopicPartition("foo", 0)); }
@Test public void testIsSendToPartitionAllowedWithPartitionNotAdded() { final long pid = 13131L; final short epoch = 1; doInitTransactions(pid, epoch); transactionManager.beginTransaction(); assertFalse(transactionManager.isSendToPartitionAllowed(tp0)); }
@Test(expected = KafkaException.class) public void testFailIfNotReadyForSendAfterAbortableError() { long pid = 13131L; short epoch = 1; doInitTransactions(pid, epoch); transactionManager.beginTransaction(); transactionManager.transitionToAbortableError(new KafkaException()); transactionManager.failIfNotReadyForSend(); }
private void verifyAddPartitionsFailsWithPartitionLevelError(final Errors error) throws InterruptedException { final long pid = 1L; final short epoch = 1; doInitTransactions(pid, epoch); transactionManager.beginTransaction(); transactionManager.maybeAddPartitionToTransaction(tp0); Future<RecordMetadata> responseFuture = accumulator.append(tp0, time.milliseconds(), "key".getBytes(), "value".getBytes(), Record.EMPTY_HEADERS, null, MAX_BLOCK_TIMEOUT).future; assertFalse(responseFuture.isDone()); prepareAddPartitionsToTxn(tp0, error); sender.run(time.milliseconds()); // attempt send addPartitions. assertTrue(transactionManager.hasError()); assertFalse(transactionManager.transactionContainsPartition(tp0)); }
@Test(expected = ExecutionException.class) public void testProducerFencedException() throws InterruptedException, ExecutionException { final long pid = 13131L; final short epoch = 1; doInitTransactions(pid, epoch); transactionManager.beginTransaction(); transactionManager.maybeAddPartitionToTransaction(tp0); Future<RecordMetadata> responseFuture = accumulator.append(tp0, time.milliseconds(), "key".getBytes(), "value".getBytes(), Record.EMPTY_HEADERS, null, MAX_BLOCK_TIMEOUT).future; assertFalse(responseFuture.isDone()); prepareAddPartitionsToTxnResponse(Errors.NONE, tp0, epoch, pid); prepareProduceResponse(Errors.INVALID_PRODUCER_EPOCH, pid, epoch); sender.run(time.milliseconds()); // Add partitions. sender.run(time.milliseconds()); // send produce. assertTrue(responseFuture.isDone()); assertTrue(transactionManager.hasError()); responseFuture.get(); }
@Test public void testTransactionalSplitBatchAndSend() throws Exception { ProducerIdAndEpoch producerIdAndEpoch = new ProducerIdAndEpoch(123456L, (short) 0); TopicPartition tp = new TopicPartition("testSplitBatchAndSend", 1); TransactionManager txnManager = new TransactionManager(logContext, "testSplitBatchAndSend", 60000, 100); setupWithTransactionState(txnManager); doInitTransactions(txnManager, producerIdAndEpoch); txnManager.beginTransaction(); txnManager.maybeAddPartitionToTransaction(tp); client.prepareResponse(new AddPartitionsToTxnResponse(0, Collections.singletonMap(tp, Errors.NONE))); sender.run(time.milliseconds()); testSplitBatchAndSend(txnManager, producerIdAndEpoch, tp); }
@Test public void testSenderShutdownWithPendingAddPartitions() throws Exception { long pid = 13131L; short epoch = 1; doInitTransactions(pid, epoch); transactionManager.beginTransaction(); transactionManager.maybeAddPartitionToTransaction(tp0); FutureRecordMetadata sendFuture = accumulator.append(tp0, time.milliseconds(), "key".getBytes(), "value".getBytes(), Record.EMPTY_HEADERS, null, MAX_BLOCK_TIMEOUT).future; prepareAddPartitionsToTxn(tp0, Errors.NONE); prepareProduceResponse(Errors.NONE, pid, epoch); sender.initiateClose(); sender.run(); assertTrue(sendFuture.isDone()); }
@Test public void testTransactionalIdAuthorizationFailureInAddPartitions() { final long pid = 13131L; final short epoch = 1; final TopicPartition tp = new TopicPartition("foo", 0); doInitTransactions(pid, epoch); transactionManager.beginTransaction(); transactionManager.maybeAddPartitionToTransaction(tp); prepareAddPartitionsToTxn(tp, Errors.TRANSACTIONAL_ID_AUTHORIZATION_FAILED); sender.run(time.milliseconds()); assertTrue(transactionManager.hasError()); assertTrue(transactionManager.lastError() instanceof TransactionalIdAuthorizationException); assertFatalError(TransactionalIdAuthorizationException.class); }
@Test public void testIsSendToPartitionAllowedWithPendingPartitionAfterFatalError() { final long pid = 13131L; final short epoch = 1; doInitTransactions(pid, epoch); transactionManager.beginTransaction(); transactionManager.maybeAddPartitionToTransaction(tp0); transactionManager.transitionToFatalError(new KafkaException()); assertFalse(transactionManager.isSendToPartitionAllowed(tp0)); assertTrue(transactionManager.hasFatalError()); }
@Test public void testIsSendToPartitionAllowedWithPendingPartitionAfterAbortableError() { final long pid = 13131L; final short epoch = 1; doInitTransactions(pid, epoch); transactionManager.beginTransaction(); transactionManager.maybeAddPartitionToTransaction(tp0); transactionManager.transitionToAbortableError(new KafkaException()); assertFalse(transactionManager.isSendToPartitionAllowed(tp0)); assertTrue(transactionManager.hasAbortableError()); }
@Test public void testIsSendToPartitionAllowedWithInFlightPartitionAddAfterFatalError() { final long pid = 13131L; final short epoch = 1; doInitTransactions(pid, epoch); transactionManager.beginTransaction(); transactionManager.maybeAddPartitionToTransaction(tp0); // Send the AddPartitionsToTxn request and leave it in-flight sender.run(time.milliseconds()); transactionManager.transitionToFatalError(new KafkaException()); assertFalse(transactionManager.isSendToPartitionAllowed(tp0)); assertTrue(transactionManager.hasFatalError()); }
@Test public void testIsSendToPartitionAllowedWithInFlightPartitionAddAfterAbortableError() { final long pid = 13131L; final short epoch = 1; doInitTransactions(pid, epoch); transactionManager.beginTransaction(); transactionManager.maybeAddPartitionToTransaction(tp0); // Send the AddPartitionsToTxn request and leave it in-flight sender.run(time.milliseconds()); transactionManager.transitionToAbortableError(new KafkaException()); assertFalse(transactionManager.isSendToPartitionAllowed(tp0)); assertTrue(transactionManager.hasAbortableError()); }
@Test public void testAddPartitionToTransactionOverridesRetryBackoffForConcurrentTransactions() { long pid = 13131L; short epoch = 1; TopicPartition partition = new TopicPartition("foo", 0); doInitTransactions(pid, epoch); transactionManager.beginTransaction(); transactionManager.maybeAddPartitionToTransaction(partition); assertTrue(transactionManager.hasPartitionsToAdd()); assertFalse(transactionManager.isPartitionAdded(partition)); assertTrue(transactionManager.isPartitionPendingAdd(partition)); prepareAddPartitionsToTxn(partition, Errors.CONCURRENT_TRANSACTIONS); sender.run(time.milliseconds()); TransactionManager.TxnRequestHandler handler = transactionManager.nextRequestHandler(false); assertNotNull(handler); assertEquals(20, handler.retryBackoffMs()); }
@Test public void testAddPartitionToTransactionRetainsRetryBackoffForRegularRetriableError() { long pid = 13131L; short epoch = 1; TopicPartition partition = new TopicPartition("foo", 0); doInitTransactions(pid, epoch); transactionManager.beginTransaction(); transactionManager.maybeAddPartitionToTransaction(partition); assertTrue(transactionManager.hasPartitionsToAdd()); assertFalse(transactionManager.isPartitionAdded(partition)); assertTrue(transactionManager.isPartitionPendingAdd(partition)); prepareAddPartitionsToTxn(partition, Errors.COORDINATOR_NOT_AVAILABLE); sender.run(time.milliseconds()); TransactionManager.TxnRequestHandler handler = transactionManager.nextRequestHandler(false); assertNotNull(handler); assertEquals(DEFAULT_RETRY_BACKOFF_MS, handler.retryBackoffMs()); }
@Test public void testHasOngoingTransactionFatalError() { long pid = 13131L; short epoch = 1; TopicPartition partition = new TopicPartition("foo", 0); assertFalse(transactionManager.hasOngoingTransaction()); doInitTransactions(pid, epoch); assertFalse(transactionManager.hasOngoingTransaction()); transactionManager.beginTransaction(); assertTrue(transactionManager.hasOngoingTransaction()); transactionManager.maybeAddPartitionToTransaction(partition); assertTrue(transactionManager.hasOngoingTransaction()); prepareAddPartitionsToTxn(partition, Errors.NONE); sender.run(time.milliseconds()); transactionManager.transitionToFatalError(new KafkaException()); assertFalse(transactionManager.hasOngoingTransaction()); }
@Test public void testEndTxnNotSentIfIncompleteBatches() { long pid = 13131L; short epoch = 1; doInitTransactions(pid, epoch); transactionManager.beginTransaction(); transactionManager.maybeAddPartitionToTransaction(tp0); prepareAddPartitionsToTxn(tp0, Errors.NONE); sender.run(time.milliseconds()); assertTrue(transactionManager.isPartitionAdded(tp0)); transactionManager.beginCommit(); assertNull(transactionManager.nextRequestHandler(true)); assertTrue(transactionManager.nextRequestHandler(false).isEndTxn()); }
@Test public void testIsSendToPartitionAllowedWithAddedPartitionAfterAbortableError() { final long pid = 13131L; final short epoch = 1; doInitTransactions(pid, epoch); transactionManager.beginTransaction(); transactionManager.maybeAddPartitionToTransaction(tp0); prepareAddPartitionsToTxnResponse(Errors.NONE, tp0, epoch, pid); sender.run(time.milliseconds()); assertFalse(transactionManager.hasPartitionsToAdd()); transactionManager.transitionToAbortableError(new KafkaException()); assertTrue(transactionManager.isSendToPartitionAllowed(tp0)); assertTrue(transactionManager.hasAbortableError()); }
@Test public void testIsSendToPartitionAllowedWithAddedPartitionAfterFatalError() { final long pid = 13131L; final short epoch = 1; doInitTransactions(pid, epoch); transactionManager.beginTransaction(); transactionManager.maybeAddPartitionToTransaction(tp0); prepareAddPartitionsToTxnResponse(Errors.NONE, tp0, epoch, pid); sender.run(time.milliseconds()); assertFalse(transactionManager.hasPartitionsToAdd()); transactionManager.transitionToFatalError(new KafkaException()); assertFalse(transactionManager.isSendToPartitionAllowed(tp0)); assertTrue(transactionManager.hasFatalError()); }
@Test public void shouldNotSendAbortTxnRequestWhenOnlyAddPartitionsRequestFailed() { final long pid = 13131L; final short epoch = 1; doInitTransactions(pid, epoch); transactionManager.beginTransaction(); transactionManager.maybeAddPartitionToTransaction(tp0); prepareAddPartitionsToTxnResponse(Errors.TOPIC_AUTHORIZATION_FAILED, tp0, epoch, pid); sender.run(time.milliseconds()); // Send AddPartitionsRequest TransactionalRequestResult abortResult = transactionManager.beginAbort(); assertFalse(abortResult.isCompleted()); sender.run(time.milliseconds()); assertTrue(abortResult.isCompleted()); assertTrue(abortResult.isSuccessful()); }