-
Notifications
You must be signed in to change notification settings - Fork 3.7k
[fix] [broker] Ensure policy cache is updated before updateTopicPoliciesAsync returns. #21030
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 |
|---|---|---|
|
|
@@ -24,9 +24,11 @@ | |
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Objects; | ||
| import java.util.concurrent.BlockingDeque; | ||
| import java.util.concurrent.CompletableFuture; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
| import java.util.concurrent.CopyOnWriteArrayList; | ||
| import java.util.concurrent.LinkedBlockingDeque; | ||
| import java.util.concurrent.TimeUnit; | ||
| import java.util.concurrent.atomic.AtomicInteger; | ||
| import javax.annotation.Nonnull; | ||
|
|
@@ -84,6 +86,8 @@ public class SystemTopicBasedTopicPoliciesService implements TopicPoliciesServic | |
| @VisibleForTesting | ||
| final Map<TopicName, List<TopicPolicyListener<TopicPolicies>>> listeners = new ConcurrentHashMap<>(); | ||
|
|
||
| final Map<TopicName, BlockingDeque<CompletableFuture>> results = new ConcurrentHashMap<>(); | ||
|
|
||
| public SystemTopicBasedTopicPoliciesService(PulsarService pulsarService) { | ||
| this.pulsarService = pulsarService; | ||
| this.clusterName = pulsarService.getConfiguration().getClusterName(); | ||
|
|
@@ -97,7 +101,10 @@ public CompletableFuture<Void> deleteTopicPoliciesAsync(TopicName topicName) { | |
|
|
||
| @Override | ||
| public CompletableFuture<Void> updateTopicPoliciesAsync(TopicName topicName, TopicPolicies policies) { | ||
| return sendTopicPolicyEvent(topicName, ActionType.UPDATE, policies); | ||
| CompletableFuture<Void> result = new CompletableFuture<>(); | ||
| results.computeIfAbsent(topicName, k -> new LinkedBlockingDeque<>()).add(result); | ||
|
thetumbled marked this conversation as resolved.
|
||
| return sendTopicPolicyEvent(topicName, ActionType.UPDATE, policies) | ||
| .thenCombine(result, (__, ___) -> null); | ||
| } | ||
|
|
||
| private CompletableFuture<Void> sendTopicPolicyEvent(TopicName topicName, ActionType actionType, | ||
|
|
@@ -420,11 +427,37 @@ private void cleanCacheAndCloseReader(@Nonnull NamespaceName namespace, boolean | |
| }); | ||
| } | ||
|
|
||
| private void completeResults(TopicName topicName) { | ||
| if (results.get(topicName) != null) { | ||
| BlockingDeque<CompletableFuture> futures = results.get(topicName); | ||
| CompletableFuture<Void> future; | ||
| while (true) { | ||
| future = futures.poll(); | ||
| if (future == null) { | ||
| break; | ||
| } | ||
| future.complete(null); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private void completeResults(BlockingDeque<CompletableFuture> futures) { | ||
| CompletableFuture<Void> future; | ||
| while (true) { | ||
| future = futures.poll(); | ||
| if (future == null) { | ||
| break; | ||
| } | ||
| future.complete(null); | ||
| } | ||
| } | ||
|
|
||
| private void readMorePolicies(SystemTopicClient.Reader<PulsarEvent> reader) { | ||
| reader.readNextAsync() | ||
| .thenAccept(msg -> { | ||
| refreshTopicPoliciesCache(msg); | ||
| notifyListener(msg); | ||
| completeResults(TopicName.get(TopicName.get(msg.getKey()).getPartitionedTopicName())); | ||
|
Contributor
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. I dont think this is a right way to solve the problem. For examle: If we have three different actions, e.g.,
Member
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. Yes, this is the corner case for this solution, which can not handle with concurrent request for modifying topic policy. This solution fix the synchronize case.
Contributor
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. OK. I see. Synchronize case is enough I think |
||
| }) | ||
| .whenComplete((__, ex) -> { | ||
| if (ex == null) { | ||
|
|
@@ -627,6 +660,11 @@ public void unregisterListener(TopicName topicName, TopicPolicyListener<TopicPol | |
| topicListeners.remove(listener); | ||
| if (topicListeners.isEmpty()) { | ||
| topicListeners = null; | ||
| // clear the policy setting result queue | ||
| BlockingDeque<CompletableFuture> futures = results.remove(topicName); | ||
|
thetumbled marked this conversation as resolved.
|
||
| if (futures != null) { | ||
| completeResults(futures); | ||
| } | ||
| } | ||
| } | ||
| return topicListeners; | ||
|
|
||
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.
I don't think it will resolve the issue completely because the next request might happened on different brokers.
Uh oh!
There was an error while loading. Please reload this page.
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.
All request for setting topic policy will be redirected to the owner broker of the partition-0, so we do not need to consider distributed situations.
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.
Could you share the code which routed the HTTP request to the "owner broker of the partition-0"? I want to double confirm it. Thanks
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.
we have such redirection logic in:
org.apache.pulsar.broker.admin.impl.PersistentTopicsBase#preValidationUh oh!
There was an error while loading. Please reload this page.
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.
There are two other concerns about the fix:
I'm also thinking about how we can implement this function simpler.
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.
Indeed, even if all the read and update operations occur on the same broker, it remains crucial to serialize all such operations. Without serialization, the broker would still be incapable of ensuring that every modification is carried out on the most up-to-date data.
The changes from v2 will be missed.
Additionally, during the transfer of ownership, it is possible for ongoing updates to continue taking place on the old broker. Meanwhile, new requests directed to the new broker may still have an opportunity to update the data based on a potentially "dirty read."