/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.kafka.clients.consumer.internals; import org.apache.kafka.clients.ClientResponse; import org.apache.kafka.clients.Metadata; import org.apache.kafka.clients.MockClient; import org.apache.kafka.clients.NetworkClient; import org.apache.kafka.common.Cluster; import org.apache.kafka.common.Node; import org.apache.kafka.common.errors.WakeupException; import org.apache.kafka.common.protocol.Errors; import org.apache.kafka.common.requests.HeartbeatRequest; import org.apache.kafka.common.requests.HeartbeatResponse; import org.apache.kafka.common.utils.MockTime; import org.apache.kafka.test.TestUtils; import org.easymock.EasyMock; import org.junit.Test; import java.util.Collections; import java.util.concurrent.atomic.AtomicBoolean; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class ConsumerNetworkClientTest { private String topicName = "test"; private MockTime time = new MockTime(1); private MockClient client = new MockClient(time); private Cluster cluster = TestUtils.singletonCluster(topicName, 1); private Node node = cluster.nodes().get(0); private Metadata metadata = new Metadata(0, Long.MAX_VALUE); private ConsumerNetworkClient consumerClient = new ConsumerNetworkClient(client, metadata, time, 100, 1000); @Test public void send() { client.prepareResponse(heartbeatResponse(Errors.NONE)); RequestFuture<ClientResponse> future = consumerClient.send(node, heartbeat()); assertEquals(1, consumerClient.pendingRequestCount()); assertEquals(1, consumerClient.pendingRequestCount(node)); assertFalse(future.isDone()); consumerClient.poll(future); assertTrue(future.isDone()); assertTrue(future.succeeded()); ClientResponse clientResponse = future.value(); HeartbeatResponse response = (HeartbeatResponse) clientResponse.responseBody(); assertEquals(Errors.NONE, response.error()); } @Test public void multiSend() { client.prepareResponse(heartbeatResponse(Errors.NONE)); client.prepareResponse(heartbeatResponse(Errors.NONE)); RequestFuture<ClientResponse> future1 = consumerClient.send(node, heartbeat()); RequestFuture<ClientResponse> future2 = consumerClient.send(node, heartbeat()); assertEquals(2, consumerClient.pendingRequestCount()); assertEquals(2, consumerClient.pendingRequestCount(node)); consumerClient.awaitPendingRequests(node, Long.MAX_VALUE); assertTrue(future1.succeeded()); assertTrue(future2.succeeded()); } @Test public void doNotBlockIfPollConditionIsSatisfied() { NetworkClient mockNetworkClient = EasyMock.mock(NetworkClient.class); ConsumerNetworkClient consumerClient = new ConsumerNetworkClient(mockNetworkClient, metadata, time, 100, 1000); // expect poll, but with no timeout EasyMock.expect(mockNetworkClient.poll(EasyMock.eq(0L), EasyMock.anyLong())).andReturn(Collections.<ClientResponse>emptyList()); EasyMock.replay(mockNetworkClient); consumerClient.poll(Long.MAX_VALUE, time.milliseconds(), new ConsumerNetworkClient.PollCondition() { @Override public boolean shouldBlock() { return false; } }); EasyMock.verify(mockNetworkClient); } @Test public void blockWhenPollConditionNotSatisfied() { long timeout = 4000L; NetworkClient mockNetworkClient = EasyMock.mock(NetworkClient.class); ConsumerNetworkClient consumerClient = new ConsumerNetworkClient(mockNetworkClient, metadata, time, 100, 1000); EasyMock.expect(mockNetworkClient.inFlightRequestCount()).andReturn(1); EasyMock.expect(mockNetworkClient.poll(EasyMock.eq(timeout), EasyMock.anyLong())).andReturn(Collections.<ClientResponse>emptyList()); EasyMock.replay(mockNetworkClient); consumerClient.poll(timeout, time.milliseconds(), new ConsumerNetworkClient.PollCondition() { @Override public boolean shouldBlock() { return true; } }); EasyMock.verify(mockNetworkClient); } @Test public void blockOnlyForRetryBackoffIfNoInflightRequests() { long retryBackoffMs = 100L; NetworkClient mockNetworkClient = EasyMock.mock(NetworkClient.class); ConsumerNetworkClient consumerClient = new ConsumerNetworkClient(mockNetworkClient, metadata, time, retryBackoffMs, 1000L); EasyMock.expect(mockNetworkClient.inFlightRequestCount()).andReturn(0); EasyMock.expect(mockNetworkClient.poll(EasyMock.eq(retryBackoffMs), EasyMock.anyLong())).andReturn(Collections.<ClientResponse>emptyList()); EasyMock.replay(mockNetworkClient); consumerClient.poll(Long.MAX_VALUE, time.milliseconds(), new ConsumerNetworkClient.PollCondition() { @Override public boolean shouldBlock() { return true; } }); EasyMock.verify(mockNetworkClient); } @Test public void wakeup() { RequestFuture<ClientResponse> future = consumerClient.send(node, heartbeat()); consumerClient.wakeup(); try { consumerClient.poll(0); fail(); } catch (WakeupException e) { } client.respond(heartbeatResponse(Errors.NONE)); consumerClient.poll(future); assertTrue(future.isDone()); } @Test public void testAwaitForMetadataUpdateWithTimeout() { assertFalse(consumerClient.awaitMetadataUpdate(10L)); } @Test public void sendExpiry() throws InterruptedException { long unsentExpiryMs = 10; final AtomicBoolean isReady = new AtomicBoolean(); final AtomicBoolean disconnected = new AtomicBoolean(); client = new MockClient(time) { @Override public boolean ready(Node node, long now) { if (isReady.get()) return super.ready(node, now); else return false; } @Override public boolean connectionFailed(Node node) { return disconnected.get(); } }; // Queue first send, sleep long enough for this to expire and then queue second send consumerClient = new ConsumerNetworkClient(client, metadata, time, 100, unsentExpiryMs); RequestFuture<ClientResponse> future1 = consumerClient.send(node, heartbeat()); assertEquals(1, consumerClient.pendingRequestCount()); assertEquals(1, consumerClient.pendingRequestCount(node)); assertFalse(future1.isDone()); time.sleep(unsentExpiryMs + 1); RequestFuture<ClientResponse> future2 = consumerClient.send(node, heartbeat()); assertEquals(2, consumerClient.pendingRequestCount()); assertEquals(2, consumerClient.pendingRequestCount(node)); assertFalse(future2.isDone()); // First send should have expired and second send still pending consumerClient.poll(0); assertTrue(future1.isDone()); assertFalse(future1.succeeded()); assertEquals(1, consumerClient.pendingRequestCount()); assertEquals(1, consumerClient.pendingRequestCount(node)); assertFalse(future2.isDone()); // Enable send, the un-expired send should succeed on poll isReady.set(true); client.prepareResponse(heartbeatResponse(Errors.NONE)); consumerClient.poll(future2); ClientResponse clientResponse = future2.value(); HeartbeatResponse response = (HeartbeatResponse) clientResponse.responseBody(); assertEquals(Errors.NONE, response.error()); // Disable ready flag to delay send and queue another send. Disconnection should remove pending send isReady.set(false); RequestFuture<ClientResponse> future3 = consumerClient.send(node, heartbeat()); assertEquals(1, consumerClient.pendingRequestCount()); assertEquals(1, consumerClient.pendingRequestCount(node)); disconnected.set(true); consumerClient.poll(0); assertTrue(future3.isDone()); assertFalse(future3.succeeded()); assertEquals(0, consumerClient.pendingRequestCount()); assertEquals(0, consumerClient.pendingRequestCount(node)); } private HeartbeatRequest.Builder heartbeat() { return new HeartbeatRequest.Builder("group", 1, "memberId"); } private HeartbeatResponse heartbeatResponse(Errors error) { return new HeartbeatResponse(error); } }