Repository navigation
Avoid quadratic cursor scans in fanout publishers - #3730
Merged
Merged
Conversation
Motivation: The GraphStage fanout publisher retains a shared ring buffer that scans all cursors for every equally paced reader, requiring O(N^2) cursor work per element. Modification: Count readers at the oldest position and rescan only when the last reader advances or cancels. Preserve lazy cursor initialization and registration, add complexity and lifecycle regressions plus JMH coverage, and document the shared-buffer behavior. Result: Cursor maintenance becomes linear for equally paced readers without changing public APIs or terminal signals. Local JDK 17 publisher benchmarks improve 3.51x with 32 subscribers and 26.58x with 128 under continuous demand. Tests: - Original implementation: complexity regression fails as intended, 1088 > 128. - Updated ring buffer and fanout publisher focused tests: 22 passed. - Expanded Scala/Java stream regression tests: 115 passed. - FanoutPublisherTest Reactive Streams TCK: 31 passed, 7 untested cases skipped. - Native scalafmt, sortImports, checkCodeStyle and git diff --check: passed. - +mimaReportBinaryIssues (Scala 2.13.18 and 3.3.9) and docs/paradox: passed. - Before/after JMH: 2 forks, 3 x 1 s warmup, 5 x 1 s measurement, passed. References: Refs apache#2874; related prior BroadcastHub exploration apache#1705.
He-Pin
requested review from
pjfanning
and
a balanced review from Copilot
and removed request for
pjfanning
October 29, 2026 18:50
This was referenced Oct 30, 2026
pjfanning
pushed a commit
that referenced
this pull request
Oct 30, 2026
Motivation: ActorsLeakSpec failed in PR #3730 CI with Address already in use because its systems bind the default port 7355 and a hardcoded restart port 7356. Modification: Allocate an available port for the first incarnation, reuse its bound address after termination, and assert that restarts retain the address with a new UID. Verify that independent systems receive different ports. Result: The leak test runs while the former fixed ports are occupied and retains its same-address quarantine and actor cleanup assertions. Tests: - Original implementation fails with BindException while port 7356 is held. - Scala 2.13.18 and 3.3.9 on JDK 17: both ActorsLeakSpec tests pass while ports 7355 and 7356 are held. - headerCreateAll, checkCodeStyle and git diff --check pass. - Scala next and validatePullRequest are delegated to GitHub CI. References: Refs #3730 - CI failure in ActorsLeakSpec.
pjfanning
added a commit
to pjfanning/incubator-pekko
that referenced
this pull request
Oct 30, 2026
Add apache#3727, apache#3728, apache#3731, apache#3734, apache#3738 (TLS), apache#3730 (fanout publisher performance) and apache#3733 (shard lease release logging). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pjfanning
added a commit
to pjfanning/incubator-pekko
that referenced
this pull request
Oct 30, 2026
Add apache#3727, apache#3728, apache#3731, apache#3734, apache#3738 (TLS), apache#3730 (fanout publisher performance) and apache#3733 (shard lease release logging).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
The GraphStage fanout publisher introduced in #2874 still uses
ResizableMultiReaderRingBuffer. When N subscribers advance together, each read of the oldest element scans all N cursors, making cursor maintenance O(N²) per published element.Modification
SubscriberManagementconstruction order.Result
Cursor maintenance for equally paced readers is linear. With 32 readers, the regression reports 1088 cursor reads on the original code and passes the 128-read upper bound with this change.
Local JMH on macOS ARM64, Temurin 17.0.21, Scala 2.13.18; 2 forks, 3 × 1 s warmups and 5 × 1 s measurements per fork. Throughput below is input elements per second, with each element delivered to every subscriber:
The 1- and 2-subscriber confidence intervals overlap. The ring-buffer microbenchmark improves 13.95× at 32 readers and 62.15× at 128; its single-reader mean is 5% lower, with overlapping confidence intervals. These benchmarks use continuous demand (
Long.MaxValue); they do not establish the same gains for per-element requests or slow subscribers. Demand accounting still scans subscriptions.Reproduce on both revisions with the same benchmark source:
Tests
stream-tests / Test / testOnly org.apache.pekko.stream.impl.ResizableMultiReaderRingBufferSpec org.apache.pekko.stream.impl.FanoutPublisherBehaviorSpec— 22 passed on JDK 17.FlowSpec,SinkSpec,PublisherSinkSpec,FlowPublisherSinkSpec,SinkTest, and the two focused specs) — 115 passed.stream-tests-tck / Test / testOnly org.apache.pekko.stream.tck.FanoutPublisherTest— 31 passed; 7 TCK cases marked untested were skipped.headerCreateAll, native scalafmt,sortImportsinbench-jmh,checkCodeStyle(including Java formatting and+headerCheckAll), andgit diff --check— passed.+mimaReportBinaryIssues— passed across the repository for Scala 2.13.18 and 3.3.9.docs/paradox— passed (existing duplicate-anchor warnings in release notes).sbt +bench-jmh/compilecheck passed on Scala 2.13.18 and 3.3.9.validatePullRequest— stopped locally; full validation is delegated to GitHub CI. The initial attempt encountered an unrelated macOS ARM64 limitation:leveldbjni-all:1.8contains an x86_64/i386 native library, causingJournalNoCompactionSpecto exit withUnsatisfiedLinkError.References
Refs #2874. Related prior BroadcastHub exploration: #1705.