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 @@ -20,6 +20,7 @@
import org.apache.kafka.common.KafkaFuture;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.annotation.InterfaceStability;
import org.apache.kafka.common.errors.ApiException;
import org.apache.kafka.common.internals.KafkaFutureImpl;
import org.apache.kafka.common.protocol.Errors;

Expand All @@ -35,9 +36,9 @@
@InterfaceStability.Evolving
public class AlterShareGroupOffsetsResult {

private final KafkaFuture<Map<TopicPartition, Errors>> future;
private final KafkaFuture<Map<TopicPartition, ApiException>> future;

AlterShareGroupOffsetsResult(KafkaFuture<Map<TopicPartition, Errors>> future) {
AlterShareGroupOffsetsResult(KafkaFuture<Map<TopicPartition, ApiException>> future) {
this.future = future;
}

Expand All @@ -54,11 +55,11 @@ public KafkaFuture<Void> partitionResult(final TopicPartition partition) {
result.completeExceptionally(new IllegalArgumentException(
"Alter offset for partition \"" + partition + "\" was not attempted"));
} else {
final Errors error = topicPartitions.get(partition);
if (error == Errors.NONE) {
final ApiException exception = topicPartitions.get(partition);
if (exception == null) {
result.complete(null);
} else {
result.completeExceptionally(error.exception());
result.completeExceptionally(exception);
}
}
});
Expand All @@ -68,22 +69,22 @@ public KafkaFuture<Void> partitionResult(final TopicPartition partition) {

/**
* Return a future which succeeds if all the alter offsets succeed.
* If not, the first topic error shall be returned.
*/
public KafkaFuture<Void> all() {
return this.future.thenApply(topicPartitionErrorsMap -> {
List<TopicPartition> partitionsFailed = topicPartitionErrorsMap.entrySet()
.stream()
.filter(e -> e.getValue() != Errors.NONE)
.filter(e -> e.getValue() != null)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the patch, just out of curiosity, would the AlterConsumerGroupOffsets RPC have the same issue?

return this.future.thenApply(topicPartitionErrorsMap -> {
List<TopicPartition> partitionsFailed = topicPartitionErrorsMap.entrySet()
.stream()
.filter(e -> e.getValue() != Errors.NONE)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
for (Errors error : topicPartitionErrorsMap.values()) {
if (error != Errors.NONE) {
throw error.exception(
"Failed altering group offsets for the following partitions: " + partitionsFailed);
}
}

@AndrewJSchofield AndrewJSchofield Jun 30, 2025

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'll check it out before I merge, but the important difference here is in KafkaApis.scala. For DeleteSGO, it already handled a non-zero error code. For AlterSGO, that code was missing.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ah, sorry. I didn't read your comment accurately. The difference is that AlterShareGroupOffsets can successfully pass back an error code, which is why this is in terms of ApiException rather than Errors. For AlterConsumerGroupOffsets, the RPC is actually OffsetCommit and this does not have an ErrorMessage field at all. So, it cannot be fixed for consumer groups until we have a version bump on the OffsetCommit RPC.

.map(Map.Entry::getKey)
.collect(Collectors.toList());

@TaiJuWu TaiJuWu Jun 30, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should this change to immutable?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'm not sure it matters here. The list in internal to this method, and it is just converted to a string and appended to the exception message.

for (Errors error : topicPartitionErrorsMap.values()) {
if (error != Errors.NONE) {
throw error.exception(
"Failed altering share group offsets for the following partitions: " + partitionsFailed);
for (ApiException exception : topicPartitionErrorsMap.values()) {
if (exception != null) {
throw Errors.forException(exception).exception(
"Failed altering group offsets for the following partitions: " + partitionsFailed);
}
}
return null;
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3804,8 +3804,10 @@ public DescribeShareGroupsResult describeShareGroups(final Collection<String> gr
}

@Override
public AlterShareGroupOffsetsResult alterShareGroupOffsets(String groupId, Map<TopicPartition, Long> offsets, AlterShareGroupOffsetsOptions options) {
SimpleAdminApiFuture<CoordinatorKey, Map<TopicPartition, Errors>> future = AlterShareGroupOffsetsHandler.newFuture(groupId);
public AlterShareGroupOffsetsResult alterShareGroupOffsets(final String groupId,
final Map<TopicPartition, Long> offsets,
final AlterShareGroupOffsetsOptions options) {
SimpleAdminApiFuture<CoordinatorKey, Map<TopicPartition, ApiException>> future = AlterShareGroupOffsetsHandler.newFuture(groupId);
AlterShareGroupOffsetsHandler handler = new AlterShareGroupOffsetsHandler(groupId, offsets, logContext);
invokeDriver(handler, future, options.timeoutMs);
return new AlterShareGroupOffsetsResult(future.get(CoordinatorKey.byGroupId(groupId)));
Expand All @@ -3821,7 +3823,9 @@ public ListShareGroupOffsetsResult listShareGroupOffsets(final Map<String, ListS
}

@Override
public DeleteShareGroupOffsetsResult deleteShareGroupOffsets(String groupId, Set<String> topics, DeleteShareGroupOffsetsOptions options) {
public DeleteShareGroupOffsetsResult deleteShareGroupOffsets(final String groupId,
final Set<String> topics,
final DeleteShareGroupOffsetsOptions options) {
SimpleAdminApiFuture<CoordinatorKey, Map<String, ApiException>> future = DeleteShareGroupOffsetsHandler.newFuture(groupId);
DeleteShareGroupOffsetsHandler handler = new DeleteShareGroupOffsetsHandler(groupId, topics, logContext);
invokeDriver(handler, future, options.timeoutMs);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
import org.apache.kafka.clients.admin.KafkaAdminClient;
import org.apache.kafka.common.Node;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.errors.ApiException;
import org.apache.kafka.common.message.AlterShareGroupOffsetsRequestData;
import org.apache.kafka.common.message.AlterShareGroupOffsetsResponseData;
import org.apache.kafka.common.protocol.Errors;
import org.apache.kafka.common.requests.AbstractResponse;
import org.apache.kafka.common.requests.AlterShareGroupOffsetsRequest;
Expand All @@ -33,7 +33,6 @@
import org.slf4j.Logger;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
Expand All @@ -42,7 +41,7 @@
/**
* This class is the handler for {@link KafkaAdminClient#alterShareGroupOffsets(String, Map, AlterShareGroupOffsetsOptions)} call
*/
public class AlterShareGroupOffsetsHandler extends AdminApiHandler.Batched<CoordinatorKey, Map<TopicPartition, Errors>> {
public class AlterShareGroupOffsetsHandler extends AdminApiHandler.Batched<CoordinatorKey, Map<TopicPartition, ApiException>> {

private final CoordinatorKey groupId;

Expand All @@ -52,16 +51,22 @@ public class AlterShareGroupOffsetsHandler extends AdminApiHandler.Batched<Coord

private final CoordinatorStrategy lookupStrategy;


public AlterShareGroupOffsetsHandler(String groupId, Map<TopicPartition, Long> offsets, LogContext logContext) {
this.groupId = CoordinatorKey.byGroupId(groupId);
this.offsets = offsets;
this.log = logContext.logger(AlterShareGroupOffsetsHandler.class);
this.lookupStrategy = new CoordinatorStrategy(FindCoordinatorRequest.CoordinatorType.GROUP, logContext);
}

public static AdminApiFuture.SimpleAdminApiFuture<CoordinatorKey, Map<TopicPartition, Errors>> newFuture(String groupId) {
return AdminApiFuture.forKeys(Collections.singleton(CoordinatorKey.byGroupId(groupId)));
public static AdminApiFuture.SimpleAdminApiFuture<CoordinatorKey, Map<TopicPartition, ApiException>> newFuture(String groupId) {
return AdminApiFuture.forKeys(Set.of(CoordinatorKey.byGroupId(groupId)));
}

private void validateKeys(Set<CoordinatorKey> groupIds) {
if (!groupIds.equals(Set.of(groupId))) {
throw new IllegalArgumentException("Received unexpected group ids " + groupIds +
" (expected only " + Set.of(groupId) + ")");
}
}

@Override
Expand All @@ -87,30 +92,38 @@ public String apiName() {
}

@Override
public ApiResult<CoordinatorKey, Map<TopicPartition, Errors>> handleResponse(Node broker, Set<CoordinatorKey> keys, AbstractResponse abstractResponse) {
public ApiResult<CoordinatorKey, Map<TopicPartition, ApiException>> handleResponse(Node broker, Set<CoordinatorKey> keys, AbstractResponse abstractResponse) {
validateKeys(keys);

AlterShareGroupOffsetsResponse response = (AlterShareGroupOffsetsResponse) abstractResponse;
final Map<TopicPartition, Errors> partitionResults = new HashMap<>();
final Set<CoordinatorKey> groupsToUnmap = new HashSet<>();
final Set<CoordinatorKey> groupsToRetry = new HashSet<>();

for (AlterShareGroupOffsetsResponseData.AlterShareGroupOffsetsResponseTopic topic : response.data().responses()) {
for (AlterShareGroupOffsetsResponseData.AlterShareGroupOffsetsResponsePartition partition : topic.partitions()) {
TopicPartition topicPartition = new TopicPartition(topic.topicName(), partition.partitionIndex());
Errors error = Errors.forCode(partition.errorCode());

if (error != Errors.NONE) {
handleError(
groupId,
topicPartition,
error,
partitionResults,
groupsToUnmap,
groupsToRetry
);
} else {
partitionResults.put(topicPartition, error);
final Map<TopicPartition, ApiException> partitionResults = new HashMap<>();

if (response.data().errorCode() != Errors.NONE.code()) {
final Errors topLevelError = Errors.forCode(response.data().errorCode());
final String topLevelErrorMessage = response.data().errorMessage();

offsets.forEach((topicPartition, offset) ->
handleError(
groupId,
topicPartition,
topLevelError,
topLevelErrorMessage,
partitionResults,
groupsToUnmap,
groupsToRetry
));
} else {
response.data().responses().forEach(topic -> topic.partitions().forEach(partition -> {
if (partition.errorCode() != Errors.NONE.code()) {
final Errors partitionError = Errors.forCode(partition.errorCode());

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: we can reuse the Errors

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: the debug message should use placeholder for partitionErrorMessage

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.

for example:

                var partitionError = Errors.forCode(partition.errorCode());
                var partitionErrorMessage = partition.errorMessage();
                if (partitionError != Errors.NONE) {
                    log.debug("AlterShareGroupOffsets request for group id {} and topic-partition {}-{} failed and returned error {}. {}",
                        groupId.idValue, topic.topicName(), partition.partitionIndex(), partitionError, partitionErrorMessage);
                }
                partitionResults.put(new TopicPartition(topic.topicName(), partition.partitionIndex()), partitionError.exception(partitionErrorMessage));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks @chia7712 . I'll put in a minor PR today.

final String partitionErrorMessage = partition.errorMessage();
log.debug("AlterShareGroupOffsets request for group id {} and topic-partition {}-{} failed and returned error {}." + partitionErrorMessage,
groupId.idValue, topic.topicName(), partition.partitionIndex(), partitionError);
}
}
partitionResults.put(new TopicPartition(topic.topicName(), partition.partitionIndex()), Errors.forCode(partition.errorCode()).exception(partition.errorMessage()));
}));
}

if (groupsToUnmap.isEmpty() && groupsToRetry.isEmpty()) {
Expand All @@ -121,23 +134,23 @@ public ApiResult<CoordinatorKey, Map<TopicPartition, Errors>> handleResponse(Nod
}

private void handleError(
CoordinatorKey groupId,
TopicPartition topicPartition,
Errors error,
Map<TopicPartition, Errors> partitionResults,
Set<CoordinatorKey> groupsToUnmap,
Set<CoordinatorKey> groupsToRetry
CoordinatorKey groupId,
TopicPartition topicPartition,
Errors error,
String errorMessage,
Map<TopicPartition, ApiException> partitionResults,
Set<CoordinatorKey> groupsToUnmap,
Set<CoordinatorKey> groupsToRetry
) {
switch (error) {
case COORDINATOR_LOAD_IN_PROGRESS:
case REBALANCE_IN_PROGRESS:
log.debug("AlterShareGroupOffsets request for group id {} returned error {}. Will retry.",
groupId.idValue, error);
log.debug("AlterShareGroupOffsets request for group id {} returned error {}. Will retry." + errorMessage, groupId.idValue, error);
groupsToRetry.add(groupId);
break;
case COORDINATOR_NOT_AVAILABLE:
case NOT_COORDINATOR:
log.debug("AlterShareGroupOffsets request for group id {} returned error {}. Will rediscover the coordinator and retry.",
log.debug("AlterShareGroupOffsets request for group id {} returned error {}. Will rediscover the coordinator and retry." + errorMessage,
groupId.idValue, error);
groupsToUnmap.add(groupId);
break;
Expand All @@ -147,14 +160,12 @@ private void handleError(
case UNKNOWN_SERVER_ERROR:
case KAFKA_STORAGE_ERROR:
case GROUP_AUTHORIZATION_FAILED:
log.debug("AlterShareGroupOffsets request for group id {} and partition {} failed due" +
" to error {}.", groupId.idValue, topicPartition, error);
partitionResults.put(topicPartition, error);
log.debug("AlterShareGroupOffsets request for group id {} failed due to error {}." + errorMessage, groupId.idValue, error);
partitionResults.put(topicPartition, error.exception(errorMessage));
break;
default:
log.error("AlterShareGroupOffsets request for group id {} and partition {} failed due" +
" to unexpected error {}.", groupId.idValue, topicPartition, error);
partitionResults.put(topicPartition, error);
log.error("AlterShareGroupOffsets request for group id {} failed due to unexpected error {}." + errorMessage, groupId.idValue, error);
partitionResults.put(topicPartition, error.exception(errorMessage));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,26 +53,31 @@ public String toString() {
}

@Override
public AbstractResponse getErrorResponse(int throttleTimeMs, Throwable e) {
Errors error = Errors.forException(e);
return new AlterShareGroupOffsetsResponse(getErrorResponse(throttleTimeMs, error));
public AlterShareGroupOffsetsResponse getErrorResponse(int throttleTimeMs, Throwable e) {
return getErrorResponse(throttleTimeMs, Errors.forException(e));
}

public static AlterShareGroupOffsetsResponseData getErrorResponse(int throttleTimeMs, Errors error) {
return new AlterShareGroupOffsetsResponseData()
.setThrottleTimeMs(throttleTimeMs)
.setErrorCode(error.code())
.setErrorMessage(error.message());
public AlterShareGroupOffsetsResponse getErrorResponse(int throttleTimeMs, Errors error) {
return getErrorResponse(throttleTimeMs, error.code(), error.message());
}

public AlterShareGroupOffsetsResponse getErrorResponse(int throttleTimeMs, short errorCode, String message) {
return new AlterShareGroupOffsetsResponse(
new AlterShareGroupOffsetsResponseData()
.setThrottleTimeMs(throttleTimeMs)
.setErrorCode(errorCode)
.setErrorMessage(message)
);
}

public static AlterShareGroupOffsetsResponseData getErrorResponse(Errors error) {
return getErrorResponse(error.code(), error.message());
public static AlterShareGroupOffsetsResponseData getErrorResponseData(Errors error) {
return getErrorResponseData(error, null);
}

public static AlterShareGroupOffsetsResponseData getErrorResponse(short errorCode, String errorMessage) {
public static AlterShareGroupOffsetsResponseData getErrorResponseData(Errors error, String errorMessage) {
return new AlterShareGroupOffsetsResponseData()
.setErrorCode(errorCode)
.setErrorMessage(errorMessage);
.setErrorCode(error.code())
.setErrorMessage(errorMessage == null ? error.message() : errorMessage);
}

public static AlterShareGroupOffsetsRequest parse(Readable readable, short version) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,18 @@ public static DeleteShareGroupOffsetsRequest parse(Readable readable, short vers
}

public static DeleteShareGroupOffsetsResponseData getErrorDeleteResponseData(Errors error) {
return getErrorDeleteResponseData(error.code(), error.message());
return getErrorDeleteResponseData(error, null);
}

public static DeleteShareGroupOffsetsResponseData getErrorDeleteResponseData(short errorCode, String errorMessage) {
return new DeleteShareGroupOffsetsResponseData()
.setErrorCode(errorCode)
.setErrorMessage(errorMessage);
.setErrorMessage(errorMessage == null ? Errors.forCode(errorCode).message() : errorMessage);
}

public static DeleteShareGroupOffsetsResponseData getErrorDeleteResponseData(Errors error, String errorMessage) {
return new DeleteShareGroupOffsetsResponseData()
.setErrorCode(error.code())
.setErrorMessage(errorMessage == null ? error.message() : errorMessage);
}
}
Loading