@Test public void testInflightBatchesExpireOnDeliveryTimeout() throws InterruptedException { long deliveryTimeoutMs = 1500L; setupWithTransactionState(null, true, null); // Send first ProduceRequest Future<RecordMetadata> request = accumulator.append(tp0, time.milliseconds(), "key".getBytes(), "value".getBytes(), null, null, MAX_BLOCK_TIMEOUT).future; sender.run(time.milliseconds()); // send request assertEquals(1, client.inFlightRequestCount()); assertEquals("Expect one in-flight batch in accumulator", 1, sender.inFlightBatches(tp0).size()); Map<TopicPartition, ProduceResponse.PartitionResponse> responseMap = new HashMap<>(); responseMap.put(tp0, new ProduceResponse.PartitionResponse(Errors.NONE, 0L, 0L, 0L)); client.respond(new ProduceResponse(responseMap)); time.sleep(deliveryTimeoutMs); sender.run(time.milliseconds()); // receive first response assertEquals("Expect zero in-flight batch in accumulator", 0, sender.inFlightBatches(tp0).size()); try { request.get(); fail("The expired batch should throw a TimeoutException"); } catch (ExecutionException e) { assertTrue(e.getCause() instanceof TimeoutException); } }
assertEquals("Expect one in-flight batch in accumulator", 1, sender.inFlightBatches(tp0).size()); assertEquals("Expect zero in-flight batch in accumulator", 0, sender.inFlightBatches(tp0).size());
@Test public void testWhenFirstBatchExpireNoSendSecondBatchIfGuaranteeOrder() throws InterruptedException { long deliveryTimeoutMs = 1500L; setupWithTransactionState(null, true, null); // Send first ProduceRequest accumulator.append(tp0, time.milliseconds(), "key".getBytes(), "value".getBytes(), null, null, MAX_BLOCK_TIMEOUT); sender.run(time.milliseconds()); // send request assertEquals(1, client.inFlightRequestCount()); assertEquals(1, sender.inFlightBatches(tp0).size()); time.sleep(deliveryTimeoutMs / 2); // Send second ProduceRequest accumulator.append(tp0, time.milliseconds(), "key".getBytes(), "value".getBytes(), null, null, MAX_BLOCK_TIMEOUT); sender.run(time.milliseconds()); // must not send request because the partition is muted assertEquals(1, client.inFlightRequestCount()); assertEquals(1, sender.inFlightBatches(tp0).size()); time.sleep(deliveryTimeoutMs / 2); // expire the first batch only client.respond(produceResponse(tp0, 0L, Errors.NONE, 0, 0L)); sender.run(time.milliseconds()); // receive response (offset=0) assertEquals(0, client.inFlightRequestCount()); assertEquals(0, sender.inFlightBatches(tp0).size()); sender.run(time.milliseconds()); // Drain the second request only this time assertEquals(1, client.inFlightRequestCount()); assertEquals(1, sender.inFlightBatches(tp0).size()); }
@Test public void testExpiredBatchDoesNotSplitOnMessageTooLargeError() throws Exception { long deliverTimeoutMs = 1500L; // create a producer batch with more than one record so it is eligible to split Future<RecordMetadata> request1 = accumulator.append(tp0, time.milliseconds(), "key1".getBytes(), "value1".getBytes(), null, null, MAX_BLOCK_TIMEOUT).future; Future<RecordMetadata> request2 = accumulator.append(tp0, time.milliseconds(), "key2".getBytes(), "value2".getBytes(), null, null, MAX_BLOCK_TIMEOUT).future; sender.run(time.milliseconds()); // send request assertEquals(1, client.inFlightRequestCount()); // return a MESSAGE_TOO_LARGE error client.respond(produceResponse(tp0, -1, Errors.MESSAGE_TOO_LARGE, -1)); time.sleep(deliverTimeoutMs); // expire the batch and process the response sender.run(time.milliseconds()); assertTrue(request1.isDone()); assertTrue(request2.isDone()); assertEquals(0, client.inFlightRequestCount()); assertEquals(0, sender.inFlightBatches(tp0).size()); // run again and must not split big batch and resend anything. sender.run(time.milliseconds()); assertEquals(0, client.inFlightRequestCount()); assertEquals(0, sender.inFlightBatches(tp0).size()); }
@Test public void testExpiredBatchDoesNotRetry() throws Exception { long deliverTimeoutMs = 1500L; setupWithTransactionState(null, false, null); // Send first ProduceRequest Future<RecordMetadata> request1 = accumulator.append(tp0, time.milliseconds(), "key".getBytes(), "value".getBytes(), null, null, MAX_BLOCK_TIMEOUT).future; sender.run(time.milliseconds()); // send request assertEquals(1, client.inFlightRequestCount()); time.sleep(deliverTimeoutMs); Map<TopicPartition, ProduceResponse.PartitionResponse> responseMap = new HashMap<>(); responseMap.put(tp0, new ProduceResponse.PartitionResponse(Errors.NONE, 0L, 0L, 0L)); client.respond(produceResponse(tp0, -1, Errors.NOT_LEADER_FOR_PARTITION, -1)); // return a retriable error sender.run(time.milliseconds()); // expire the batch assertTrue(request1.isDone()); assertEquals(0, client.inFlightRequestCount()); assertEquals(0, sender.inFlightBatches(tp0).size()); sender.run(time.milliseconds()); // receive first response and do not reenqueue. assertEquals(0, client.inFlightRequestCount()); assertEquals(0, sender.inFlightBatches(tp0).size()); sender.run(time.milliseconds()); // run again and must not send anything. assertEquals(0, client.inFlightRequestCount()); assertEquals(0, sender.inFlightBatches(tp0).size()); }
@Test public void testCanRetryWithoutIdempotence() throws Exception { // do a successful retry Future<RecordMetadata> future = accumulator.append(tp0, 0L, "key".getBytes(), "value".getBytes(), null, null, MAX_BLOCK_TIMEOUT).future; sender.run(time.milliseconds()); // connect sender.run(time.milliseconds()); // send produce request String id = client.requests().peek().destination(); Node node = new Node(Integer.parseInt(id), "localhost", 0); assertEquals(1, client.inFlightRequestCount()); assertTrue(client.hasInFlightRequests()); assertEquals(1, sender.inFlightBatches(tp0).size()); assertTrue("Client ready status should be true", client.isReady(node, 0L)); assertFalse(future.isDone()); client.respond(new MockClient.RequestMatcher() { @Override public boolean matches(AbstractRequest body) { ProduceRequest request = (ProduceRequest) body; assertFalse(request.hasIdempotentRecords()); return true; } }, produceResponse(tp0, -1L, Errors.TOPIC_AUTHORIZATION_FAILED, 0)); sender.run(time.milliseconds()); assertTrue(future.isDone()); try { future.get(); } catch (Exception e) { assertTrue(e.getCause() instanceof TopicAuthorizationException); } }
@Test public void testSimple() throws Exception { long offset = 0; Future<RecordMetadata> future = accumulator.append(tp0, 0L, "key".getBytes(), "value".getBytes(), null, null, MAX_BLOCK_TIMEOUT).future; sender.run(time.milliseconds()); // connect sender.run(time.milliseconds()); // send produce request assertEquals("We should have a single produce request in flight.", 1, client.inFlightRequestCount()); assertEquals(1, sender.inFlightBatches(tp0).size()); assertTrue(client.hasInFlightRequests()); client.respond(produceResponse(tp0, offset, Errors.NONE, 0)); sender.run(time.milliseconds()); assertEquals("All requests completed.", 0, client.inFlightRequestCount()); assertEquals(0, sender.inFlightBatches(tp0).size()); assertFalse(client.hasInFlightRequests()); sender.run(time.milliseconds()); assertTrue("Request should be completed", future.isDone()); assertEquals(offset, future.get().offset()); }
assertEquals(1, client.inFlightRequestCount()); assertTrue(client.hasInFlightRequests()); assertEquals(1, sender.inFlightBatches(tp0).size()); assertTrue("Client ready status should be true", client.isReady(node, 0L)); client.disconnect(id); assertEquals(1, sender.inFlightBatches(tp0).size()); assertEquals(1, sender.inFlightBatches(tp0).size()); long offset = 0; client.respond(produceResponse(tp0, offset, Errors.NONE, 0)); assertTrue("Request should have retried and completed", future.isDone()); assertEquals(offset, future.get().offset()); assertEquals(0, sender.inFlightBatches(tp0).size()); assertEquals(1, sender.inFlightBatches(tp0).size()); for (int i = 0; i < maxRetries + 1; i++) { client.disconnect(client.requests().peek().destination()); assertEquals(0, sender.inFlightBatches(tp0).size()); assertEquals(i > 0 ? 0 : 1, sender.inFlightBatches(tp0).size()); assertEquals(0, sender.inFlightBatches(tp0).size()); } finally { m.close();
assertTrue(client.hasInFlightRequests()); assertTrue("Client ready status should be true", client.isReady(node, 0L)); assertEquals(1, sender.inFlightBatches(tp2).size()); client.prepareMetadataUpdate(metadataUpdate2); assertEquals(1, sender.inFlightBatches(tp2).size()); assertEquals(1, sender.inFlightBatches(tp2).size()); } finally { m.close();
@Test public void testResetWhenOutOfOrderSequenceReceived() throws InterruptedException { final long producerId = 343434L; TransactionManager transactionManager = new TransactionManager(); transactionManager.setProducerIdAndEpoch(new ProducerIdAndEpoch(producerId, (short) 0)); setupWithTransactionState(transactionManager); int maxRetries = 10; Metrics m = new Metrics(); SenderMetricsRegistry senderMetrics = new SenderMetricsRegistry(m); Sender sender = new Sender(logContext, client, metadata, this.accumulator, true, MAX_REQUEST_SIZE, ACKS_ALL, maxRetries, senderMetrics, time, REQUEST_TIMEOUT, 50, transactionManager, apiVersions); Future<RecordMetadata> responseFuture = accumulator.append(tp0, time.milliseconds(), "key".getBytes(), "value".getBytes(), null, null, MAX_BLOCK_TIMEOUT).future; sender.run(time.milliseconds()); // connect. sender.run(time.milliseconds()); // send. assertEquals(1, client.inFlightRequestCount()); assertEquals(1, sender.inFlightBatches(tp0).size()); client.respond(produceResponse(tp0, 0, Errors.OUT_OF_ORDER_SEQUENCE_NUMBER, 0)); sender.run(time.milliseconds()); assertTrue(responseFuture.isDone()); assertEquals(0, sender.inFlightBatches(tp0).size()); assertFalse("Expected transaction state to be reset upon receiving an OutOfOrderSequenceException", transactionManager.hasProducerId()); }
assertEquals(2, sender.inFlightBatches(tp0).size()); assertEquals(1, sender.inFlightBatches(tp0).size()); assertEquals(0, sender.inFlightBatches(tp0).size()); assertEquals(1, sender.inFlightBatches(tp0).size()); assertEquals(0, sender.inFlightBatches(tp0).size()); assertEquals(1, client.inFlightRequestCount()); assertFalse(request3.isDone()); assertEquals(1, sender.inFlightBatches(tp0).size());
@Test public void testNoDoubleDeallocation() throws Exception { long deliverTimeoutMs = 1500L; long totalSize = 1024 * 1024; String metricGrpName = "producer-custom-metrics"; MatchingBufferPool pool = new MatchingBufferPool(totalSize, batchSize, metrics, time, metricGrpName); setupWithTransactionState(null, false, pool); // Send first ProduceRequest Future<RecordMetadata> request1 = accumulator.append(tp0, time.milliseconds(), "key".getBytes(), "value".getBytes(), null, null, MAX_BLOCK_TIMEOUT).future; sender.run(time.milliseconds()); // send request assertEquals(1, client.inFlightRequestCount()); assertEquals(1, sender.inFlightBatches(tp0).size()); time.sleep(deliverTimeoutMs); assertFalse(pool.allMatch()); sender.run(time.milliseconds()); // expire the batch assertTrue(request1.isDone()); assertTrue("The batch should have been de-allocated", pool.allMatch()); assertTrue(pool.allMatch()); sender.run(time.milliseconds()); assertTrue("The batch should have been de-allocated", pool.allMatch()); assertEquals(0, client.inFlightRequestCount()); assertEquals(0, sender.inFlightBatches(tp0).size()); }
assertEquals(0, sender.inFlightBatches(tp0).size()); assertTrue(request2.isDone()); assertEquals(1, request2.get().offset());
assertEquals("Request completed.", 0, client.inFlightRequestCount()); assertFalse(client.hasInFlightRequests()); assertEquals(0, sender.inFlightBatches(tp0).size()); sender.run(time.milliseconds()); assertTrue("Request should be completed", future.isDone()); assertEquals("Request completed.", 0, client.inFlightRequestCount()); assertFalse(client.hasInFlightRequests()); assertEquals(0, sender.inFlightBatches(tp0).size()); sender.run(time.milliseconds()); assertTrue("Request should be completed", future.isDone());
assertEquals(0, request1.get().offset()); assertFalse(client.hasInFlightRequests()); assertEquals(0, sender.inFlightBatches(tp0).size()); assertEquals(1, sender.inFlightBatches(tp0).size()); assertEquals(0, sender.inFlightBatches(tp0).size()); assertEquals(1, sender.inFlightBatches(tp0).size()); assertEquals(2, request3.get().offset()); assertEquals(1, client.inFlightRequestCount()); assertEquals(1, sender.inFlightBatches(tp0).size());