Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package org.apache.kafka.clients.consumer.internals;

import java.time.Duration;
import java.util.SortedSet;
import java.util.TreeSet;
import org.apache.kafka.clients.GroupRebalanceConfig;
Expand Down Expand Up @@ -548,14 +549,18 @@ public boolean poll(Timer timer, boolean waitForJoinGroup) {
}
}
} else {
// For manually assigned partitions, if coordinator is unknown, make sure we lookup one and await metadata.
// For manually assigned partitions, we do not try to pro-actively lookup coordinator;
// instead we only try to refresh metadata when necessary.
// If connections to all nodes fail, wakeups triggered while attempting to send fetch
// requests result in polls returning immediately, causing a tight loop of polls. Without
// the wakeup, poll() with no channels would block for the timeout, delaying re-connection.
// awaitMetadataUpdate() in ensureCoordinatorReady initiates new connections with configured backoff and avoids the busy loop.
if (coordinatorUnknownAndUnready(timer)) {
return false;
if (metadata.updateRequested() && !client.hasReadyNodes(timer.currentTimeMs())) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think that we need to remove if coordinator is unknown, make sure we lookup one and from the above comment (first sentence).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack

client.awaitMetadataUpdate(timer);
}

// if there is pending coordinator requests, ensure they have a chance to be transmitted.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a major change while addressing @dajac 's comment: previously the manual assignment, the coordinator.poll call would not call networkClient.poll, which means that if the coordinator discovery does not complete within the commitAsync (note we call networkClient.poll twice in that function, so it's possible that function would complete the discovery), we would have no other places to poll the network client to complete the pending requests.

client.pollNoWakeup();
}

maybeAutoCommitOffsetsAsync(timer.currentTimeMs());
Expand Down Expand Up @@ -1004,7 +1009,17 @@ public RequestFuture<Void> commitOffsetsAsync(final Map<TopicPartition, OffsetAn
if (offsets.isEmpty()) {
// No need to check coordinator if offsets is empty since commit of empty offsets is completed locally.
future = doCommitOffsetsAsync(offsets, callback);
} else if (!coordinatorUnknown()) {
} else if (!coordinatorUnknownAndUnready(time.timer(Duration.ZERO))) {
// we need to make sure coordinator is ready before committing, since
// this is for async committing we do not try to block, but just try once to
// clear the previous discover-coordinator future, resend, or get responses;
// if the coordinator is not ready yet then we would just proceed and put that into the
// pending requests, and future poll calls would still try to complete them.
//
// the key here though is that we have to try sending the discover-coordinator if
// it's not known or ready, since this is the only place we can send such request
// under manual assignment (there we would not have heartbeat thread trying to auto-rediscover
// the coordinator).
future = doCommitOffsetsAsync(offsets, callback);
} else {
// we don't know the current coordinator, so try to find it and then send the commit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -717,7 +717,6 @@ public void testFetchProgressWithMissingPartitionPosition() {
consumer.seekToEnd(singleton(tp0));
consumer.seekToBeginning(singleton(tp1));

client.prepareResponseFrom(FindCoordinatorResponse.prepareResponse(Errors.NONE, groupId, node), node);
client.prepareResponse(body -> {
ListOffsetsRequest request = (ListOffsetsRequest) body;
List<ListOffsetsPartition> partitions = request.topics().stream().flatMap(t -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -514,10 +514,62 @@ public void testCoordinatorNotAvailableWithUserAssignedType() {
coordinator.poll(time.timer(0));
assertTrue(coordinator.coordinatorUnknown());

// should find an available node in next find coordinator request
// should not try to find coordinator since we are in manual assignment
// hence the prepared response should not be returned
client.prepareResponse(groupCoordinatorResponse(node, Errors.NONE));
coordinator.poll(time.timer(Long.MAX_VALUE));
assertTrue(coordinator.coordinatorUnknown());
}

@Test
public void testAutoCommitAsyncWithUserAssignedType() {
try (ConsumerCoordinator coordinator = buildCoordinator(rebalanceConfig, new Metrics(), assignors, true, subscriptions)) {
subscriptions.assignFromUser(Collections.singleton(t1p));
// set timeout to 0 because we expect no requests sent
coordinator.poll(time.timer(0));
assertTrue(coordinator.coordinatorUnknown());
assertFalse(client.hasInFlightRequests());

// elapse auto commit interval and set committable position
time.sleep(autoCommitIntervalMs);
subscriptions.seekUnvalidated(t1p, new SubscriptionState.FetchPosition(100L));

// should try to find coordinator since we are auto committing
coordinator.poll(time.timer(0));
assertTrue(coordinator.coordinatorUnknown());
assertTrue(client.hasInFlightRequests());

client.respond(groupCoordinatorResponse(node, Errors.NONE));
coordinator.poll(time.timer(0));
assertFalse(coordinator.coordinatorUnknown());
// after we've discovered the coordinator we should send
// out the commit request immediately
assertTrue(client.hasInFlightRequests());
}
}

@Test
public void testCommitAsyncWithUserAssignedType() {
subscriptions.assignFromUser(Collections.singleton(t1p));
// set timeout to 0 because we expect no requests sent
coordinator.poll(time.timer(0));
assertTrue(coordinator.coordinatorUnknown());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also assert that there is not inflight requests after calling poll?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack

assertFalse(client.hasInFlightRequests());

// should try to find coordinator since we are commit async
coordinator.commitOffsetsAsync(singletonMap(t1p, new OffsetAndMetadata(100L)), (offsets, exception) -> {
fail("Commit should not get responses, but got offsets:" + offsets +", and exception:" + exception);
});
coordinator.poll(time.timer(0));
assertTrue(coordinator.coordinatorUnknown());
assertTrue(client.hasInFlightRequests());

client.respond(groupCoordinatorResponse(node, Errors.NONE));
coordinator.poll(time.timer(0));
assertFalse(coordinator.coordinatorUnknown());
// after we've discovered the coordinator we should send
// out the commit request immediately
assertTrue(client.hasInFlightRequests());
}

@Test
Expand Down Expand Up @@ -2116,8 +2168,7 @@ private void testInFlightRequestsFailedAfterCoordinatorMarkedDead(Errors error)

@Test
public void testAutoCommitDynamicAssignment() {
try (ConsumerCoordinator coordinator = buildCoordinator(rebalanceConfig, new Metrics(), assignors, true, subscriptions)
) {
try (ConsumerCoordinator coordinator = buildCoordinator(rebalanceConfig, new Metrics(), assignors, true, subscriptions)) {
subscriptions.subscribe(singleton(topic1), rebalanceListener);
joinAsFollowerAndReceiveAssignment(coordinator, singletonList(t1p));
subscriptions.seek(t1p, 100);
Expand Down