synchronized boolean shouldResetProducerStateAfterResolvingSequences() { if (isTransactional()) // We should not reset producer state if we are transactional. We will transition to a fatal error instead. return false; for (Iterator<TopicPartition> iter = partitionsWithUnresolvedSequences.iterator(); iter.hasNext(); ) { TopicPartition topicPartition = iter.next(); if (!hasInflightBatches(topicPartition)) { // The partition has been fully drained. At this point, the last ack'd sequence should be once less than // next sequence destined for the partition. If so, the partition is fully resolved. If not, we should // reset the sequence number if necessary. if (isNextSequence(topicPartition, sequenceNumber(topicPartition))) { // This would happen when a batch was expired, but subsequent batches succeeded. iter.remove(); } else { // We would enter this branch if all in flight batches were ultimately expired in the producer. log.info("No inflight batches remaining for {}, last ack'd sequence for partition is {}, next sequence is {}. " + "Going to reset producer state.", topicPartition, lastAckedSequence(topicPartition), sequenceNumber(topicPartition)); return true; } } } return false; }
synchronized void adjustSequencesDueToFailedBatch(ProducerBatch batch) { if (!this.nextSequence.containsKey(batch.topicPartition)) // Sequence numbers are not being tracked for this partition. This could happen if the producer id was just // reset due to a previous OutOfOrderSequenceException. return; log.debug("producerId: {}, send to partition {} failed fatally. Reducing future sequence numbers by {}", batch.producerId(), batch.topicPartition, batch.recordCount); int currentSequence = sequenceNumber(batch.topicPartition); currentSequence -= batch.recordCount; if (currentSequence < 0) throw new IllegalStateException("Sequence number for partition " + batch.topicPartition + " is going to become negative : " + currentSequence); setNextSequence(batch.topicPartition, currentSequence); for (ProducerBatch inFlightBatch : inflightBatchesBySequence.get(batch.topicPartition)) { if (inFlightBatch.baseSequence() < batch.baseSequence()) continue; int newSequence = inFlightBatch.baseSequence() - batch.recordCount; if (newSequence < 0) throw new IllegalStateException("Sequence number for batch with sequence " + inFlightBatch.baseSequence() + " for partition " + batch.topicPartition + " is going to become negative :" + newSequence); log.info("Resetting sequence number of batch with current sequence {} for partition {} to {}", inFlightBatch.baseSequence(), batch.topicPartition, newSequence); inFlightBatch.resetProducerState(new ProducerIdAndEpoch(inFlightBatch.producerId(), inFlightBatch.producerEpoch()), newSequence, inFlightBatch.isTransactional()); } }
@Test public void testSequenceNumberOverflow() { TransactionManager transactionManager = new TransactionManager(); assertEquals((int) transactionManager.sequenceNumber(tp0), 0); transactionManager.incrementSequenceNumber(tp0, Integer.MAX_VALUE); assertEquals((int) transactionManager.sequenceNumber(tp0), Integer.MAX_VALUE); transactionManager.incrementSequenceNumber(tp0, 100); assertEquals((int) transactionManager.sequenceNumber(tp0), 99); transactionManager.incrementSequenceNumber(tp0, Integer.MAX_VALUE); assertEquals((int) transactionManager.sequenceNumber(tp0), 98); }
@Test public void testProducerIdReset() { TransactionManager transactionManager = new TransactionManager(); assertEquals((int) transactionManager.sequenceNumber(tp0), 0); transactionManager.incrementSequenceNumber(tp0, 3); assertEquals((int) transactionManager.sequenceNumber(tp0), 3); transactionManager.resetProducerId(); assertEquals((int) transactionManager.sequenceNumber(tp0), 0); }
@Test public void testDefaultSequenceNumber() { TransactionManager transactionManager = new TransactionManager(); assertEquals((int) transactionManager.sequenceNumber(tp0), 0); transactionManager.incrementSequenceNumber(tp0, 3); assertEquals((int) transactionManager.sequenceNumber(tp0), 3); }
@Test public void testShouldResetProducerStateAfterResolvingSequences() { // Create a TransactionManager without a transactionalId to test // shouldResetProducerStateAfterResolvingSequences. TransactionManager manager = new TransactionManager(logContext, null, transactionTimeoutMs, DEFAULT_RETRY_BACKOFF_MS); assertFalse(manager.shouldResetProducerStateAfterResolvingSequences()); TopicPartition tp0 = new TopicPartition("foo", 0); TopicPartition tp1 = new TopicPartition("foo", 1); assertEquals(Integer.valueOf(0), manager.sequenceNumber(tp0)); assertEquals(Integer.valueOf(0), manager.sequenceNumber(tp1)); manager.incrementSequenceNumber(tp0, 1); manager.incrementSequenceNumber(tp1, 1); manager.maybeUpdateLastAckedSequence(tp0, 0); manager.maybeUpdateLastAckedSequence(tp1, 0); manager.markSequenceUnresolved(tp0); manager.markSequenceUnresolved(tp1); assertFalse(manager.shouldResetProducerStateAfterResolvingSequences()); manager.maybeUpdateLastAckedSequence(tp0, 5); manager.incrementSequenceNumber(tp0, 1); manager.markSequenceUnresolved(tp0); manager.markSequenceUnresolved(tp1); assertTrue(manager.shouldResetProducerStateAfterResolvingSequences()); }
assertTrue(transactionManager.hasProducerId()); assertEquals(0, transactionManager.sequenceNumber(tp0).longValue()); assertEquals(1, transactionManager.sequenceNumber(tp0).longValue()); assertEquals(-1, transactionManager.lastAckedSequence(tp0)); assertEquals(2, transactionManager.sequenceNumber(tp0).longValue()); assertEquals(0, transactionManager.lastAckedSequence(tp0));
@Test public void testExpiryOfUnsentBatchesShouldNotCauseUnresolvedSequences() throws Exception { final long producerId = 343434L; TransactionManager transactionManager = new TransactionManager(); setupWithTransactionState(transactionManager); prepareAndReceiveInitProducerId(producerId, Errors.NONE); assertTrue(transactionManager.hasProducerId()); assertEquals(0, transactionManager.sequenceNumber(tp0).longValue()); // Send first ProduceRequest Future<RecordMetadata> request1 = accumulator.append(tp0, 0L, "key".getBytes(), "value".getBytes(), null, null, MAX_BLOCK_TIMEOUT).future; Node node = metadata.fetch().nodes().get(0); time.sleep(10000L); client.disconnect(node.idString()); client.blackout(node, 10); sender.run(time.milliseconds()); assertFutureFailure(request1, TimeoutException.class); assertFalse(transactionManager.hasUnresolvedSequence(tp0)); }
assertTrue(transactionManager.hasProducerId()); assertEquals(0, transactionManager.sequenceNumber(tp0).longValue()); assertEquals(2, transactionManager.sequenceNumber(tp0).longValue()); assertEquals(-1, transactionManager.lastAckedSequence(tp0)); sendIdempotentProducerResponse(0, tp0, Errors.NONE, 0); sender.run(time.milliseconds()); assertEquals(1, client.inFlightRequestCount()); assertEquals(3, transactionManager.sequenceNumber(tp0).longValue()); assertEquals(1, transactionManager.lastAckedSequence(tp0)); assertTrue(request1.isDone());
assertTrue(transactionManager.hasProducerId()); assertEquals(0, transactionManager.sequenceNumber(tp0).longValue()); assertEquals(1, transactionManager.sequenceNumber(tp0).longValue()); assertEquals(-1, transactionManager.lastAckedSequence(tp0)); Future<RecordMetadata> request2 = accumulator.append(tp0, time.milliseconds(), "key".getBytes(), "value".getBytes(), null, null, MAX_BLOCK_TIMEOUT).future; sender.run(time.milliseconds()); assertEquals(3, transactionManager.sequenceNumber(tp0).longValue()); assertEquals(0, transactionManager.lastAckedSequence(tp0)); assertEquals(2, transactionManager.sequenceNumber(tp0).longValue()); assertFalse(request2.isDone()); assertFalse(client.hasInFlightRequests()); assertEquals(2, transactionManager.sequenceNumber(tp0).longValue()); assertFalse(client.hasInFlightRequests()); assertTrue(request2.isDone());
assertTrue(transactionManager.hasProducerId()); assertEquals(0, transactionManager.sequenceNumber(tp0).longValue()); assertEquals(1, transactionManager.sequenceNumber(tp0).longValue()); assertEquals(-1, transactionManager.lastAckedSequence(tp0)); assertEquals(2, transactionManager.sequenceNumber(tp0).longValue()); assertEquals(0, transactionManager.lastAckedSequence(tp0)); assertEquals(2, transactionManager.sequenceNumber(tp0).longValue()); assertFalse(request2.isDone()); assertFalse(client.hasInFlightRequests()); assertEquals(2, transactionManager.sequenceNumber(tp0).longValue()); assertFalse(client.hasInFlightRequests()); assertTrue(request2.isDone());
assertTrue(transactionManager.hasProducerId()); assertEquals(0, transactionManager.sequenceNumber(tp0).longValue()); Node node = new Node(Integer.valueOf(nodeId), "localhost", 0); assertEquals(1, client.inFlightRequestCount()); assertEquals(1, transactionManager.sequenceNumber(tp0).longValue()); assertEquals(-1, transactionManager.lastAckedSequence(tp0)); sender.run(time.milliseconds()); assertEquals(2, client.inFlightRequestCount()); assertEquals(2, transactionManager.sequenceNumber(tp0).longValue()); assertEquals(-1, transactionManager.lastAckedSequence(tp0)); assertFalse(request1.isDone());
@Test public void testExpiryOfAllSentBatchesShouldCauseUnresolvedSequences() throws Exception { final long producerId = 343434L; TransactionManager transactionManager = new TransactionManager(); setupWithTransactionState(transactionManager); prepareAndReceiveInitProducerId(producerId, Errors.NONE); assertTrue(transactionManager.hasProducerId()); assertEquals(0, transactionManager.sequenceNumber(tp0).longValue()); // Send first ProduceRequest Future<RecordMetadata> request1 = accumulator.append(tp0, 0L, "key".getBytes(), "value".getBytes(), null, null, MAX_BLOCK_TIMEOUT).future; sender.run(time.milliseconds()); // send request sendIdempotentProducerResponse(0, tp0, Errors.NOT_LEADER_FOR_PARTITION, -1); sender.run(time.milliseconds()); // receive response assertEquals(1L, transactionManager.sequenceNumber(tp0).longValue()); Node node = metadata.fetch().nodes().get(0); time.sleep(15000L); client.disconnect(node.idString()); client.blackout(node, 10); sender.run(time.milliseconds()); // now expire the batch. assertFutureFailure(request1, TimeoutException.class); assertTrue(transactionManager.hasUnresolvedSequence(tp0)); assertFalse(client.hasInFlightRequests()); Deque<ProducerBatch> batches = accumulator.batches().get(tp0); assertEquals(0, batches.size()); assertTrue(transactionManager.hasProducerId(producerId)); // We should now clear the old producerId and get a new one in a single run loop. prepareAndReceiveInitProducerId(producerId + 1, Errors.NONE); assertTrue(transactionManager.hasProducerId(producerId + 1)); }
assertEquals(0, transactionManager.sequenceNumber(tp1).longValue());
assertTrue(transactionManager.hasProducerId()); assertEquals(0, transactionManager.sequenceNumber(tp0).longValue()); Node node = new Node(Integer.valueOf(nodeId), "localhost", 0); assertEquals(1, client.inFlightRequestCount()); assertEquals(1, transactionManager.sequenceNumber(tp0).longValue()); assertEquals(-1, transactionManager.lastAckedSequence(tp0)); sender.run(time.milliseconds()); assertEquals(2, client.inFlightRequestCount()); assertEquals(2, transactionManager.sequenceNumber(tp0).longValue()); assertEquals(-1, transactionManager.lastAckedSequence(tp0)); assertFalse(request1.isDone());
assertTrue(transactionManager.hasProducerId()); assertEquals(0, transactionManager.sequenceNumber(tp0).longValue()); Node node = new Node(Integer.valueOf(nodeId), "localhost", 0); assertEquals(1, client.inFlightRequestCount()); assertEquals(1, transactionManager.sequenceNumber(tp0).longValue()); assertEquals(-1, transactionManager.lastAckedSequence(tp0)); sender.run(time.milliseconds()); assertEquals(2, client.inFlightRequestCount()); assertEquals(2, transactionManager.sequenceNumber(tp0).longValue()); assertEquals(-1, transactionManager.lastAckedSequence(tp0)); assertFalse(request1.isDone());
prepareAndReceiveInitProducerId(producerId, Errors.NONE); assertTrue(transactionManager.hasProducerId()); assertEquals(0, transactionManager.sequenceNumber(tp0).longValue());
assertTrue(transactionManager.hasProducerId()); assertEquals(0, transactionManager.sequenceNumber(tp0).longValue());
assertEquals(1L, (long) transactionManager.sequenceNumber(tp0));
assertEquals(0, (long) transactionManager.sequenceNumber(tp0));