-
Notifications
You must be signed in to change notification settings - Fork 3.8k
[fix][broker] Fix persistent throughput degradation caused by permit loss during frequent reconnects on Shared subscriptions #26289
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
base: master
Are you sure you want to change the base?
Changes from all commits
fdc69f6
045ab14
f102ddf
e2467d0
cf57407
ecdb4b5
f81fcae
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 |
|---|---|---|
|
|
@@ -36,6 +36,7 @@ | |
| import java.util.concurrent.CompletableFuture; | ||
| import java.util.concurrent.CopyOnWriteArrayList; | ||
| import java.util.concurrent.ExecutorService; | ||
| import java.util.concurrent.RejectedExecutionException; | ||
| import java.util.concurrent.TimeUnit; | ||
| import java.util.concurrent.atomic.AtomicBoolean; | ||
| import java.util.concurrent.atomic.AtomicInteger; | ||
|
|
@@ -258,9 +259,12 @@ public synchronized void removeConsumer(Consumer consumer) throws BrokerServiceE | |
| notifyAddedToReplay.setTrue(); | ||
| } | ||
| }); | ||
| totalAvailablePermits -= consumer.getAvailablePermits(); | ||
| // Restore the invariant that the dispatcher total equals the sum of the removal balances of the | ||
| // remaining consumers. Exclude Flow permits that have not updated the dispatcher total yet. | ||
| int availablePermits = consumer.getAvailablePermitsForDispatcherRemoval(); | ||
| totalAvailablePermits -= availablePermits; | ||
| log.debug() | ||
| .attr("diffAvailablePermits", consumer.getAvailablePermits()) | ||
| .attr("availablePermits", availablePermits) | ||
| .attr("totalAvailablePermits", totalAvailablePermits) | ||
| .log("Decreased totalAvailablePermits"); | ||
| if (notifyAddedToReplay.booleanValue()) { | ||
|
|
@@ -300,27 +304,45 @@ protected synchronized void clearComponentsAfterRemovedAllConsumers() { | |
|
|
||
| @Override | ||
| public void consumerFlow(Consumer consumer, int additionalNumberOfMessages) { | ||
| topic.getBrokerService().executor().execute(() -> { | ||
| internalConsumerFlow(consumer, additionalNumberOfMessages); | ||
| }); | ||
| Runnable flowTask = () -> internalConsumerFlow(consumer, additionalNumberOfMessages); | ||
| try { | ||
| dispatchMessagesThread.execute(flowTask); | ||
| } catch (RejectedExecutionException e) { | ||
| // Leave the permits pending so removal excludes this unapplied Flow. Never wait for the dispatcher | ||
| // monitor on the connection EventLoop, including while the broker is shutting down. | ||
| log.debug() | ||
| .attr("consumer", consumer) | ||
| .attr("executorShutdown", dispatchMessagesThread.isShutdown()) | ||
| .exception(e) | ||
| .log("Unable to schedule flow control update"); | ||
| } | ||
| } | ||
|
|
||
| private synchronized void internalConsumerFlow(Consumer consumer, int additionalNumberOfMessages) { | ||
| if (!consumerSet.contains(consumer)) { | ||
| private void internalConsumerFlow(Consumer consumer, int additionalNumberOfMessages) { | ||
| boolean connected; | ||
| int updatedTotalAvailablePermits = 0; | ||
| synchronized (this) { | ||
| consumer.completePendingDispatcherFlow(additionalNumberOfMessages); | ||
| connected = containsConsumerInstance(consumer); | ||
| if (connected) { | ||
| totalAvailablePermits += additionalNumberOfMessages; | ||
| updatedTotalAvailablePermits = totalAvailablePermits; | ||
| } | ||
| } | ||
|
|
||
| if (!connected) { | ||
| log.debug() | ||
| .attr("consumer", consumer) | ||
| .log("Ignoring flow control from disconnected consumer"); | ||
| return; | ||
| } | ||
|
|
||
| totalAvailablePermits += additionalNumberOfMessages; | ||
|
|
||
| log.debug() | ||
| .attr("consumer", consumer) | ||
| .attr("totalAvailablePermits", totalAvailablePermits) | ||
| .attr("totalAvailablePermits", updatedTotalAvailablePermits) | ||
| .attr("additionalNumberOfMessages", additionalNumberOfMessages) | ||
| .log("Trigger new read after receiving flow control message"); | ||
| readMoreEntriesAsync(); | ||
| readMoreEntries(); | ||
|
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. PIP-379 intentionally changed the path from readMoreEntries() to readMoreEntriesAsync() and added readMoreEntriesAsyncRequested to deduplicate read triggers. As a result, Flow accounting is now correctly serialized on the dispatchMessagesThread, but each queued Flow re-invokes the full readMoreEntries() path directly, bypassing the deduplication. The q1/JFR stress results appear acceptable, so this does not seem to be a correctness issue. However, should we either preserve per-dispatch-lane read-trigger deduplication or add a comment clarifying that bypassing the PIP-379 dedup is intentional? Without that, this appears to be an unintended regression of the optimization and may be reverted later. |
||
| } | ||
|
|
||
| /** | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.