-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-12778: Fix QuorumController request timeouts and electLeaders #10688
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,7 @@ | |
| import java.util.OptionalLong; | ||
| import java.util.Random; | ||
| import java.util.concurrent.CompletableFuture; | ||
| import java.util.concurrent.CountDownLatch; | ||
| import java.util.concurrent.RejectedExecutionException; | ||
| import java.util.concurrent.TimeUnit; | ||
| import java.util.function.Function; | ||
|
|
@@ -89,6 +90,7 @@ | |
| import org.slf4j.Logger; | ||
|
|
||
| import static java.util.concurrent.TimeUnit.MICROSECONDS; | ||
| import static java.util.concurrent.TimeUnit.MILLISECONDS; | ||
| import static java.util.concurrent.TimeUnit.NANOSECONDS; | ||
|
|
||
|
|
||
|
|
@@ -475,6 +477,12 @@ <T> CompletableFuture<T> appendReadEvent(String name, Supplier<T> handler) { | |
| return event.future(); | ||
| } | ||
|
|
||
| <T> CompletableFuture<T> appendReadEvent(String name, long deadlineNs, Supplier<T> handler) { | ||
| ControllerReadEvent<T> event = new ControllerReadEvent<T>(name, handler); | ||
| queue.appendWithDeadline(deadlineNs, event); | ||
| return event.future(); | ||
| } | ||
|
|
||
| interface ControllerWriteOperation<T> { | ||
| /** | ||
| * Generate the metadata records needed to implement this controller write | ||
|
|
@@ -602,11 +610,10 @@ public String toString() { | |
| } | ||
|
|
||
| private <T> CompletableFuture<T> appendWriteEvent(String name, | ||
| long timeoutMs, | ||
| long deadlineNs, | ||
| ControllerWriteOperation<T> op) { | ||
| ControllerWriteEvent<T> event = new ControllerWriteEvent<>(name, op); | ||
| queue.appendWithDeadline(time.nanoseconds() + | ||
| NANOSECONDS.convert(timeoutMs, TimeUnit.MILLISECONDS), event); | ||
| queue.appendWithDeadline(deadlineNs, event); | ||
| return event.future(); | ||
| } | ||
|
|
||
|
|
@@ -961,8 +968,9 @@ public CompletableFuture<AlterIsrResponseData> alterIsr(AlterIsrRequestData requ | |
| if (request.topics().isEmpty()) { | ||
| return CompletableFuture.completedFuture(new CreateTopicsResponseData()); | ||
| } | ||
| return appendWriteEvent("createTopics", () -> | ||
| replicationControl.createTopics(request)); | ||
| return appendWriteEvent("createTopics", | ||
| time.nanoseconds() + NANOSECONDS.convert(request.timeoutMs(), MILLISECONDS), | ||
| () -> replicationControl.createTopics(request)); | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -972,23 +980,26 @@ public CompletableFuture<Void> unregisterBroker(int brokerId) { | |
| } | ||
|
|
||
| @Override | ||
| public CompletableFuture<Map<String, ResultOrError<Uuid>>> findTopicIds(Collection<String> names) { | ||
| public CompletableFuture<Map<String, ResultOrError<Uuid>>> findTopicIds(long deadlineNs, | ||
| Collection<String> names) { | ||
| if (names.isEmpty()) return CompletableFuture.completedFuture(Collections.emptyMap()); | ||
| return appendReadEvent("findTopicIds", | ||
| return appendReadEvent("findTopicIds", deadlineNs, | ||
| () -> replicationControl.findTopicIds(lastCommittedOffset, names)); | ||
| } | ||
|
|
||
| @Override | ||
| public CompletableFuture<Map<Uuid, ResultOrError<String>>> findTopicNames(Collection<Uuid> ids) { | ||
| public CompletableFuture<Map<Uuid, ResultOrError<String>>> findTopicNames(long deadlineNs, | ||
| Collection<Uuid> ids) { | ||
| if (ids.isEmpty()) return CompletableFuture.completedFuture(Collections.emptyMap()); | ||
| return appendReadEvent("findTopicNames", | ||
| return appendReadEvent("findTopicNames", deadlineNs, | ||
| () -> replicationControl.findTopicNames(lastCommittedOffset, ids)); | ||
| } | ||
|
|
||
| @Override | ||
| public CompletableFuture<Map<Uuid, ApiError>> deleteTopics(Collection<Uuid> ids) { | ||
| public CompletableFuture<Map<Uuid, ApiError>> deleteTopics(long deadlineNs, | ||
| Collection<Uuid> ids) { | ||
| if (ids.isEmpty()) return CompletableFuture.completedFuture(Collections.emptyMap()); | ||
| return appendWriteEvent("deleteTopics", | ||
| return appendWriteEvent("deleteTopics", deadlineNs, | ||
| () -> replicationControl.deleteTopics(ids)); | ||
| } | ||
|
|
||
|
|
@@ -1002,7 +1013,13 @@ public CompletableFuture<Map<Uuid, ApiError>> deleteTopics(Collection<Uuid> ids) | |
| @Override | ||
| public CompletableFuture<ElectLeadersResponseData> | ||
| electLeaders(ElectLeadersRequestData request) { | ||
| return appendWriteEvent("electLeaders", request.timeoutMs(), | ||
| // If topicPartitions is null, we will try to trigger a new leader election on | ||
| // all partitions (!). But if it's empty, there is nothing to do. | ||
| if (request.topicPartitions() != null && request.topicPartitions().isEmpty()) { | ||
| return CompletableFuture.completedFuture(new ElectLeadersResponseData()); | ||
| } | ||
| return appendWriteEvent("electLeaders", | ||
| time.nanoseconds() + NANOSECONDS.convert(request.timeoutMs(), MILLISECONDS), | ||
| () -> replicationControl.electLeaders(request)); | ||
| } | ||
|
|
||
|
|
@@ -1016,6 +1033,9 @@ public CompletableFuture<FeatureMapAndEpoch> finalizedFeatures() { | |
| public CompletableFuture<Map<ConfigResource, ApiError>> incrementalAlterConfigs( | ||
| Map<ConfigResource, Map<String, Entry<OpType, String>>> configChanges, | ||
| boolean validateOnly) { | ||
| if (configChanges.isEmpty()) { | ||
| return CompletableFuture.completedFuture(Collections.emptyMap()); | ||
| } | ||
| return appendWriteEvent("incrementalAlterConfigs", () -> { | ||
| ControllerResult<Map<ConfigResource, ApiError>> result = | ||
| configurationControl.incrementalAlterConfigs(configChanges); | ||
|
|
@@ -1030,17 +1050,24 @@ public CompletableFuture<Map<ConfigResource, ApiError>> incrementalAlterConfigs( | |
| @Override | ||
| public CompletableFuture<AlterPartitionReassignmentsResponseData> | ||
| alterPartitionReassignments(AlterPartitionReassignmentsRequestData request) { | ||
| CompletableFuture<AlterPartitionReassignmentsResponseData> future = new CompletableFuture<>(); | ||
| future.completeExceptionally(new UnsupportedOperationException()); | ||
| return future; | ||
| if (request.topics().isEmpty()) { | ||
| return CompletableFuture.completedFuture(new AlterPartitionReassignmentsResponseData()); | ||
| } | ||
| return appendWriteEvent("alterPartitionReassignments", | ||
| time.nanoseconds() + NANOSECONDS.convert(request.timeoutMs(), MILLISECONDS), | ||
| () -> { | ||
| throw new UnsupportedOperationException(); | ||
| }); | ||
| } | ||
|
|
||
| @Override | ||
| public CompletableFuture<ListPartitionReassignmentsResponseData> | ||
| listPartitionReassignments(ListPartitionReassignmentsRequestData request) { | ||
| CompletableFuture<ListPartitionReassignmentsResponseData> future = new CompletableFuture<>(); | ||
| future.completeExceptionally(new UnsupportedOperationException()); | ||
| return future; | ||
| return appendReadEvent("listPartitionReassignments", | ||
| time.nanoseconds() + NANOSECONDS.convert(request.timeoutMs(), MILLISECONDS), | ||
| () -> { | ||
| throw new UnsupportedOperationException(); | ||
| }); | ||
|
Comment on lines
1068
to
1070
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: we can do without the curly braces here and above. However, these will soon be replaced by the actual impl
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agreed |
||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -1118,12 +1145,12 @@ public CompletableFuture<Map<ClientQuotaEntity, ApiError>> alterClientQuotas( | |
|
|
||
| @Override | ||
| public CompletableFuture<List<CreatePartitionsTopicResult>> | ||
| createPartitions(List<CreatePartitionsTopic> topics) { | ||
| createPartitions(long deadlineNs, List<CreatePartitionsTopic> topics) { | ||
| if (topics.isEmpty()) { | ||
| return CompletableFuture.completedFuture(Collections.emptyList()); | ||
| } | ||
| return appendWriteEvent("createPartitions", () -> | ||
| replicationControl.createPartitions(topics)); | ||
| return appendWriteEvent("createPartitions", deadlineNs, | ||
| () -> replicationControl.createPartitions(topics)); | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -1165,4 +1192,22 @@ public long curClaimEpoch() { | |
| public void close() throws InterruptedException { | ||
| queue.close(); | ||
| } | ||
|
|
||
| // VisibleForTesting | ||
| CountDownLatch pause() { | ||
| final CountDownLatch latch = new CountDownLatch(1); | ||
| appendControlEvent("pause", () -> { | ||
| try { | ||
| latch.await(); | ||
| } catch (InterruptedException e) { | ||
| log.info("Interrupted while waiting for unpause.", e); | ||
| } | ||
| }); | ||
| return latch; | ||
| } | ||
|
|
||
| // VisibleForTesting | ||
| Time time() { | ||
| return time; | ||
| } | ||
|
Comment on lines
1210
to
1212
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need this? Can't we just use the Time we pass into the constructor in tests? Not a big deal really, just wondering
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have several helper classes for creating quorum controllers, so getting access to the time parameter that way would be difficult. Anyway, this is package-private, so it really only applies to unit tests specifically. |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this related to timeouts, or is it just a different fix?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a slightly different fix, although sort of related.