synchronized RequestFuture<Void> sendHeartbeatRequest() { log.debug("Sending Heartbeat request to coordinator {}", coordinator); HeartbeatRequest.Builder requestBuilder = new HeartbeatRequest.Builder(this.groupId, this.generation.generationId, this.generation.memberId); return client.send(coordinator, requestBuilder) .compose(new HeartbeatResponseHandler()); }
/** * Discover the current coordinator for the group. Sends a GroupMetadata request to * one of the brokers. The returned future should be polled to get the result of the request. * @return A request future which indicates the completion of the metadata request */ private RequestFuture<Void> sendFindCoordinatorRequest(Node node) { // initiate the group metadata request log.debug("Sending FindCoordinator request to broker {}", node); FindCoordinatorRequest.Builder requestBuilder = new FindCoordinatorRequest.Builder(FindCoordinatorRequest.CoordinatorType.GROUP, this.groupId); return client.send(node, requestBuilder) .compose(new FindCoordinatorResponseHandler()); }
/** * Fetch the committed offsets for a set of partitions. This is a non-blocking call. The * returned future can be polled to get the actual offsets returned from the broker. * * @param partitions The set of partitions to get offsets for. * @return A request future containing the committed offsets. */ private RequestFuture<Map<TopicPartition, OffsetAndMetadata>> sendOffsetFetchRequest(Set<TopicPartition> partitions) { Node coordinator = checkAndGetCoordinator(); if (coordinator == null) return RequestFuture.coordinatorNotAvailable(); log.debug("Fetching committed offsets for partitions: {}", partitions); // construct the request OffsetFetchRequest.Builder requestBuilder = new OffsetFetchRequest.Builder(this.groupId, new ArrayList<>(partitions)); // send the request with a callback return client.send(coordinator, requestBuilder) .compose(new OffsetFetchResponseHandler()); }
/** * Send the ListOffsetRequest to a specific broker for the partitions and target timestamps. * * @param node The node to send the ListOffsetRequest to. * @param timestampsToSearch The mapping from partitions to the target timestamps. * @param requireTimestamp True if we require a timestamp in the response. * @return A response which can be polled to obtain the corresponding timestamps and offsets. */ private RequestFuture<ListOffsetResult> sendListOffsetRequest(final Node node, final Map<TopicPartition, ListOffsetRequest.PartitionData> timestampsToSearch, boolean requireTimestamp) { ListOffsetRequest.Builder builder = ListOffsetRequest.Builder .forConsumer(requireTimestamp, isolationLevel) .setTargetTimes(timestampsToSearch); log.debug("Sending ListOffsetRequest {} to broker {}", builder, node); return client.send(node, builder) .compose(new RequestFutureAdapter<ClientResponse, ListOffsetResult>() { @Override public void onSuccess(ClientResponse response, RequestFuture<ListOffsetResult> future) { ListOffsetResponse lor = (ListOffsetResponse) response.responseBody(); log.trace("Received ListOffsetResponse {} from broker {}", lor, node); handleListOffsetResponse(timestampsToSearch, lor, future); } }); }
private RequestFuture<ByteBuffer> sendSyncGroupRequest(SyncGroupRequest.Builder requestBuilder) { if (coordinatorUnknown()) return RequestFuture.coordinatorNotAvailable(); return client.send(coordinator, requestBuilder) .compose(new SyncGroupResponseHandler()); }
/** * Join the group and return the assignment for the next generation. This function handles both * JoinGroup and SyncGroup, delegating to {@link #performAssignment(String, String, Map)} if * elected leader by the coordinator. * * NOTE: This is visible only for testing * * @return A request future which wraps the assignment returned from the group leader */ RequestFuture<ByteBuffer> sendJoinGroupRequest() { if (coordinatorUnknown()) return RequestFuture.coordinatorNotAvailable(); // send a join group request to the coordinator log.info("(Re-)joining group"); JoinGroupRequest.Builder requestBuilder = new JoinGroupRequest.Builder( groupId, this.sessionTimeoutMs, this.generation.memberId, protocolType(), metadata()).setRebalanceTimeout(this.rebalanceTimeoutMs); log.debug("Sending JoinGroup ({}) to coordinator {}", requestBuilder, this.coordinator); // Note that we override the request timeout using the rebalance timeout since that is the // maximum time that it may block on the coordinator. We add an extra 5 seconds for small delays. int joinGroupTimeoutMs = Math.max(rebalanceTimeoutMs, rebalanceTimeoutMs + 5000); return client.send(coordinator, requestBuilder, joinGroupTimeoutMs) .compose(new JoinGroupResponseHandler()); }
/** * Leave the current group and reset local generation/memberId. */ public synchronized void maybeLeaveGroup() { if (!coordinatorUnknown() && state != MemberState.UNJOINED && generation.isValid()) { // this is a minimal effort attempt to leave the group. we do not // attempt any resending if the request fails or times out. log.info("Sending LeaveGroup request to coordinator {}", coordinator); LeaveGroupRequest.Builder request = new LeaveGroupRequest.Builder(groupId, generation.memberId); client.send(coordinator, request) .compose(new LeaveGroupResponseHandler()); client.pollNoWakeup(); } resetGeneration(); }
.compose(new OffsetCommitResponseHandler(offsets));
@Test public void testCoordinatorUnknownInUnsentCallbacksAfterCoordinatorDead() throws Exception { // When the coordinator is marked dead, all unsent or in-flight requests are cancelled // with a disconnect error. This test case ensures that the corresponding callbacks see // the coordinator as unknown which prevents additional retries to the same coordinator. client.prepareResponse(groupCoordinatorResponse(node, Errors.NONE)); coordinator.ensureCoordinatorReady(time.timer(Long.MAX_VALUE)); final AtomicBoolean asyncCallbackInvoked = new AtomicBoolean(false); Map<TopicPartition, OffsetCommitRequest.PartitionData> offsets = singletonMap( new TopicPartition("foo", 0), new OffsetCommitRequest.PartitionData(13L, Optional.empty(), "")); consumerClient.send(coordinator.checkAndGetCoordinator(), new OffsetCommitRequest.Builder(groupId, offsets)) .compose(new RequestFutureAdapter<ClientResponse, Object>() { @Override public void onSuccess(ClientResponse value, RequestFuture<Object> future) {} @Override public void onFailure(RuntimeException e, RequestFuture<Object> future) { assertTrue("Unexpected exception type: " + e.getClass(), e instanceof DisconnectException); assertTrue(coordinator.coordinatorUnknown()); asyncCallbackInvoked.set(true); } }); coordinator.markCoordinatorUnknown(); consumerClient.pollNoWakeup(); assertTrue(asyncCallbackInvoked.get()); }
@Test public void testComposeFailureCase() { RequestFuture<String> future = new RequestFuture<>(); RequestFuture<Integer> composed = future.compose(new RequestFutureAdapter<String, Integer>() { @Override public void onSuccess(String value, RequestFuture<Integer> future) { future.complete(value.length()); } }); RuntimeException e = new RuntimeException(); future.raise(e); assertTrue(composed.isDone()); assertTrue(composed.failed()); assertEquals(e, composed.exception()); }
@Test public void testComposeSuccessCase() { RequestFuture<String> future = new RequestFuture<>(); RequestFuture<Integer> composed = future.compose(new RequestFutureAdapter<String, Integer>() { @Override public void onSuccess(String value, RequestFuture<Integer> future) { future.complete(value.length()); } }); future.complete("hello"); assertTrue(composed.isDone()); assertTrue(composed.succeeded()); assertEquals(5, (int) composed.value()); }